11

我需要对 1 us 级别的精确计时来计时 pwm 波占空比的变化。

背景

我正在使用 Gumstix Over Water COM ( https://www.gumstix.com/store/app.php/products/265/ ),它有一个以 499.92 BogoMIPS 运行的单核 ARM Cortex-A8 处理器(Gumstix 页面声称根据 /proc/cpuinfo 到 1Ghz,建议使用 800Mhz)。该操作系统是基于内核版本 2.6.34 的 Angstrom Image 版本的 Linux,它在 Gumstix Water COM 上有库存。

问题

我已经阅读了大量关于 Linux 中精确计时的文章(并且已经尝试了大部分),并且共识似乎是使用 clock_gettime() 并引用 CLOCK_MONOTONIC 是最好的方法。(我本来希望使用 RDTSC 寄存器进行计时,因为我有一个具有最低节能能力的内核,但这不是英特尔处理器。)所以这是奇怪的部分,而 clock_getres() 返回 1,表明分辨率为 1 ns , 实际时序测试表明最小分辨率为 30517ns 或(不可能是巧合)恰好是 32.768KHz 时钟滴答之间的时间。这就是我的意思:

// Stackoverflow example
#include <stdio.h>
#include <time.h>    

#define SEC2NANOSEC 1000000000

int main( int argc, const char* argv[] )
{               
    // //////////////// Min resolution test //////////////////////
    struct timespec resStart, resEnd, ts;
    ts.tv_sec  = 0; // s
    ts.tv_nsec = 1; // ns
    int iters = 100;
    double resTime,sum = 0;    
    int i;
    for (i = 0; i<iters; i++)
    {
        clock_gettime(CLOCK_MONOTONIC, &resStart);      // start timer
        // clock_nanosleep(CLOCK_MONOTONIC, 0, &ts, &ts);
        clock_gettime(CLOCK_MONOTONIC, &resEnd);        // end timer
        resTime = ((double)resEnd.tv_sec*SEC2NANOSEC + (double)resEnd.tv_nsec 
                  - ((double)resStart.tv_sec*SEC2NANOSEC + (double)resStart.tv_nsec);
        sum = sum + resTime;
        printf("resTime = %f\n",resTime);
    }    
    printf("Average = %f\n",sum/(double)iters);
}

(不要为双重转换而烦恼, tv_sec 在 time_t 和 tv_nsec 是很长的。)

编译:

gcc soExample.c -o runSOExample -lrt

运行:

./runSOExample

如图所示,将 nanosleep 注释掉后,结果为 0ns 或 30517ns,大多数为 0ns。这使我相信 CLOCK_MONOTONIC 在 32.768kHz 处更新,并且大部分时间在第二次 clock_gettime() 调用之前时钟尚未更新,并且在结果为 30517ns 的情况下,时钟已在两次调用之间更新。

当我在我的开发计算机(运行在 1.4 GHz 的 AMD FX(tm)-6100 六核处理器)上做同样的事情时,最小延迟是更恒定的 149-151ns,没有零。

因此,让我们将这些结果与 CPU 速度进行比较。对于 Gumstix,30517ns (32.768kHz) 相当于 499.93MHz cpu 的 15298 个周期。对于我的开发计算机,150ns 相当于 1.4Ghz CPU 的 210 个周期。

使用未注释的 clock_nanosleep() 调用,平均结果如下: Gumstix:平均值 = 213623,结果上下变化,以 30517ns 的最小分辨率的倍数 开发计算机:57710-68065 ns,没有明显的趋势。在开发计算机的情况下,我希望分辨率实际上处于 1 ns 水平,而测量到的 ~150ns 确实是两个 clock_gettime() 调用之间经过的时间。

所以,我的问题是:是什么决定了最小分辨率?当处理器的运行速度仅快 2.6 倍时,为什么开发计算机的分辨率比 Gumstix 高 30000 倍?有没有办法改变 CLOCK_MONOTONIC 的更新频率和位置?在内核中?

谢谢!如果您需要更多信息或说明,请询问。

4

2 回答 2

10

据我了解,两种环境(Gumstix 和您的开发计算机)之间的区别可能是它们使用的底层计时器硬件。

评论 nanosleep() 案例:

您正在使用 clock_gettime() 两次。为了让您大致了解这个 clock_gettime() 最终将映射到什么(在内核中):

clock_gettime -->clock_get() -->posix_ktime_get_ts -->ktime_get_ts() -->timekeeping_get_ns() -->clock->read()

clock->read() 基本上读取底层定时器驱动程序提供的计数器的值和相应的硬件。与过去存储的计数器值和当前计数器值以及纳秒转换数学的简单区别将为您提供经过的纳秒,并将更新内核中的计时数据结构。

例如,如果您有一个 HPET 计时器,它为您提供 10 MHz 时钟,则硬件计数器将以 100 ns 的时间间隔更新。

比方说,在第一个 clock->read() 上,你得到一个计数器值 X。

Linux计时数据结构将读取X的这个值,得到与一些旧的存储计数器值相比的差异'D'。做一些计数器差异'D'到纳秒'n'的转换数学,通过'更新数据结构n' 将此新时间值提供给用户空间。

当发出第二个时钟->read() 时,它将再次读取计数器并更新时间。现在,对于 HPET 计时器,此计数器每 100ns 更新一次,因此,您将看到此差异报告给用户空间。

现在,让我们用慢速 32.768 KHz 时钟替换这个 HPET 计时器。现在,clock->read() 的计数器将仅在 30517 ns 秒后更新,因此,如果您在此期间之前第二次调用 clock_gettime(),您将得到 0(大多数情况下)并且在某些情况下,您的第二个函数调用将在计数器增加 1 后进行,即 30517 ns 已经过去。因此,有时 30517 ns 的值。

未注释的 Nanosleep() 案例: 让我们跟踪单调时钟的 clock_nanosleep():

clock_nanosleep() -->nsleep --> common_nsleep() -->hrtimer_nanosleep() -->do_nanosleep()

do_nanosleep() 将简单地将当前任务置于 INTERRUPTIBLE 状态,将等待计时器到期(即 1 ns),然后再次将当前任务设置为 RUNNING 状态。你看,现在涉及到很多因素,主要是当你的内核线程(以及用户空间进程)将再次被调度时。根据您的操作系统,您在进行上下文切换时总是会遇到一些延迟,这就是我们观察到的平均值。

现在你的问题:

是什么决定了最小分辨率?

我认为您的系统的分辨率/精度将取决于所使用的底层计时器硬件(假设您的操作系统能够为用户空间进程提供该精度)。

*为什么当处理器运行速度仅快约 2.6 倍时,开发计算机的分辨率比 Gumstix 高 30000 倍?*

对不起,我在这里想你了。它如何快 30000 倍?对我来说,它看起来快了 200 倍(30714 ns/150 ns ~ 200X ?)。但无论如何,据我所知,CPU 速度可能与定时器分辨率/精度有关,也可能无关。因此,这种假设在某些架构中可能是正确的(当您使用 TSC H/W 时),但在其他架构中可能会失败(使用 HPET、PIT 等)。

有没有办法改变 CLOCK_MONOTONIC 的更新频率和位置?在内核中?

您可以随时查看内核代码以获取详细信息(这就是我查看它的方式)。在 linux 内核代码中,查找这些源文件和文档:

  1. 内核/posix-timers.c
  2. 内核/hrtimer.c
  3. 文档/计时器/hrtimers.txt
于 2013-09-18T09:29:17.227 回答
1

我手头没有 gumstix,但看起来你的时钟源很慢。跑:

$ dmesg | grep clocksource

如果你回来

[ 0.560455] Switching to clocksource 32k_counter

这也许可以解释为什么你的时钟这么慢。

在最近的内核中,有一个/sys/devices/system/clocksource/clocksource0包含两个文件的目录:available_clocksourcecurrent_clocksource. 如果您有此目录,请尝试通过将其名称回显到第二个文件来切换到不同的源。

于 2013-08-30T00:26:33.567 回答