19

我需要一个在 linux 下分辨率为 1ms 的计时器。它用于增加一个计时器值,该值又用于查看是否应该触发各种事件。由于 glibc 的要求,POSIX timerfd_create 不是一个选项。我尝试了 timer_create 和 timer_settimer,但我从中得到的最好的结果是 10 毫秒的分辨率,较小的值似乎默认为 10 毫秒的分辨率。根据手册页,Getittimer 和 setitimer 的分辨率为 10 毫秒。

我目前能想到的执行此计时器的唯一方法是在我的主循环中使用 clock_gettime 和 CLOCK_MONOTONIC 来测试是否经过了毫秒,如果是则增加计数器(然后检查各种事件是否应该触发)。

有没有比在主循环中不断查询更好的方法呢?推荐的解决方案是什么?

我使用的语言是普通的 c

更新
我使用的是 2.6.26 内核。我知道你可以让它以 1kHz 的频率中断,然后可以将 POSIX timer_* 函数编程为最高 1ms,但这似乎不可靠,我不想使用它,因为它可能需要一些新内核系统。一些股票内核似乎仍然配置了 100Hz。我需要检测到这一点。该应用程序可能在我的系统之外的其他东西上运行:)

我无法入睡 1 毫秒,因为我可能需要对网络事件做出反应。

我是如何解决 的 因为它不是那么重要,所以我简单地声明全局计时器的分辨率为 100 毫秒。所有使用自己的计时器的事件都必须设置至少 100 毫秒的计时器到期时间。我或多或少想知道是否会有更好的方法,因此提出了这个问题。

为什么我接受这个答案 我认为来自 freespace 的答案最好地描述了为什么没有实时 Linux 系统是不可能的。

4

12 回答 12

17

在主循环中轮询也不是一个答案——你的进程可能不会获得太多的 CPU 时间,因此在你的代码运行之前会经过超过 10 毫秒,使它变得毫无意义。

10ms 大约是大多数非实时操作系统(RTOS) 的标准计时器分辨率。但它在非 RTOS 中没有实际意义——调度程序和调度程序的行为将极大地影响您对计时器到期的响应速度。例如,即使您有一个低于 10 毫秒的分辨率计时器,如果您的代码没有运行,您也无法响应计时器到期。由于您无法预测代码何时运行,因此您无法准确响应计时器到期。

当然还有实时 linux 内核,请参阅http://www.linuxdevices.com/articles/AT8073314981.html获取列表。RTOS 提供了一些工具,您可以通过这些工具获得有关代码何时运行的软或硬保证。这是可靠和准确地响应计时器到期等的唯一方法。

于 2008-10-27T14:52:02.473 回答
17

要获得 1ms 分辨率计时器,请执行libevent所做的操作。

将您的计时器组织成一个min-heap,也就是说,堆的顶部是具有最早到期(绝对)时间的计时器(rb-tree 也可以工作,但开销更大)。在调用之前select()epoll()在您的主事件循环中计算最早计时器的到期时间与现在之间的增量(以毫秒为单位)。将此增量用作select(). 超时有 1ms 的分辨率select()epoll()

