Linux,timerfd 精度

发布于 2024-09-07 03:25:53 字数 1731 浏览 1 评论 0原文

我的系统需要至少 10 毫秒的计时器精度。
我选择了timerfd,因为它非常适合我,但发现即使时间长达15毫秒,它也根本不准确,或者我不明白它是如何工作的。

我测得的时间在 10 毫秒计时器上长达 21 毫秒。
我已经整理了一个快速测试来显示我的问题。
这里有一个测试:

#include <sys/timerfd.h>
#include <time.h>
#include <string.h>
#include <stdint.h>
#include <stdlib.h>
#include <stdio.h>
#include <unistd.h>
#include <inttypes.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:");
       return 1;
    }
    uint64_t expirations = 0;
    int iterations = 0;
    while( res = read(timerfd, &expirations, sizeof(expirations))){
        if(res < 0){ perror("read:"); continue; }
        if(expirations > 1){
            printf("%" PRIu64 " expirations, %d iterations\n", expirations, iterations);
            break;
        }
        iterations++;
    }
    return 0;
}

并像这样执行:

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 毫秒的延迟对我来说听起来太多了。

I have a system that needs at least 10 mseconds of accuracy for timers.
I went for timerfd as it suits me perfectly, but found that even for times up to 15 milliseconds it is not accurate at all, either that or I don't understand how it works.

The times I have measured were up to 21 mseconds on a 10 mseconds timer.
I have put together a quick test that shows my problem.
Here a test:

#include <sys/timerfd.h>
#include <time.h>
#include <string.h>
#include <stdint.h>
#include <stdlib.h>
#include <stdio.h>
#include <unistd.h>
#include <inttypes.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:");
       return 1;
    }
    uint64_t expirations = 0;
    int iterations = 0;
    while( res = read(timerfd, &expirations, sizeof(expirations))){
        if(res < 0){ perror("read:"); continue; }
        if(expirations > 1){
            printf("%" PRIu64 " expirations, %d iterations\n", expirations, iterations);
            break;
        }
        iterations++;
    }
    return 0;
}

And executed like this:

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

Even assuming some possible delays, 15 milliseconds delays sounds too much for me.

如果你对这篇内容有疑问,欢迎到本站社区发帖提问 参与讨论,获取更多帮助,或者扫码二维码加入 Web 技术交流群。

扫码二维码加入Web技术交流群

发布评论

需要 登录 才能够评论, 你可以免费 注册 一个本站的账号。

