我的系统需要至少10毫秒的定时器准确度 我选择了timerfd,因为它完全适合我,但发现即使时间长达15毫秒也不准确,或者我不明白它是如何工作的。
我测量的时间在10毫秒计时器上最多可达21毫秒 我已经整理了一个显示我问题的快速测试 这是一个测试:
#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毫秒的延迟听起来对我来说太过分了。
答案 0 :(得分:12)
尝试按如下方式更改它,这应该非常适合它,它永远不会错过唤醒,但要小心它,因为运行实时优先级可以锁定你的机器,如果它不睡觉,也可能需要设置事情,以便您的用户能够以实时优先级运行东西(请参阅/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
。
Realtime也不是关于低延迟,它是关于保证,RT意味着如果你想在第二秒每秒钟恰好唤醒一次,你会,正常的调度没有给你这个,它可能决定唤醒你在100ms之后,因为当时还有其他一些工作要做。如果你想每10分钟醒来并且你真的需要,那么你应该将自己设置为一个实时任务,然后内核会每10分钟唤醒你一次。除非更高优先级的实时任务正忙着做事。
如果你需要保证你的唤醒时间间隔恰好是一段时间,无论它是1毫秒还是1秒,除非你作为实时任务运行,否则你不会得到它。内核有很好的理由为你做到这一点(节省能力是其中之一,更高的吞吐量是另一个,还有其他),但它完全有权这样做,因为你从来没有告诉它你需要更好的保证。大多数东西实际上并不需要这么准确,或者永远不要错过,所以你应该认真思考你是否真的需要它。
引自http://www.ganssle.com/articles/realtime.htm
硬实时任务或系统 一个活动必须是一个 完成 - 总是 - 按指定的 最后期限。截止日期可能是一个 特定时间或时间间隔,或 可能是某些事件的到来。硬 根据定义,实时任务失败 如果他们错过这样的截止日期。
注意这个定义没有 关于频率的假设 任务期间。一微秒或 一周 - 如果错过截止日期 诱导失败,那么任务就有了 硬实时要求。
软实时几乎是一样的,除了错过最后期限,虽然不受欢迎,但不是世界末日(例如视频和音频播放是软实时任务,你不想错过显示框架,或者用完缓冲区,但是如果你这样做只是暂时的打嗝,你只需继续)。如果你想要做的是“软”实时,我不会为实时优先运行而烦恼,因为你通常应该及时唤醒(或者至少接近它)。
编辑:
如果您没有实时运行,内核将默认为任何计时器提供一些“松弛”,以便它可以合并您的请求以唤醒与您所要求的接近的事件发生的其他事件(如果另一个事件是在你的'松弛'时间内,它会在你问的时候不会叫醒你,但是早一点或更晚,同时它已经会做其他事情,这可以节省电力。)
有关更多信息,请参阅High- (but not too high-) resolution timeouts和Timer slack(注意我不确定这些内容中的任何一个是否真的在内核中,因为这两篇文章都是关于lkml邮件列表讨论的,但是第一个真正存在于内核中的东西。
答案 1 :(得分:3)
我感觉你的测试非常依赖于硬件。当我在我的系统上运行示例程序时,它似乎在1ms处挂起。为了使我的计算机上的测试完全有意义,我不得不从毫秒改为微秒。 (我将乘数从1_000_000更改为1_000。)
$ grep 1000 test.c timspec.it_interval.tv_nsec = microseconds * 1000;
$ for i in 1 2 4 5 7 8 9 15 16 17\ 31 32 33 47 48 49 63 64 65 ; do\ echo "intervals of $i microseconds";\ ./test $i;done intervals of 1 microseconds 11 expirations, 0 iterations intervals of 2 microseconds 5 expirations, 0 iterations intervals of 4 microseconds 3 expirations, 0 iterations intervals of 5 microseconds 2 expirations, 0 iterations intervals of 7 microseconds 2 expirations, 0 iterations intervals of 8 microseconds 2 expirations, 0 iterations intervals of 9 microseconds 2 expirations, 0 iterations intervals of 15 microseconds 2 expirations, 7788 iterations intervals of 16 microseconds 4 expirations, 1646767 iterations intervals of 17 microseconds 2 expirations, 597 iterations intervals of 31 microseconds 2 expirations, 370969 iterations intervals of 32 microseconds 2 expirations, 163167 iterations intervals of 33 microseconds 2 expirations, 3267 iterations intervals of 47 microseconds 2 expirations, 1913584 iterations intervals of 48 microseconds 2 expirations, 31 iterations intervals of 49 microseconds 2 expirations, 17852 iterations intervals of 63 microseconds 2 expirations, 24 iterations intervals of 64 microseconds 2 expirations, 2888 iterations intervals of 65 microseconds 2 expirations, 37668 iterations
(有点有趣的是我从16和47微秒获得了最长的时间,但是17和48是非常糟糕的。)
时间(7)对我们的平台为何如此不同有一些建议:
High-Resolution Timers Before Linux 2.6.21, the accuracy of timer and sleep system calls (see below) was also limited by the size of the jiffy. Since Linux 2.6.21, Linux supports high-resolution timers (HRTs), optionally configurable via CONFIG_HIGH_RES_TIMERS. On a system that supports HRTs, the accuracy of sleep and timer system calls is no longer constrained by the jiffy, but instead can be as accurate as the hardware allows (microsecond accuracy is typical of modern hardware). You can determine whether high-resolution timers are supported by checking the resolution returned by a call to clock_getres(2) or looking at the "resolution" entries in /proc/timer_list. HRTs are not supported on all hardware architectures. (Support is provided on x86, arm, and powerpc, among others.)
我的/ proc / timer_list中的所有“分辨率”行在我的(无可置疑的非常强大的)x86_64系统上都是1ns。
我决定试图找出计算机上“断点”的位置,但放弃了110微秒的运行:
$ for i in 70 80 90 100 110 120 130\ ; do echo "intervals of $i microseconds";\ ./test $i;done intervals of 70 microseconds 2 expirations, 639236 iterations intervals of 80 microseconds 2 expirations, 150304 iterations intervals of 90 microseconds 4 expirations, 3368248 iterations intervals of 100 microseconds 4 expirations, 1964857 iterations intervals of 110 microseconds ^C
90微秒,在失败几次之前进行了300万次迭代;这比你的第一次测试的分辨率提高了22倍,所以我说如果硬件合适,10ms不应该接近困难。 (90微秒的分辨率是10毫秒的111倍。)
但是如果您的硬件没有为高分辨率计时器提供计时器,那么Linux无法使用SCHED_RR或SCHED_FIFO来帮助您。即使这样,也许另一个内核可以更好地为您提供所需的软件定时器支持。
祝你好运。 :)答案 2 :(得分:1)
这是一个理论。如果系统将HZ设置为250(典型值),那么您的计时器分辨率为4毫秒。一旦您的进程被调度程序换出,很可能会在您的进程获得另一个时间片之前调度并运行许多其他进程。这可以解释您在15到21毫秒范围内看到计时器分辨率。解决这个问题的唯一方法是运行实时内核。
非实时系统上高分辨率计时的典型解决方案基本上是忙于等待选择呼叫。
答案 3 :(得分:0)
根据系统正在执行的操作,切换回任务可能会有点慢。除非你有一个“真正的”实时系统,否则不能保证它会比你看到的更好,尽管我同意结果有点令人失望。
您可以(大部分)消除任务切换/调度程序时间。如果您有备用的CPU电源(和电源!),那么一个残酷而有效的解决方案就是忙碌的等待旋转循环。
我们的想法是在一个紧密的循环中运行你的程序,该循环不断地将时钟轮询到它的时间,然后在时机成熟时调用你的其他代码。代价是让你的系统对其他所有事情都非常缓慢并且加热你的CPU,你最终会得到几乎没有抖动的任务调度。
我在Windows XP下编写了一个类似这样的系统来旋转步进电机,每秒提供均匀间隔的脉冲达40K次,并且工作正常。当然,您的里程可能会有所不同。