【问题标题】:1ms resolution timer under linux recommended waylinux下1ms分辨率定时器推荐方式
【发布时间】:2008-10-27 14:26:22
【问题描述】:

我需要一个在 linux 下分辨率为 1 毫秒的计时器。它用于增加一个计时器值,该值又用于查看是否应该触发各种事件。由于 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 系统是不可能的。

【问题讨论】:

  • 使用glibc有什么问题?
  • timerfd_create() 与 POSIX 无关,它是 Linux 特有的函数。

标签: c linux timer


【解决方案1】:

在主循环中轮询也不是一个答案 - 您的进程可能不会获得太多 CPU 时间,因此在您的代码开始运行之前将经过 10 毫秒以上,从而使其没有实际意义。

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

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

【讨论】:

  • 即使在旧的非 RT 内核(如 CentOS 5.3 上的 2.6.18)上,您也可以获得 1 毫秒分辨率的计时器。
  • 解决方案只是保证实际安排的一小部分。
【解决方案2】:

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

将你的定时器组织成min-heap,也就是说,堆的顶部是具有最早到期(绝对)时间的定时器(rb-tree 也可以工作,但开销更大)。在主事件循环中调用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 代码在保密协议下,所以我不能显示它。而且我不喜欢你贬义的“所谓”。
  • @MaximEgorushkin “实际”在这里有点主观,真的不能称为“实际”。甚至原子钟也可以在某种程度上是“真实的”。我只想说“实际”不是“绝对”,(当我阅读它时,我希望感受到)。我询问了测量工具或时间源(例如是clock_gettime()?外部硬件定时器?还是什么?)
  • @xakepp35 rdtsc 转换为纳秒。
【解决方案3】:

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

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

【讨论】:

    【解决方案4】:

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

    您可能想查看Real-Time Linux

    【讨论】:

      【解决方案5】:

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

      以下是一些文档和示例:

      【讨论】:

        【解决方案6】:

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

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

        【讨论】:

          【解决方案7】:

          您是否在 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。

          【讨论】:

            【解决方案8】:
            【解决方案9】:

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

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

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

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

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

            通常,RTAI 是解决您问题的万能解决方案,但它的 硬件支持非常有限。但是,好的 CNC 控制器,例如 emc2,使用 RTAI 作为时钟,可能是 5000 Hz,但也可以是 努力安装它。

            如果可以,您可以添加硬件来生成脉冲。那会让 一个可以适应任何操作系统版本的系统。

            【讨论】:

              【解决方案10】:

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

              您可以采用两种方法:

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

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

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

              【讨论】:

                【解决方案11】:

                您至少可以在循环中使用 nanosleep 来休眠 1 毫秒吗?还是那是 glibc 的事情?

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

                【讨论】:

                  【解决方案12】:

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

                  【讨论】:

                    猜你喜欢
                    • 1970-01-01
                    • 2020-03-09
                    • 1970-01-01
                    • 1970-01-01
                    • 2013-10-26
                    • 1970-01-01
                    • 1970-01-01
                    • 1970-01-01
                    • 1970-01-01
                    相关资源
                    最近更新 更多