评论(4

吻风 2024-09-14 03:25:55

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

非实时系统上高分辨率计时的典型解决方案基本上是忙于等待 select 调用。

Here's a theory. If HZ is set to 250 for your system ( as is typical ) then you have a 4 millisecond timer resolution. Once your process is swapped out by the scheduler, it's likely that a number of other processes will be scheduled and run before your process gets another time slice. This might explain you seeing timer resolutions in the 15 to 21 millisecond range. The only way to get around this would be to run a real-time kernel.

The typical solution for high resolution timing on non-realtime systems is basically to busy wait with a call to select.

莳間冲淡了誓言ζ 2024-09-14 03:25:54

尝试按如下方式更改它,这几乎可以保证它永远不会错过唤醒,但要小心,因为运行实时优先级如果不睡眠,可能会硬锁定您的计算机,而且您可能需要进行设置,以便您的用户能够以实时优先级运行某些内容(请参阅 /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后来,因为当时无论如何还有其他工作要做。如果您想每 10 毫秒唤醒一次并且确实需要,那么您应该将自己设置为实时任务运行,然后内核将每 10 毫秒唤醒您一次,不会失败。除非优先级较高的实时任务正忙着做事情。

如果您需要保证唤醒间隔恰好是某个时间(无论是 1 毫秒还是 1 秒),除非您作为实时任务运行,否则您将无法获得它。内核会对你这样做有充分的理由(节省电力是其中之一,更高的吞吐量是另一个,还有其他),但这样做完全在它的权利之内,因为你从未告诉它你需要更好的保证。大多数东西实际上并不需要如此准确,或者不需要永远不会错过,所以你应该认真考虑你是否真的需要它。

引用自 http://www.ganssle.com/articles/realtime.htm

硬实时任务或系统是
一项活动必须是
完成 - 始终 - 由指定的
最后期限。截止日期可能是
特定时间或时间间隔,或
可能是某些事件的到来。难的
根据定义,实时任务失败,
如果他们错过了这样的截止日期。

注意这个定义没有
关于频率的假设或
任务期间。一微秒或
一周 - 如果错过最后期限
导致失败,那么任务有
硬实时要求。

软实时几乎是相同的,除了错过最后期限,虽然不受欢迎,但这并不是世界末日(例如视频和音频播放是软实时任务,您不想错过显示帧或用完)缓冲区,但如果你这样做,这只是暂时的打嗝,你只需继续)。如果您尝试做的是“软”实时,我不会费心以实时优先级运行,因为您通常应该及时(或至少接近)唤醒。

编辑:

如果您没有实时运行,内核默认情况下会给您设置一些“松弛”的任何计时器,以便它可以将您的唤醒请求与有时发生的接近您要求的事件的其他事件合并(即如果其他事件在您的“空闲”时间内,它不会在您要求的时间唤醒您,而是早一点或晚一点,同时它已经要做其他事情,这可以节省电量)。

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

Try altering it as follows, this should pretty much guarantee that it'll never miss a wakeup, but be careful with it since running realtime priority can lock your machine hard if it doesn't sleep, also you may need to set things up so that your user has the ability to run stuff at realtime priority (see /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++;
    }
}

If you are using threads you should use pthread_setschedparam instead of sched_setscheduler.

Realtime also isn't about low latency, it's about guarantees, RT means that if you want to wake up exactly once every second on the second, you WILL, the normal scheduling does not give you this, it might decide to wake you up 100ms later, because it had some other work to do at that time anyway. If you want to wake up every 10ms and you REALLY do need to, then you should set yourself to run as a realtime task then the kernel will wake you up every 10ms without fail. Unless a higher priority realtime task is busy doing stuff.

If you need to guarantee that your wakeup interval is exactly some time it doesn't matter if it's 1ms or 1 second, you won't get it unless you run as a realtime task. There are good reasons the kernel will do this to you (saving power is one of them, higher throughput is another, there are others), but it's well within it's rights to do so since you never told it you need better guarantees. Most stuff doesn't actually need to be this accurate, or need to never miss so you should think hard about whether or not you really do need it.

quoting from http://www.ganssle.com/articles/realtime.htm

A hard real time task or system is
one where an activity simply must be
completed - always - by a specified
deadline. The deadline may be a
particular time or time interval, or
may be the arrival of some event. Hard
real time tasks fail, by definition,
if they miss such a deadline.

Notice this definition makes no
assumptions about the frequency or
period of the tasks. A microsecond or
a week - if missing the deadline
induces failure, then the task has
hard real time requirements.

Soft realtime is pretty much the same, except that missing a deadline, while undesirable, is not the end of the world (for example video and audio playback are soft realtime tasks, you don't want to miss displaying a frame, or run out of buffer, but if you do it's just a momentary hiccough, and you simply continue). If what you are trying to do is 'soft' realtime I wouldn't bother with running at realtime priority since you should generally get your wakeups in time (or at least close to it).

EDIT:

If you aren't running realtime the kernel will by default give any timers you make some 'slack' so that it can merge your request to wake up with other events that happen at times close to the one you asked for (that is if the other event is within your 'slack' time it will not wake you at the time you asked, but a little earlier or later, at the same time it was already going to do something else, this saves power).

