Linux,timerfd的准确性

我有一个系统需要至less10毫秒的定时器的准确性。
我去了timerfd,因为它非常适合我,但是发现即使在15毫秒的时间内也是不准确的,要么我不明白它是如何工作的。

我测量的时间在10毫秒计时器上高达21毫秒。
我已经做了一个快速testing,显示我的问题。
这里testing一下:

#include <sys/timerfd.h> #include <time.h> #include <string.h> #include <stdint.h> int main(int argc, char *argv[]){ int timerfd = timerfd_create(CLOCK_MONOTONIC,0); int milliseconds = atoi(argv[1]); struct itimerspec timspec; bzero(&timspec, sizeof(timspec)); timspec.it_interval.tv_sec = 0; timspec.it_interval.tv_nsec = milliseconds * 1000000; timspec.it_value.tv_sec = 0; timspec.it_value.tv_nsec = 1; int res = timerfd_settime(timerfd, 0, &timspec, 0); if(res < 0){ perror("timerfd_settime:"); } uint64_t expirations = 0; int iterations = 0; while( res = read(timerfd, &expirations, sizeof(expirations))){ if(res < 0){ perror("read:"); continue; } if(expirations > 1){ printf("%lld expirations, %d iterations\n", expirations, iterations); break; } iterations++; } } 

并像这样执行:

 Zack ~$ for i in 2 4 8 10 15; do echo "intervals of $i milliseconds"; ./test $i;done intervals of 2 milliseconds 2 expirations, 1 iterations intervals of 4 milliseconds 2 expirations, 6381 iterations intervals of 8 milliseconds 2 expirations, 21764 iterations intervals of 10 milliseconds 2 expirations, 1089 iterations intervals of 15 milliseconds 2 expirations, 3085 iterations 

即使假设一些可能的延迟,15毫秒延迟听起来对我来说太多了。

Solutions Collecting From Web of "Linux,timerfd的准确性"

