题
我需要一个在linux下分辨率为1ms的定时器。它用于增加计时器值,该计时器值又用于查看是否应触发各种事件。由于 glibc 的要求,POSIXtimerfd_create 不是一个选项。我尝试了timer_create和timer_settimer,但我从它们那里得到的最好的结果是10ms分辨率,较小的值似乎默认为10ms分辨率。根据联机帮助页,Getittimer 和 setitimer 的分辨率为 10 毫秒。
我目前能想到的执行此计时器的唯一方法是在主循环中使用clock_gettime 和 CLOCK_MONOTONIC 来测试是否已过去毫秒,如果已过去,则增加计数器(然后检查是否应触发各种事件)。
有没有比在主循环中不断查询更好的方法来做到这一点?对此建议的解决方案是什么?
我使用的语言是普通的旧c
更新
我使用的是 2.6.26 内核。我知道你可以让它以 1kHz 中断,然后 POSIXtimer_* 函数可以编程为最多 1ms,但这似乎不可靠,我不想使用它,因为它可能需要一个新的内核系统。一些库存内核似乎仍然配置了 100Hz。我需要检测到这一点。该应用程序可能在我的系统以外的其他设备上运行:)
我无法睡眠 1 毫秒,因为可能有网络事件我必须做出反应。
我是如何解决的因为它并不那么重要,所以我简单地声明全局计时器的分辨率为 100ms。所有使用自己的计时器的事件都必须设置至少 100 毫秒的计时器到期时间。我或多或少想知道是否有更好的方法,因此提出了这个问题。
为什么我接受答案我认为 freespace 的答案最好地描述了为什么没有实时 Linux 系统就不可能实现。
解决方案
主循环中的轮询也不是一个答案 - 您的进程可能没有太多的CPU时间,因此在代码运行之前将超过10毫秒,这使得它没有实际意义。
10ms是大多数非实时操作系统(RTOS)的标准计时器分辨率)。但是在非RTOS中没有实际意义 - 调度程序和调度程序的行为将极大地影响您响应计时器到期的速度。例如,即使您有一个10ms的子分辨率计时器,如果您的代码没有运行,也无法响应计时器到期。由于无法预测代码何时运行,因此无法准确响应计时器到期。
当然有实时Linux内核,请参阅 http://www.linuxdevices.com/articles /AT8073314981.html 列表。 RTOS提供了一些工具,您可以通过这些工具获得有关何时运行代码的软或硬保证。这是可靠和准确地响应定时器到期等的唯一方法。
其他提示
要获得1ms的分辨率计时器,请执行 libevent 所做的事。
将您的计时器组织到 min-heap 中,也就是说,堆顶部是计时器最早的到期(绝对)时间(rb-tree也可以工作,但有更多的开销)。在主事件循环中调用 select()
或 epoll()
之前,计算最早计时器到现在的到期时间之间的增量(以毫秒为单位)。使用此delta作为 select()
的超时。 select()
和 epoll()
超时的分辨率为1ms。
我有一个定时器分辨率测试,它使用上面解释的机制(但不是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。
我不确定它是最好的解决方案,但您可能会考虑编写一个使用内核高分辨率计时器来执行计时的小型内核模块。基本上,您将创建一个设备文件,其读取时间间隔仅为1毫秒。
通过ztdummy模块在Asterisk PBX中使用了这种方法的一个示例。如果你谷歌搜索ztdummy,你可以找到执行此操作的代码。
我认为即使在主循环中进行常量查询,您也无法使用标准Linux实现1 ms精度,因为内核无法确保您的应用程序始终获得CPU。例如,由于先发制人的多任务处理,您可以在几十毫秒内进入睡眠状态,而且您无能为力。
您可能需要查看 Real-Time Linux 。
如果您的目标是 x86 平台,您应该检查 HPET 计时器。这是高精度的硬件定时器。它必须受到您的主板的支持(现在所有主板都支持它)并且您的内核也应该包含它的驱动程序。我用过几次,没有任何问题,并且能够达到比 1ms 更好的分辨率。
这是一些文档和示例:
我似乎记得使用基于gettimeofday / usleep的轮询获得正常结果 - 我不需要一秒钟或任何时间1000个计时器,但我需要很好的准确性与我需要的刻度的时间 - 我的应用程序是一个MIDI鼓机控制器,我似乎记得得到亚毫秒精度,如果你不希望它听起来像一个非常糟糕的鼓手(特别是计算MIDI的内置延迟),你需要一个鼓机 - iirc (这是2005年所以我的记忆有点模糊)我在睡眠时间内达到200微秒的目标时间。
但是,我在系统上没有运行太多其他东西。如果你有一个受控制的环境,你可能会得到这样的解决方案。如果系统上有更多内容(观看cron激活更新等),那么事情可能会崩溃。
您是否在Linux 2.4内核上运行?
来自VMware知识库文章#1420( http://kb.vmware.com/kb/1420 )。
Linux客户操作系统保持不变 计时定时器中断的时间。 未修补的2.4及更早版本的内核 将虚拟系统计时器编程为 请求时钟中断为100Hz(100 每秒中断)。 2.6内核, 另一方面,请求中断 在1000Hz - 十倍的频率。一些 2.4分发供应商修改的内核也包含2.6特性 请求1000Hz中断,或某些 案件,其他费率的中断等 为512Hz。
首先,获取内核源代码并使用调整后的 HZ 参数进行编译。
- 如果
HZ=1000
, ,定时器每秒中断1000次。使用就可以了HZ=1000
对于 i386 机器。 - 在嵌入式计算机上,HZ 可能限制为 100 或 200。
为了良好的操作,PREEMPT_KERNEL 选项应该打开。有些内核不能正确支持此选项。您可以通过搜索来检查它们。
最近的内核,即2.6.35.10,支持NO_Hz选项,它打开动态滴答。这意味着在闲置时不会有计时器的滴答声,但是在指定的时刻将生成计时器tick。
内核有 RT 补丁,但硬件支持非常有限。
通常,RTAI是解决问题的所有杀手级解决方案,但是其硬件支持非常有限。但是,诸如EMC2之类的良好CNC控制器使用RTAI进行时钟,也许是5000 Hz,但是安装它可能很难。
如果可以的话,您可以添加硬件来生成脉冲。那将制造一个可以适应任何操作系统版本的系统。
你能在循环中至少使用nanosleep睡眠1ms吗?或者这是一个glibc的事情?
更新:没关系,我从手册页中看到“它可能需要比指定的时间长10毫秒才能再次运行”
对于简单的实时应用程序,您不需要RTOS。所有现代处理器都有通用定时器。获取您正在处理的目标CPU的数据表。查看内核源代码,在arch目录下,您将找到处理器特定的源如何处理这些定时器。
您可以采取两种方法:
1)您的应用程序仅运行您的状态机,而不是其他任何东西。 Linux只是你的“引导装载程序”。创建一个安装字符设备的内核对象。插入内核后,将GP定时器设置为连续运行。你知道它正在运行的频率。现在,在内核中,明确禁用您的看门狗。现在禁用中断(硬件和软件)在单CPU Linux内核上,调用spin_lock()将完成此任务(永远不要放弃它。)CPU是你的。繁忙的循环,检查GPT的值,直到所需的滴答声已经过去,当它们有时,为下一个超时设置一个值并输入你的处理循环。只需确保代码的突发时间小于1毫秒
2)第二种选择。这假设您正在运行抢占式Linux内核。在正在运行的操作系统旁边设置未使用的GPT。现在,在1ms超时发生之前(例如50-75 uSec)设置一个中断来触发一些可配置的余量。当中断触发时,你将立即禁用中断并旋转等待1ms窗口发生,然后进入你的状态机,然后进入在等待OUT上启用中断。这说明您正在与内核中的其他东西合作以禁用中断。这假设没有其他内核活动可以长时间锁定中断(超过100us)。现在,您可以测量点火事件的准确性,并使窗口变大,直到满足您的需要。
如果您正在尝试了解RTOS的工作方式......或者您是否尝试解决具有多个实时责任的控制问题......那么请使用RTOS。
使用“/ dev / rtc0”怎么样? (或“/ dev / rtc”)设备及其相关的ioctl()接口?我认为它提供了一个准确的计时器计数器不能将速率设置为1 ms,而是设置为接近值或1 / 1024sec(1024Hz),或设置为更高的频率,如8192Hz。