For a little more info see High- (but not too high-) resolution timeouts and Timer slack (note I'm not sure if either of those things is exactly what's really in the kernel since both those articles are about lkml mailing list discussions, but something like the first one really is in the kernel.

心凉 2024-09-14 03:25:54

我有一种感觉,您的测试非常依赖于硬件。当我在我的系统上运行你的示例程序时,它似乎在 1 毫秒处挂起。为了使您的测试在我的计算机上有意义,我必须从毫秒更改为微秒。 (我将乘数从 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 很糟糕。)

建议:

   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.)

time(7) 对于我们的平台为何如此不同有一些 我的 /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 倍,所以我想说,如果有合适的硬件,10 毫秒应该不是什么困难的事情。 (90 微秒的分辨率是 10 毫秒的 111 倍。)

但是,如果您的硬件不提供高分辨率定时器的定时器,那么 Linux 在不求助于 SCHED_RR 或 SCHED_FIFO 的情况下无法为您提供帮助。即使如此,也许另一个内核可以更好地为您提供所需的软件计时器支持。

祝你好运。 :)

I've got a feeling that your test is very hardware dependent. When I ran your sample program on my system, it appeared to hang at 1ms. To make your test at all meaningful on my computer, I had to change from milliseconds to microseconds. (I changed the multiplier from 1_000_000 to 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

(Somewhat interesting that I got the longest runs from 16 and 47 microseconds, but 17 and 48 were awful.)

time(7) has some suggestions on why our platforms are so different:

   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.)

All the 'resolution' lines in my /proc/timer_list are 1ns on my (admittedly ridiculously powerful) x86_64 system.

I decided to try to figure out where the 'breaking point' is on my computer, but gave up on the 110 microsecond run:

$ 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 microseconds ran for three million iterations before it failed a few times; that's 22 times better resolution than your very first test, so I'd say that given the right hardware, 10ms shouldn't be anywhere near difficult. (90 microseconds is 111 times better resolution than 10 milliseconds.)

But if your hardware doesn't provide the timers for high resolution timers, then Linux can't help you without resorting to SCHED_RR or SCHED_FIFO. And even then, perhaps another kernel could better provide you with the software timer support you need.

Good luck. :)

↙厌世 2024-09-14 03:25:54

根据系统正在执行的其他操作,切换回您的任务可能会有点慢。除非您有一个“真正的”实时系统,否则不能保证它会比您所看到的更好,尽管我同意结果有点令人失望。

您(大部分)可以消除任务切换/调度程序时间。如果您有空闲的 CPU 能力(和电力!),一个残酷但有效的解决方案是繁忙的等待旋转循环。

这个想法是在一个紧密的循环中运行你的程序,不断轮询时钟的时间,然后在时间合适时调用你的其他代码。以让您的系统在其他所有事情上表现得非常缓慢并加热您的 CPU 为代价,您最终将得到几乎没有抖动的任务调度。

我曾经在 Windows XP 下编写过一个这样的系统来旋转步进电机,每秒提供高达 40K 次的均匀间隔脉冲,并且运行良好。当然,您的里程可能会有所不同。

Depending on what else the system is doing, it may be a bit slow in switching back to your task. Unless you have a "real" realtime system, there's no guarantee it will do better than what you're seeing, although I agree that result is a bit disappointing.

You can (mostly) eliminate that task switch / scheduler time. If you have CPU power (and electrical power!) to spare, a brutal but effective solution would be a busy wait spin loop.

The idea is to run your program in a tight loop that continuously polls the clock for what time it is, and then calls your other code when the time is right. At the expense of making your system act very sluggish for everything else and heating up your CPU, you will end up with task scheduling that is mostly jitter free.

I wrote a system like this once under Windows XP to spin a stepper motor, supplying evenly spaced pulses up to 40K times per second, and it worked fine. Of course, your mileage may vary.

~没有更多了~
我们使用 Cookies 和其他技术来定制您的体验包括您的登录状态等。通过阅读我们的 隐私政策 了解更多相关信息。 单击 接受 或继续使用网站,即表示您同意使用 Cookies 和您的相关数据。
原文