Linux下1毫秒分辨率计时器的推荐方法

19
我需要在Linux下使用1ms分辨率的计时器,用于增加计时器值,以便检查是否应触发各种事件。由于glibc要求,POSIX timerfd_create不是可选项。我尝试了timer_create和timer_settimer,但它们提供的最好分辨率为10ms,较小的值似乎默认为10ms分辨率。根据manpage,getittimer和setitimer具有10毫秒的分辨率。目前我能想到的唯一方法是在我的主循环中使用带CLOCK_MONOTONIC的clock_gettime进行测试,并测试是否已经过去了1毫秒,如果是,则增加计数器(然后检查各种事件是否应该触发)。是否有比在主循环中不断查询更好的方法?这个问题的推荐解决方案是什么?我正在使用普通的C语言。 更新:我正在使用2.6.26内核。我知道你可以使其中断1kHz,并且POSIX timer_*函数可以编程高达1ms,但这似乎并不可靠,而且我不想使用它,因为它可能需要在某些系统上使用新内核。某些股票内核似乎仍配置为100Hz。 我不能睡眠1ms,因为可能会有我必须反应的网络事件。 如何解决:由于这并不那么重要,因此我简单地宣布全局计时器具有100ms的分辨率。使用自己的计时器的所有事件必须设置至少100毫秒的计时器过期时间。我更多地是想知道是否有更好的方法,因此才提出这个问题。 为什么我接受了答案:我认为freespace的答案最好地描述了为什么在没有实时Linux系统的情况下不太可能做到这一点。

使用glibc有什么问题? - Cristian Ciupitu
timerfd_create()与POSIX无关,它是Linux特定的函数。 - Maxim Egorushkin
12个回答

17
为了实现 1 毫秒的分辨率定时器,可以像 libevent 那样操作。将定时器组织成一个最小堆,即堆顶上的定时器是最早到期(绝对)时间的定时器(红黑树也可以,但开销更大)。在主事件循环中调用 select()epoll() 前,计算最早定时器到期时间与当前时间之间的毫秒差。使用此差作为 select() 的超时时间。select()epoll() 的超时精度为 1 毫秒。
我有一个定时器分辨率测试程序,它使用上述机制进行测量,但不使用 libevent。该测试程序可测量 1ms、5ms 和 10ms 定时器的实际到期时间与期望到期时间之间的差异。
1000 deviation samples of  1msec timer: min=  -246115nsec max=  1143471nsec median=   -70775nsec avg=      901nsec stddev=    45570nsec
1000 deviation samples of  5msec timer: min=  -265280nsec max=   256260nsec median=  -252363nsec avg=     -195nsec stddev=    30933nsec
1000 deviation samples of 10msec timer: min=  -273119nsec max=   274045nsec median=   103471nsec avg=     -179nsec stddev=    31228nsec
1000 deviation samples of  1msec timer: min=  -144930nsec max=  1052379nsec median=  -109322nsec avg=     1000nsec stddev=    43545nsec
1000 deviation samples of  5msec timer: min= -1229446nsec max=  1230399nsec median=  1222761nsec avg=      724nsec stddev=   254466nsec
1000 deviation samples of 10msec timer: min= -1227580nsec max=  1227734nsec median=    47328nsec avg=      745nsec stddev=   173834nsec
1000 deviation samples of  1msec timer: min=  -222672nsec max=   228907nsec median=    63635nsec avg=       22nsec stddev=    29410nsec
1000 deviation samples of  5msec timer: min= -1302808nsec max=  1270006nsec median=  1251949nsec avg=     -222nsec stddev=   345944nsec
1000 deviation samples of 10msec timer: min= -1297724nsec max=  1298269nsec median=  1254351nsec avg=     -225nsec stddev=   374717nsec

测试在Fedora 13内核2.6.34上作为实时进程运行,最佳实现的1ms定时器精度为avg=22nsec stddev=29410nsec。


“我进行了计时器分辨率测试。”- 请向我们展示代码。我非常感兴趣,想知道这个所谓的“实际到期”是什么意思。 - xakepp35
@xakepp35 代码受NDA保护,所以我不能展示它。而且我不喜欢你贬低的“所谓”。 - Maxim Egorushkin
1
@MaximEgorushkin 在这里,“实际的”有点主观,它真的不能被称为“实际的”。即使是原子钟也只能在某种程度上被称为“实际的”。我只是想说,“实际的”并不是“绝对的”(当我读到它时,我希望感受到的)。我问的是测量工具或时间源(例如是clock_gettime()吗?外部硬件计时器?或者是什么?)。 - xakepp35
1
@xakepp35 rdtsc 转换为纳秒。 - Maxim Egorushkin

17
在主循环中轮询也不是一个答案 - 你的进程可能得不到足够的CPU时间,所以在你的代码运行之前会经过超过10ms的时间,这使它无关紧要。
对于大多数非实时操作系统(RTOS),10ms是标准计时器分辨率。但在非RTOS中,调度程序和分派程序的行为将极大地影响您对计时器到期的快速响应能力。例如,即使假设您有一个小于10ms的分辨率计时器,如果您的代码没有运行,您就无法响应计时器到期。由于您无法预测代码何时运行,因此无法准确响应计时器到期。
当然有实时Linux内核,见http://www.linuxdevices.com/articles/AT8073314981.html列表。RTOS提供了一些设施,可以获得关于代码何时运行的软性或硬性保证。这是可靠且准确地响应计时器到期等的唯一方法。

