【问题标题】:Why nanosleep() adds a constant delay and how can I avoid that?为什么 nanosleep() 会增加一个恒定的延迟,我该如何避免呢?
【发布时间】:2019-07-04 08:41:17
【问题描述】:

我想用我的 RaspberryPi 3 驱动步进电机。我需要每秒生成大约 10000 个脉冲,这意味着我需要生成间隔约为 100us 的脉冲。我知道 Raspberry 可以生成 PWM,但情况并非如此,因为我需要精确控制脉冲数以及加速/减速,所以我更喜欢显式循环。

虽然忙循环提供了相当精确的计时,但它显然会消耗 100% 的 CPU 时间。同时 nanosleep() 在时间精度和 CPU 负载之间提供了很好的平衡——我可以用大约 10% 的 CPU 负载来驱动电机。

但是。我写了一小段代码来测量 nanosleep() 延迟

    unsigned long iterations = 5000;
    for(int d=10; d<500; d+=5)
    {
        unsigned long accumDelayTime = 0;
        unsigned long accumExpDelayTime = 0;
        for(unsigned long i=0; i<iterations; i++)
        {
            accumExpDelayTime += d;

            unsigned long start = micros();
            struct timespec ts;
            ts.tv_sec = 0;
            ts.tv_nsec = 1000*d;
            clock_nanosleep(CLOCK_REALTIME, 0, &ts, NULL);

            unsigned long end = micros();

            accumDelayTime += end - start;
        }

        printf("%d\t%d\t%d\n", accumExpDelayTime / iterations, accumDelayTime / iterations, (accumDelayTime - accumExpDelayTime) / iterations);
    }

我完全理解通用 linux(尤其是 Raspbian)不是 RTOS,还有其他进程与我的程序共享相同的 CPU 资源 - 我对此非常满意。如果有其他 CPU 消耗进程,我的应用程序会变慢一点,我没问题。

但结果非常令人惊讶。列是:请求延迟、实际延迟、请求延迟和实际延迟之间的差异

10      80      70
15      85      70
20      89      69
25      95      70
30      99      69
35      105     70
40      110     70
45      114     69
50      123     70
55      124     69
60      129     69
65      134     69
70      130     60    <---- here I run CPU-heavy process
75      135     60
80      140     60
85      145     60
90      150     60
95      158     62
100     161     61
105     166     61
110     172     62
115     177     62
120     181     61
125     186     61
130     191     61
135     205     70    <---- here it finished
140     210     70
145     215     70
150     220     70
155     225     70
160     230     70
165     235     70

我尝试了 nanosleep()、clock_nanosleep() 和 usleep() - 结果几乎相同。

我对这些结果有几个疑问: 1) 为什么我想要的睡眠时间和实际的睡眠时间有差异? 2)这种差异非常稳定,不依赖于请求的延迟。有人对此有解释吗? 3) 如果 CPU 负载增加,为什么这种差异会变小(我的预期正好相反)?

【问题讨论】:

    标签: c raspberry-pi real-time


    【解决方案1】:

    您的进程正在用户空间运行。为了既能睡眠又能获得时间,您需要切换到内核空间。这会产生开销。此外,任何睡眠功能只能保证在睡眠定时器到期后的某个时间唤醒。

    归根结底,为了尝试获得所需的精度,您可能需要将代码编写为设备驱动程序。这使您可以直接访问所需的高分辨率计时器。

    【讨论】:

    • 是的,我正在考虑编写一个内核模块,但这需要一些时间来学习如何去做。
    【解决方案2】:

    1) 为什么我希望的睡眠时间和实际的睡眠时间有差异?

    Excerpt from Wikipedia:

    虽然通常可以保证最短时间段,但并不能严格保证线程会在指定时间过后立即或很快运行,甚至根本不运行。这取决于调度程序的判断,并取决于线程优先级和实现细节,例如休眠线程何时再次运行的计时器分辨率。

    这也适用于 Linux。如果您的进程很关键,您可以使用nice 命令提高它的优先级,这会影响进程调度,可能会给您带来更好的结果。

    2) 这种差异非常稳定,不依赖于请求的延迟。有人对此有解释吗?

    除了上面提到的,另一个因素是context switching。摘自维基百科:

    在计算中,上下文切换是存储进程或线程状态的过程,以便以后可以从同一点恢复并恢复执行。这允许多个进程共享一个 CPU,并且是多任务操作系统的基本特征。

    显然,上下文切换需要时间,而这正是您的测试中应该考虑的因素。

    3) 为什么如果 CPU 负载增加,这种差异会变小(我的预期正好相反)?

    few governors in Linux kernel,默认一个是ondemand。内核文档摘录:

    CPUfreq 调控器“ondemand”设置 CPU 频率取决于 当前系统负载。

    这意味着如果 CPU 负载更大,您的系统可能会变得更快。

    对于您的测试,您可能希望将调控器设置为 performanceuserspace 以获得不受动态变化的 CPU 速度影响的结果:

    echo performance > /sys/bus/cpu/devices/cpu0/cpufreq/scaling_governor
    echo 1000000 > /sys/bus/cpu/devices/cpu0/cpufreq/scaling_max_freq
    

    【讨论】:

    • 谢谢,切换到“性能”模式使延迟更加稳定。它不再依赖于 CPU 负载,但仍然是 60us 左右。
    • 添加了 1 和 2 的答案。
    【解决方案3】:

    似乎计时器延迟(即所需睡眠时间与实际时间之间的差异)是由名为 timer_slop 的内核参数引起的。它的默认值为 50 us,这意味着您的计时器将延迟最多 50 us。 timer_slop 旨在通过批处理定时器中断来提高系统性能,从而减少定时器中断的数量。 https://lwn.net/Articles/262493/

    您可以通过文件 /proc/[pid]/timerslack_ns 更改进程的 timer_slop。 例如,echo 10000 &gt; /proc/[pid]/timerslack_ns 命令将 timer_slop 更改为 10 us。

    【讨论】:

      【解决方案4】:

      1) 为什么我希望的睡眠时间和实际的睡眠时间有差异?

      Linux 可能会等到所需的时间过去,然后需要一定的时间才能切换回您的任务并从内核返回。

      3) 为什么如果 CPU 负载增加,这种差异会变小(我的预期正好相反)?

      当 CPU 无事可做时(例如,因为您正在“纳米延迟”),Linux 可能会将 CPU 置于某种睡眠状态以降低功耗;当您的延迟到期时,需要额外的时间来唤醒 CPU。当然,如果 CPU 有其他工作要做,它不会进入睡眠状态(也不会省电),也不需要被唤醒。

      请注意,使用 TIMER_ABSTIME 来确保额外的延迟保持不变(并且不会增加 - 例如,这次延迟 65 微秒,下次延迟 130 微秒,之后延迟 196 微秒,您可能会获得更多乐趣, ...)。

      【讨论】:

        猜你喜欢
        • 2012-04-08
        • 1970-01-01
        • 1970-01-01
        • 2023-01-22
        • 1970-01-01
        • 1970-01-01
        • 2012-07-20
        • 1970-01-01
        • 2018-03-08
        相关资源
        最近更新 更多