我有一个计时器分辨率测试,它使用上面解释的机制(但不是 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。

于 2011-01-18T12:33:19.383 回答
6

我不确定这是不是最好的解决方案,但您可能会考虑编写一个使用内核高分辨率计时器进行计时的小型内核模块。基本上,您将创建一个设备文件,其读取只会以 1 毫秒的间隔返回。

Asterisk PBX 中通过 ztdummy 模块使用了这种方法的一个示例。如果你用谷歌搜索 ztdummy,你可以找到执行此操作的代码。

于 2008-10-27T14:53:05.140 回答
5

我认为即使在主循环中不断查询,使用标准 Linux 实现 1 毫秒的精度也会有困难,因为内核不能确保您的应用程序始终获得 CPU。例如,由于抢先式多任务处理,您可能会陷入数十毫秒的睡眠,而您对此无能为力。

您可能想研究Real-Time Linux

于 2008-10-27T14:49:47.597 回答
3

如果您的目标是 x86 平台,您应该检查 HPET 计时器。这是一个高精度的硬件定时器。你的主板必须支持它(现在它们都支持它),你的内核也应该包含它的驱动程序。我已经使用它几次,没有任何问题,并且能够实现比 1ms 更好的分辨率。

以下是一些文档和示例:

于 2011-01-18T12:02:39.753 回答
2

我似乎记得使用基于 gettimeofday/usleep 的轮询获得了不错的结果——我不需要每秒 1000 个计时器或任何东西,但我需要在我确实需要的滴答声的计时上保持良好的准确性——我的应用程序是一个 MIDI 鼓机控制器,我似乎记得获得亚毫秒精度,如果你不希望它听起来像一个非常糟糕的鼓手(特别是计算 MIDI 的内置延迟),你需要一个鼓机 - iirc(它是2005 所以我的记忆有点模糊)我在目标时间的 200 微秒内进入睡眠状态。

但是,我没有在系统上运行太多其他东西。如果您有一个受控的环境,您可能能够摆脱这样的解决方案。如果系统上发生了更多事情(观看 cron 启动更新数据库等),那么事情可能会分崩离析。

于 2009-01-20T05:26:10.693 回答
1

您是否在 Linux 2.4 内核上运行?

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

Linux 客户操作系统通过计数定时器中断来保持时间。未修补的 2.4 和更早的内核对虚拟系统计时器进行编程,以请求 100Hz 的时钟中断(每秒 100 个中断)。另一方面,2.6 内核以 1000Hz 的频率请求中断——频率是后者的十倍。分发供应商修改为包含 2.6 功能的一些 2.4 内核也请求 1000Hz 中断,或者在某些情况下,以其他速率中断,例如 512Hz。

于 2008-10-27T14:50:18.873 回答
1

linux内核有ktimer补丁:

http://lwn.net/Articles/167897/

http://www.kernel.org/pub/linux/kernel/projects/rt/

高温高压

于 2008-10-27T14:56:56.100 回答
1

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

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

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

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

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

通常,RTAI 是解决您问题的万能解决方案,但它的硬件支持非常有限。然而,好的 CNC 控制器,如 emc2,使用 RTAI 作为其时钟,可能是 5000 Hz,但安装它可能很困难。

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

于 2011-01-18T11:24:29.930 回答
1

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

您可以采用两种方法:

1)你的应用程序只运行你的状态机,没有别的。Linux 只是您的“引导加载程序”。创建一个安装字符设备的内核对象。插入内核后,将您的 GP 计时器设置为连续运行。你知道它的工作频率。现在,在内核中,明确禁用你的看门狗。现在禁用中断(硬件和软件) 在单 CPU Linux 内核上,调用 spin_lock() 将完成此操作(永远不要放弃它。)CPU 是你的。繁忙的循环,检查 GPT 的值,直到通过了所需的刻度数,当它们通过时,为下一个超时设置一个值并进入您的处理循环。只需确保您的代码的突发时间低于 1ms

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

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

于 2013-01-11T03:00:42.990 回答
0

您至少可以在循环中使用 nanosleep 睡眠 1ms 吗?还是那是 glibc 的事情?

更新:没关系,我从手册页中看到“在进程再次可运行之前,它可能需要比指定的时间长 10 毫秒”

于 2008-10-27T14:48:12.887 回答
0

使用“/dev/rtc0”(或“/dev/rtc”)设备及其相关的ioctl() 接口怎么样?我认为它提供了一个准确的计时器计数器。不能将速率设置为 1 毫秒,而是设置为接近值或 1/1024 秒 (1024Hz) 或更高的频率,如 8192Hz。

于 2014-05-31T11:18:32.710 回答