一尘不染

Linux,timerfd准确性

linux

我的系统需要至少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毫秒的延迟听起来也太多了。


阅读 690

收藏
2020-06-07

共1个答案

一尘不染

尝试按以下方式进行更改,这应该确保它永远不会丢失唤醒,但要谨慎行事,因为运行实时优先级会在机器不hibernate时将其锁定,因此您可能需要进行设置您的用户可以实时运行内容(请参阅参考资料/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表示,如果您想每秒精确地每秒唤醒一次,那么您将,正常的调度不会给您这样做,它可能会决定将您唤醒100毫秒后来,因为当时还是有其他工作要做。如果您确实需要每10毫秒唤醒一次,那么您应该将自己设置为作为实时任务运行,然后内核将每10毫秒唤醒您一次,而不会失败。除非更高优先级的实时任务忙于做事。

如果您需要确保唤醒间隔恰好是某个时间,那么它是1毫秒还是1秒无关紧要,除非您作为实时任务运行,否则您不会得到它。内核会为您做到这一点有充分的理由(节省功率是其中之一,更高的吞吐量是另外一个,还有其他方面),但是这样做是有权利的,因为您从未告诉过您需要更好的保证。大多数东西实际上并不需要那么精确,或者永远不要错过,所以您应该认真考虑您是否真的需要它。

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

硬实时任务或系统是必须在指定的截止日期之前(总是)完成一项活动的系统。截止日期可以是特定时间或时间间隔,也可以是某个事件的到来。根据定义,如果硬实时任务错过了这样的期限,它们就会失败。

请注意,此定义不对任务的频率或周期做出任何假设。一微秒或一周-如果错过最后期限会导致失败,那么任务对实时性有严格要求。

软实时几乎是相同的,只是错过了最后期限,尽管不是很理想,但这并不是世界末日(例如,视频和音频播放是软实时任务,您不想错过显示帧或用尽时间)缓冲区,但是如果您这样做只是暂时的打hi,您可以继续执行)。如果您想做的是“软”实时的,那么我不会为实时优先运行而烦恼,因为您通常应该及时(或至少接近)唤醒您。

编辑:

如果您不是实时运行的,则内核默认会给您提供一些“松弛”的计时器,以便它可以合并您的请求以唤醒其他事件,这些事件在接近您要求的时间发生(即是否另一个事件是在您的“空闲”时间内,它不会在您询问时唤醒您,但会早一点或晚一点,同时已经在做其他事情了,这样可以节省电量。

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

2020-06-07