精确的L​​inux时序 - 什么决定了clock_gettime()的分辨率?

时间:2013-08-20 19:10:54

标签: c precision timing gettime gumstix

我需要对1 us水平进行精确计时,以便改变pwm波的占空比。

背景

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

问题

我已经对Linux中的精确计时做了大量的阅读(并且已经尝试了大部分计划)并且共识似乎是使用clock_gettime()和引用CLOCK_MONOTONIC是最好的方法。 (我本来希望使用RDTSC寄存器进行定时,因为我有一个具有最小省电能力的内核但这不是Intel处理器。)所以这里是奇数部分,而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);
}

(不要担心双重投射,时间t中的tv_sec和tv_nsec很长。)

编译:

gcc soExample.c -o runSOExample -lrt

使用以下命令运行:

./runSOExample

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

当我在我的开发计算机上执行相同操作(AMD FX(tm)-6100六核处理器,运行频率为1.4 GHz)时,最小延迟为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倍时,为什么开发计算机30000X的分辨率优于Gumstix? 有没有办法改变CLOCK_MONOTONIC的更新频率和位置?在内核中?

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

2 个答案:

答案 0 :(得分:7)

据我了解,两种环境(Gumstix和您的开发计算机)之间的差异可能是他们正在使用的基础计时器。

评论nanosleep()案例:

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

clock_gettime - &gt; clock_get() - &gt; posix_ktime_get_ts - &gt; ktime_get_ts() - &gt; timekeeping_get_ns() - &gt; clock-&gt; read()

clock-&gt; read()基本上读取由底层计时器驱动程序提供的计数器的值和相应的h / w。与过去和当前计数器值的计数器存储值的简单差异,然后纳秒转换数学将产生经过的纳秒,并将更新内核中的计时数据结构。

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

让我们说,在第一个clock-&gt; read()上,你得到一个X的计数器值。

Linux时间保持数据结构将读取X的这个值,与一些旧的存储计数器值相比得到“D”的差异。做一些反差“D”到纳秒'n'转换数学,更新数据 - 结构'n' 将此新时间值产生到用户空间。

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

现在,让我们用一个32.768 KHz的慢时钟代替这个HPET定时器。现在,clock-&gt; read()的计数器只会在30517 ns秒之后更新,所以,如果你第二次调用clock_gettime()就在这段时间之前,你将得到0(这是大多数情况)并且在某些情况下例如,在计数器递增1之后,将放置第二个函数调用,即已经过了30517 ns。因此,有时值为30517 ns。

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

clock_nanosleep() - &gt; nsleep - &gt; common_nsleep() - &gt; hrtimer_nanosleep() - &gt; do_nanosleep()

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

现在你的问题:

什么决定了最低分辨率?

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

*当处理器运行速度提高约2.6倍时,为什么开发计算机30000X的分辨率优于Gumstix? * < / EM>

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

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

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

  1. kernel / posix-timers.c
  2. 内核/ hrtimer.c
  3. 文档/定时器/ hrtimers.txt

答案 1 :(得分:1)

我手边没有gumstix,但看起来你的clockource很慢。 跑:

$ dmesg | grep clocksource

如果你回来了

[ 0.560455] Switching to clocksource 32k_counter

这可以解释为什么你的时钟太慢了。

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