即使在旧的非实时内核上,例如CentOS 5.3上的2.6.18,您也可以获得1ms分辨率计时器。 - Maxim Egorushkin
1
然而,解决方案只是确保实际安排的一小部分保证。 - user562374

6
我不确定这是否是最佳解决方案,但您可以考虑编写一个小内核模块,使用内核高分辨率定时器进行计时。 基本上,您将为其创建一个设备文件,在此文件上读取仅在1ms间隔返回。
这种方法的示例用于Asterisk PBX,通过ztdummy模块。 如果您搜索ztdummy,您可以找到执行此操作的代码。

5

我认为即使在主循环中不断查询,使用标准的Linux也很难实现1毫秒的精度,因为内核不能保证您的应用程序一直获得CPU。例如,由于抢占式多任务处理,您可能会被休眠数十毫秒,而您无能为力。

您可能需要了解实时Linux


3
如果您的目标平台是x86,您应该检查HPET计时器。这是具有大精度的硬件计时器。它必须得到您的主板支持(现在所有主板都支持它),并且您的内核也应包含其驱动程序。我已经使用过几次,没有任何问题,并且能够实现比1ms更好的分辨率。
以下是一些文档和示例:

2

我记得使用gettimeofday/usleep进行轮询时,结果还不错。我不需要每秒1000个定时器之类的东西,但我需要在需要的节拍的时间上有很好的准确性。我的应用程序是一个MIDI鼓机控制器,我记得获得了亚毫秒级别的准确性,如果你不想让它听起来像一个非常糟糕的鼓手(特别是考虑到MIDI内置延迟),那么你就需要这样的准确性。根据我的回忆(它是2005年,所以我的记忆有点模糊),我使用usleep可以接近目标时间200微秒。

然而,我没有在系统上运行太多其他东西。如果您有一个受控环境,您可能能够使用这样的解决方案。如果系统上有更多的事情发生(例如cron启动updatedb等),那么事情可能会崩溃。


1

首先,获取内核源代码并使用调整后的HZ参数进行编译。

  • 如果HZ=1000,则定时器每秒中断1000次。对于i386机器,使用HZ=1000是可以的。
  • 在嵌入式设备上,HZ可能被限制为100或200。

为了良好的操作,PREEMPT_KERNEL选项应该打开。有些内核不支持这个选项。您可以通过搜索来查找它们。

最近的内核,例如2.6.35.10,支持NO_HZ选项,它打开了动态滴答声。这意味着在空闲时不会有计时器滴答声,但在指定的时刻将生成计时器滴答声。

有一个RT补丁可用于内核,但硬件支持非常有限。

通常,RTAI是解决问题的全杀手解决方案,但其硬件支持非常有限。然而,像emc2这样的良好CNC控制器使用RTAI进行时钟同步,可能是5000 Hz,但安装它可能需要一些努力。

如果可以的话,您可以添加硬件来生成脉冲。这将使系统适应任何操作系统版本。


1

您正在运行Linux 2.4内核吗?

来自VMware KB文章#1420(http://kb.vmware.com/kb/1420)。

Linux客户操作系统通过计算定时器中断来保持时间。未打补丁的2.4及更早版本的内核将虚拟系统定时器编程为请求100Hz(每秒100个中断)的时钟中断。另一方面,2.6内核请求以1000Hz的频率进行中断-十倍于此。一些由发行商修改以包含2.6功能的2.4内核也会请求1000Hz中断,或者在某些情况下,请求其他速率的中断,例如512Hz。



1

对于简单的实时应用程序,您不需要RTOS。所有现代处理器都有通用定时器。获取您正在使用的目标CPU的数据表。在内核源代码中,在arch目录下,您将找到特定于处理器的源代码,以了解如何处理这些定时器。

您可以采取两种方法:

1)您的应用程序仅运行您的状态机,没有其他任何操作系统。Linux只是您的“引导加载程序”。创建一个内核对象,安装一个字符设备。插入内核时,设置您的GP计时器连续运行。您知道它的工作频率。现在,在内核中显式禁用您的看门狗。现在禁用中断(硬件和软件)。在单CPU Linux内核上,调用spin_lock()将实现此目的(永远不要释放它)。 CPU是您的。忙碌循环,检查GPT的值,直到所需的滴答数已经过去,当它们过去时,为下一个超时设置一个值并进入处理循环。只需确保您的代码的突发时间小于1ms即可。

2) 第二个选项。这假设您正在运行一个抢占式的Linux内核。在运行中的操作系统旁边设置一个未使用的GPT。现在,设置一个中断,在您的1ms超时发生之前触发一些可配置的余量(例如50-75微秒)。当中断触发时,您将立即禁用中断并旋转等待1ms窗口出现,然后进入您的状态机,并随后在等待OUT上启用中断。这考虑到您与内核中的其他事物合作,这些事物会禁用中断。这假定没有其他内核活动会长时间锁定中断(超过100us)。现在,您可以测量您的触发事件的准确性,并使窗口变大,直到满足您的需求。

如果您想了解RTOS的工作原理...或者如果您正在尝试解决具有多个实时责任的控制问题...那么请使用RTOS。


网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接