尝试改变它如下,这应该是非常garuntee,它不会错过一个唤醒,但要小心,因为运行实时优先级可以锁定你的机器,如果它不睡觉,也可能需要设置您的用户有能力以实时优先级运行内容(请参阅/etc/security/limits.conf

 #include <sys/timerfd.h> #include <time.h> #include <string.h> #include <stdint.h> #include <stdio.h> #include <sched.h> int main(int argc, char *argv[]) { int timerfd = timerfd_create(CLOCK_MONOTONIC,0); int milliseconds = atoi(argv[1]); struct itimerspec timspec; struct sched_param schedparm; memset(&schedparm, 0, sizeof(schedparm)); schedparm.sched_priority = 1; // lowest rt priority sched_setscheduler(0, SCHED_FIFO, &schedparm); bzero(&timspec, sizeof(timspec)); timspec.it_interval.tv_sec = 0; timspec.it_interval.tv_nsec = milliseconds * 1000000; timspec.it_value.tv_sec = 0; timspec.it_value.tv_nsec = 1; int res = timerfd_settime(timerfd, 0, &timspec, 0); if(res < 0){ perror("timerfd_settime:"); } uint64_t expirations = 0; int iterations = 0; while( res = read(timerfd, &expirations, sizeof(expirations))){ if(res < 0){ perror("read:"); continue; } if(expirations > 1){ printf("%ld expirations, %d iterations\n", expirations, iterations); break; } iterations++; } } 

如果你正在使用线程,你应该使用pthread_setschedparam而不是sched_setscheduler

实时也不是关于低延迟,而是关于保证,RT意味着,如果你想每秒醒来一次,你会,正常的调度不会给你这个,它可能会决定唤醒你100ms以后呢,因为那个时候还有其他的工作要做。 如果你想每10ms唤醒一次,你真的需要,那么你应该设置自己作为一个实时任务运行,然后内核将每隔10毫秒唤醒你,而不会失败。 除非更高优先级的实时任务忙于做东西。

如果您需要保证您的唤醒间隔时间恰好是一段时间,那么无论是1ms还是1秒都无所谓,除非您作为实时任务运行,否则您将无法获得。 内核会对你做这些事情有很好的理由(节约能源是其中之一,更高的吞吐量是另一个,还有其他的),但是这样做是有好处的,因为你从未告诉过你需要更好的保证。 大多数东西实际上并不需要这个准确,或者不要错过,所以你应该认真思考你是否真的需要它。

http://www.ganssle.com/articles/realtime.htm引&#x7528;

一个艰难的实时任务或系统是一个活动只需要完成 – 总是 – 在指定的最后期限。 截止日期可能是特定的时间或时间间隔,也可能是某个事件的到来。 根据定义,硬实时任务失败,如果他们错过了这样的最后期限。

注意这个定义对任务的频率或周期没有任何假设。 一个微秒或一个星期 – 如果错过了截止日期会导致失败,那么这个任务就很难实时要求。

软实时是几乎相同的,除了错过最后期限,而不希望的,不是世界末日(例如,视频和音频播放是软实时任务,你不想错过显示一帧,或用完的缓冲区,但如果你这样做只是一个瞬间hiccough,而你只是继续)。 如果你想做的事情是“软”实时,我不会打扰与实时优先级,因为你通常应该及时(或至少接近它)的唤醒。

编辑:

如果你没有实时运行,内核会默认给你任何定时器一些“松弛”,这样它就可以合并你的请求来唤醒其他事件,这些事件发生在接近你请求的那个时刻(也就是说,其他事件在你的“闲置”时间内,在你问的时候它不会唤醒你,但有一点早或晚,同时它已经在做别的事情了,这样可以节省电力)。

有关更多信息,请参阅高(但不是太高)的解决方案超时和定时器松弛 (注意:我不确定这些内容是否是内核中的真正内容,因为这两篇文章都是关于lkml邮件列表的讨论,但像第一个真正在内核中的东西。

我有一种感觉,你的测试是非常依赖硬件的。 当我在我的系统上运行你的示例程序时,它似乎在1ms处挂起。 为了让你的测试在我的计算机上有意义,我必须从毫秒变为微秒。 (我把乘数从1_000_000改成了1_000。)

 $ grep 1000 test.c
     timspec.it_interval.tv_nsec =微秒* 1000;
 $ for i in 1 2 4 5 7 8 9 15 16 17 \
  31 32 33 47 48 49 63 64 65; 做\
 回声“$ i微秒的间隔”; \
  ./test $ i;完成
间隔1微秒
 11次到期,0次迭代
 2微秒的间隔
 5次到期,0次迭代
间隔4微秒
 3次到期,0次迭代
 5微秒的间隔
 2次到期,0次迭代
 7微秒的间隔
 2次到期,0次迭代
间隔8微秒
 2次到期,0次迭代
间隔9微秒
 2次到期,0次迭代
间隔15微秒
 2次到期,7788次迭代
间隔16微秒
 4次到期,1646767次迭代
间隔17微秒
 2次到期,597次迭代
 31微秒的间隔
 2次到期,370969次迭代
 32微秒的间隔
 2次到期,163167次迭代
间隔33微秒
 2次到期,3267次迭代
 47微秒的间隔
 2次到期,1913584次迭代
 48微秒的间隔
 2次到期,31次迭代
间隔49微秒
 2次到期,17852次迭代
间隔63微秒
 2次到期,24次迭代
间隔64微秒
 2次到期,2888次迭代
间隔65微秒
 2次到期,37668次迭代

(有点有趣,我得到了16和47微秒的最长时间,但17和48是可怕的。)

时间(7)对于我们的平台如此不同的一些建议:

   高分辨率定时器
       在Linux 2.6.21之前,定时器和睡眠系统的准确性
       电话(见下文)也受jiffy大小的限制。

       由于Linux 2.6.21,Linux支持高分辨率定时器
        (HRT),可以通过CONFIG_HIGH_RES_TIMERS进行配置。 上
       支持HRT的系统,睡眠和定时器的准确性
       系统调用不再受制于jiffy,而是相反
       可以像硬件一样准确(微秒精度
       是现代硬件的典型)。 你可以确定是否
       通过检查分辨率来支持高分辨率定时器
       通过调用clock_getres(2)返回或者查看
        / proc / timer_list中的“分辨率”条目。

       所有硬件架构都不支持HRT。  (支持
       在x86,arm和powerpc等等上提供。)

我的/ proc / timer_list中的所有“分辨率”行在我的(可以说是功能强大的)x86_64系统上是1ns。

我决定试图找出我的电脑上的“突破点”在哪里,但放弃了110微秒的运行:

 $ for i in 70 80 90 100 110 120 130 \
  ; 做回声“$ i微秒的时间间隔”; \
  ./test $ i;完成
 70微秒的间隔
 2次到期,639236次迭代
 80微秒的间隔
 2次到期,150304次迭代
间隔90微秒
 4次到期,3368248次迭代
 100微秒的间隔
 4次到期,1964857次迭代
间隔110微秒
 ^ C

在几次失败之前,90微秒进行了300万次迭代; 这比第一次测试的分辨率高出22倍,所以我会说,给定合适的硬件,10毫秒不应该在任何地方接近困难。 (90微秒的分辨率是10毫秒的111倍)。

但是如果你的硬件没有提供高分辨率定时器的定时器,那么Linux就不能使用SCHED_RR或SCHED_FIFO来帮助你。 即使如此,也许另一个内核可以更好地为您提供所需的软件定时器支持。

祝你好运。 🙂

这是一个理论。 如果您的系统的HZ设置为250(典型值),那么您的计时器分辨率为4毫秒。 一旦你的程序被调度程序换出,在你的程序获得另一个时间片之前,很可能会安排和运行一些其他的程序。 这可能解释你看到在15到21毫秒范围内的定时器分辨率。 解决这个问题的唯一方法就是运行实时内核。

非实时系统的高分辨率定时的典型解决方案基本上是忙于等待和选择呼叫。

根据系统在做什么,在切换回任务时可能会有点慢。 除非你有一个“真正的”实时系统,否则不能保证它比你所看到的要好,但我同意这个结果有点令人失望。

您可以(主要)消除该任务切换/调度程序时间。 如果你有CPU的能量(和电力!),备用,一个残酷但有效的解决方案将是一个繁忙的等待旋转循环。

这个想法是在一个严格的循环中运行你的程序,不断地轮询时钟是什么时间,然后在时间正确的时候调用你的其他代码。 代价是让系统对其他任何事情非常缓慢,并加热CPU,最终的任务调度大部分是无抖动的。

我在Windows XP下编写了一个这样的系统来驱动一个步进电机,提供每秒高达40K次的均匀间隔的脉冲,并且工作正常。 当然,你的里程可能会有所不同。