如何在Linux中获得最准确的实时周期性中断?

Mat*_*att 7 linux real-time timer

我希望在10的幂的频率下被中断,因此启用/ dev/rtc的中断并不理想.我想在中断之间睡1毫秒或250微秒.

从/ dev/hpet启用定期中断非常有效,但它似乎在某些机器上不起作用.显然我不能在没有HPET的机器上使用它.但我无法让它在一些可以作为时钟源使用的机器上工作.例如,在Core 2 Quad上,当设置为poll时,内核文档中包含的示例程序在HPET_IE_ON处失败.

使用Linux提供的itimer接口而不是直接与硬件设备驱动程序连接会更好.在某些系统上,itimer提供的定期中断随着时间的推移更加稳定.也就是说,由于hpet不能以我想要的频率中断,因此中断开始从挂起时间开始漂移.但我发现有些系统的睡眠时间比使用itimer要长(10+毫秒).

这是一个使用itimer进行中断的测试程序.在某些系统上,它只打印出一个警告,它在目标时间内睡眠约100微秒左右.在其他情况下,它将打印出批次警告,它在目标时间内睡了10+毫秒.用-lrt编译并运行sudo chrt -f 50 [name]

#include <stdio.h>
#include <stdlib.h>
#include <unistd.h>
#include <error.h>
#include <errno.h>
#include <sys/ioctl.h>
#include <sys/types.h>
#include <sys/time.h>
#include <time.h>
#include <signal.h>
#include <fcntl.h>
#define NS_PER_SECOND 1000000000LL
#define TIMESPEC_TO_NS( aTime ) ( ( NS_PER_SECOND * ( ( long long int ) aTime.tv_sec ) ) \
    + aTime.tv_nsec )

int main()
{
    // Block alarm signal, will be waited on explicitly
    sigset_t lAlarm;
    sigemptyset( &lAlarm );
    sigaddset( &lAlarm, SIGALRM  );
    sigprocmask( SIG_BLOCK, &lAlarm, NULL );

    // Set up periodic interrupt timer
    struct itimerval lTimer;
    int lReceivedSignal = 0;

    lTimer.it_value.tv_sec = 0;
    lTimer.it_value.tv_usec = 250;
    lTimer.it_interval = lTimer.it_value;

    // Start timer
    if ( setitimer( ITIMER_REAL, &lTimer, NULL ) != 0 )
    {
        error( EXIT_FAILURE, errno, "Could not start interval timer" );
    }
    struct timespec lLastTime;
    struct timespec lCurrentTime;
    clock_gettime( CLOCK_REALTIME, &lLastTime );
    while ( 1 )
    {
        //Periodic wait
        if ( sigwait( &lAlarm, &lReceivedSignal ) != 0 )
        {
            error( EXIT_FAILURE, errno, "Failed to wait for next clock tick" );
        }
        clock_gettime( CLOCK_REALTIME, &lCurrentTime );
        long long int lDifference = 
            ( TIMESPEC_TO_NS( lCurrentTime ) - TIMESPEC_TO_NS( lLastTime ) );
        if ( lDifference  > 300000 )
        {
            fprintf( stderr, "Waited too long: %lld\n", lDifference  );
        }
        lLastTime = lCurrentTime;
    }
    return 0;
}
Run Code Online (Sandbox Code Playgroud)

SzG*_*SzG 7

裸机setitimer()设置我遇到了同样的问题.问题是默认情况下,您的进程由静态优先级0的SCHED_OTHER策略调度.这意味着您在一个包含所有其他进程的池中,并且动态优先级决定.有一些系统负载的那一刻,你会得到延迟.

解决方案是使用sched_setscheduler()系统调用,将静态优先级提高到至少一个,并指定SCHED_FIFO策略.它引起了戏剧性的改善.

#include <sched.h>
...
int main(int argc, char *argv[])
{
    ...
    struct sched_param schedp;
    schedp.sched_priority = 1;
    sched_setscheduler(0, SCHED_FIFO, &schedp);
    ...
}
Run Code Online (Sandbox Code Playgroud)

您必须以root身份运行才能执行此操作.另一种方法是使用chrt程序执行相同操作,但您必须知道RT过程的PID.

sudo chrt -f -p 1 <pid>
Run Code Online (Sandbox Code Playgroud)

在这里查看我的博客文章.


Rak*_*kis 5

无论您使用何种计时机制,它都可以归结为任务运行状态的变化,调用内核调度程序(通常每秒100或1000次)以及与其他进程的cpu争用的组合.

我发现在Linux(Windows也是如此)上实现"最佳"时机的机制是执行以下操作:

  1. 将过程放在屏蔽CPU上
  2. 让该过程最初睡眠1ms.如果在屏蔽的CPU上,您的进程应该在OS调度程序的刻度线边界上唤醒
  3. 直接使用RDTSC或CLOCK_MONOTONIC捕获当前时间.将其用作计算所有未来周期的绝对唤醒时间的零时间.这有助于减少随时间的漂移.它不能完全消除,因为硬件计时随着时间的推移而波动(热问题等),但这是一个非常好的开始.
  4. 创建一个休眠函数,该函数在目标绝对唤醒时间之前休眠1ms(因为这是OS调度程序可以是最准确的)然后在紧密循环中刻录CPU,不断检查RDTSC/CLOCK_REALTIME值.

这需要一些工作,但使用这种方法可以获得相当不错的结果.您可以在此处找到您可能想要查看的相关问题.

  • Linux现在可以在任何准备就绪时安排任务,而不是依赖于每次运行的调度程序(我相信这是NO_HZ选项).如果HPET方法完全正常工作,HPET方法(在read()调用上阻塞)适用于每250微秒上下文切换到高实时优先级任务.itimer方法通常用于做同样的事情.在某些机器上,它一直有效.在其他情况下,它通常有效(我没有充斥着警告信息),但有时我会等待1MS +. (2认同)