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

dil*_*erj 10 c precision gettime timing gumstix

我需要对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,推荐使用800Mhz的1Ghz.操作系统是基于内核版本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);
}
Run Code Online (Sandbox Code Playgroud)

(不要担心双重投射,tv_sec在time_t和tv_nsec很长.)

编译:

gcc soExample.c -o runSOExample -lrt
Run Code Online (Sandbox Code Playgroud)

运行:

./runSOExample
Run Code Online (Sandbox Code Playgroud)

如图所示纳米睡眠被注释掉,结果是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:Avg值= 213623,结果上下变化,以30517ns Dev计算机的最小分辨率的倍数:57710-68065 ns,没有明显的趋势.在开发计算机的情况下,我希望分辨率实际上在1 ns级别,并且测量的~150ns确实是两次clock_gettime()调用之间经过的时间.

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

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

dku*_*mar 7

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

评论nanosleep()案例:

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

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

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

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

让我们说,在第一个clock-> read()上,你得到一个计数器值X.

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

当发出第二个clock-> 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倍时,为什么开发计算机30000X的分辨率优于Gumstix?*

对不起,我在这里想念你.怎么快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


the*_*amk 1

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

$ dmesg | grep clocksource

如果你回来

[ 0.560455] Switching to clocksource 32k_counter

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

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