【问题标题】:How do I get the most accurate realtime periodic interrupts in Linux?如何在 Linux 中获得最准确的实时周期性中断?
【发布时间】:2011-08-15 13:38:33
【问题描述】:

我希望以 10 次方的频率被打断,因此从 /dev/rtc 启用中断并不理想。我想在两次中断之间睡 1 毫秒或 250 微秒。

从 /dev/hpet 启用周期性中断效果很好,但它似乎不适用于某些机器。显然,我不能在实际上没有 HPET 的机器上使用它。但我也无法让它在一些将 hpet 用作时钟源的机器上工作。例如,在 Core 2 Quad 上,内核文档中包含的示例程序在设置为 poll 时会在 HPET_IE_ON 处失败。

最好使用Linux提供的itimer接口,而不是直接与硬件设备驱动程序接口。在某些系统上,定时器提供了随着时间的推移更加稳定的周期性中断。也就是说,由于 hpet 不能以我想要的频率中断,中断开始从墙上时间漂移。但是我看到一些系统的睡眠时间比使用定时器的时间长(10 多毫秒)。

这是一个使用定时器中断的测试程序。在某些系统上,它只会打印出一个警告,即它在目标时间内睡了大约 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;
}

【问题讨论】:

  • 这可能是内核错误。我的 itimer 示例似乎在所有使用 2.6.32 的机器上都可以正常工作,但在 2.6.35 或 2.6.38 上却不行。

标签: linux timer real-time


【解决方案1】:

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

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

  1. 将进程放在Shielded CPU
  2. 让进程最初休眠 1 毫秒。如果在受屏蔽的 CPU 上,您的进程应该在 OS 调度程序的滴答边界上唤醒
  3. 直接使用 RDTSC 或 CLOCK_MONOTONIC 来捕获当前时间。将此用作计算所有未来时段的绝对唤醒时间的零时间。这将有助于最大限度地减少随时间的漂移。由于硬件计时会随时间波动(散热问题等),因此无法完全消除它,但这是一个很好的开始。
  4. 创建一个睡眠函数,它比目标绝对唤醒时间短 1 毫秒(因为这是 OS 调度程序可以做到的最准确的时间),然后在一个紧密的循环中消耗 CPU,不断检查 RDTSC/CLOCK_REALTIME 值。

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

【讨论】:

  • Linux 现在可以在任务准备就绪时安排任务,而不是依赖于每时每刻运行的调度程序(我相信这是 NO_HZ 选项)。 HPET 方法(阻塞 read() 调用)适用于每 250 微秒进行一次高实时优先级任务的上下文切换,如果 HPET 方法有效的话。 iter 方法通常用于做同样的事情。在某些机器上,它一直有效。在其他人身上,它通常可以工作(我没有被警告消息淹没),但有时我会等待 1MS+。
【解决方案2】:

我在设置一个简单的 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);
    ...
}

您必须以 root 身份运行才能执行此操作。另一种方法是使用 chrt 程序来做同样的事情,但你必须知道你的 RT 进程的 PID。

sudo chrt -f -p 1 <pid>

请参阅我的博文here

【讨论】:

    猜你喜欢
    • 1970-01-01
    • 1970-01-01
    • 2019-09-20
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    相关资源
    最近更新 更多