2019-12-17 Linux的时钟+纳秒定时器

例子

/* 例子 可能不对*/
#include  
#include 

void *my_thrd_routine(void *arg)
{
    (void) arg;
    
    pthread_t self_thrd_id = pthread_self();
    clockid_t self_clk_id;
    int err = 0;
    
    err =  pthread_getcpuclockid(self_thrd_id, &self_clk_id);
    
    struct timespec tp1;
    struct timespec tp2;
    
    clock_gettime(self_clk_id, &tp1);
    for (int i=1; i<=100; i++) {
        static double floatnum = 1.0;
        floatnum *= (double) i;
    }
    clock_gettime(self_clk_id, &tp2);
    
    return NULL;
}

时钟或者钟表(clock)是一种计时工具,每个人都至少有一块,可能在你的手机里,也可能佩戴在你的手腕上。如果Linux也是一个普通人的话,那么她的手腕上应该有十几块手表,包括:

  • CLOCK_REALTIME;
  • CLOCK_MONOTONIC
  • CLOCK_PROCESS_CPUTIME_ID
  • CLOCK_THREAD_CPUTIME_ID
  • CLOCK_MONOTONIC_RAW
  • CLOCK_REALTIME_COARSE
  • CLOCK_MONOTONIC_COARSE
  • CLOCK_BOOTTIME
  • CLOCK_REALTIME_ALARM
  • CLOCK_BOOTTIME_ALARM
  • CLOCK_TAI
    本文主要就是介绍Linux内核中的形形色色的“钟表”。

MONOTONIC类型的时钟相对比较简单,如果你设定事件A之后5秒进行动作B,那么用MONOTONIC类型的时钟是一个比较好的选择,如果使用REALTIME的时钟,当用户在事件A和动作B之间插入时间设定的操作,那么你设定事件A之后5秒进行动作B将不能触发。此外,用户需要了解系统启动时间,这个需求需要使用MONOTONIC类型的时钟的时钟。需要指出的是MONOTONIC类型的时钟不是绝对时间的概念,多半是计算两个采样点之间的时间,并且保证采样点之间时间的单调性。

MONOTONIC_RAW是一个底层工具,一般而言程序员不会操作它,使用MONOTONIC类型的时钟就够用了,当然,一些高级的应用场合,例如你想使用另外的方法(不是NTP)来调整时间,那么就可以使用MONOTONIC_RAW了。

有些应用场景使用real time的时钟(墙上时钟)是不合适的,例如当我们进行系统中各个应用程序的性能分析和统计的时候。正因为如此,kernel提供了基于进程或者线程的系统时钟,也就是CLOCK_PROCESS_CPUTIME_IDCLOCK_THREAD_CPUTIME_ID了。当我们打算使用基于进程或者线程的系统时钟的时候,需要首先获取clock id:

#include 

int clock_getcpuclockid(pid_t pid, clockid_t *clock_id);

如果是线程的话,需要调用pthread_getcpuclockid()接口函数:

#include  
#include 

int pthread_getcpuclockid(pthread_t thread, clockid_t *clock_id);

pthread_t pthread_self(void);

虽然这组函数接口的精度可以达到ns级别,但是实际的系统可以达到什么样的精度是实现相关的,因此,clock_getres()用来获取系统时钟的精度。

纳秒级别的时间函数:clock_gettime和clock_settime

#include 

int clock_getres(clockid_t clk_id, struct timespec *res);

int clock_gettime(clockid_t clk_id, struct timespec *tp);

int clock_settime(clockid_t clk_id, const struct timespec *tp);

正因为传统的Interval timer函数的不足之处,POSIX标准定义了更高级,更灵活的timer函数,我们称之POSIX (interval)Timer。

创建timer定时器对象

#include 
#include 

int timer_create(clockid_t clockid, struct sigevent *sevp,   timer_t *timerid);

在这个接口函数中,clock_id_t clockid相信大家都很熟悉了, timer_t *timerid一看就是返回的timer ID的句柄,就像open函数返回的文件描述符一样。因此,要理解这个接口函数重点是了解struct sigevent *sig_event_ptr这个数据结构


转发请注明出处。蜗窝科技:

  • http://www.wowotech.net/timer_subsystem/timer_subsystem_userspace.html

转发请注明出处。蜗窝科技:

  • http://www.wowotech.net/timer_subsystem/timer_subsystem_userspace.html

一、前言

从应用程序的角度看,内核需要提供的和时间相关的服务有三种:

  • 1、和系统时间相关的服务。例如,在向数据库写入一条记录的时候,需要记录操作时间(何年何月何日何时)。
  • 2、让进程睡眠一段时间
  • 3、和timer相关的服务。在一段指定的时间过去后,kernel要alert用户进程

本文主要描述和时间子系统相关的用户空间接口函数知识。

二、和系统时间相关的服务

1、秒级别的时间函数:time和stime

time和stime函数的定义如下:

#include 

time_t time(time_t *t);
int stime(time_t *t);

time函数返回了当前时间点到linux epoch的秒数(内核中timekeeper模块保存了这个值,timekeeper->xtime_sec)。stime是设定当前时间点到linux epoch的秒数。对于linux kernel,设定时间的进程必须拥有CAP_SYS_TIME的权利,否则会失败。

linux kernel用系统调用sys_time和sys_stime来支持这两个函数。实际上,在引入更高精度的时间相关的系统调用之后(例如:sys_gettimeofday),上面这两个系统调用可以用新的系统调在用户空间实现time和stime函数。在kernel中,只有定义了__ARCH_WANT_SYS_TIME这个宏,系统才会提供上面这两个系统调用。当然,提供这样的系统调用多半是为了兼容旧的应用软件。

配合上面的接口函数还有一系列将当前时间点到linux epoch的秒数转换成适合人类阅读的接口函数,例如asctime, ctime, gmtime, localtime, mktime, asctime_r, ctime_r, gmtime_r, localtime_r ,这些函数主要用来将time_t类型的时间转换成break-down time或者字符形式。

2、微秒级别的时间函数:gettimeofday和settimeofday

#include 

int gettimeofday(struct timeval *tv, struct timezone *tz);

int settimeofday(const struct timeval *tv, const struct timezone *tz);

这两个函数和上一小节秒数的函数类似,只不过时间精度可以达到微秒级别。gettimeofday函数可以获取从linux epoch到当前时间点的秒数以及微秒数(在内核态,这个时间值仍然是通过timekeeper模块获得的,具体接口是getnstimeofday64,该接口的时间精度是纳秒级别的,不过没有关系,除以1000就获得微秒级别的精度了),settimeofday则是设定从linux epoch到当前时间点的秒数以及微秒数。同样的,设定时间的进程必须拥有CAP_SYS_TIME的权利,否则会失败。tz参数是由于历史原因而存在,实际上内核并没有对timezone进行支持。

显然,sys_gettimeofday和sys_settimeofday这两个系统调用是用来支持上面两个函数功能的,值得一提的是:这些系统调用在新的POSIX标准中 gettimeofday和settimeofday接口函数被标注为obsolescent,取而代之的是clock_gettime和clock_settime接口函数

3、纳秒级别的时间函数:clock_gettime和clock_settime

#include 

int clock_getres(clockid_t clk_id, struct timespec *res);

int clock_gettime(clockid_t clk_id, struct timespec *tp);

int clock_settime(clockid_t clk_id, const struct timespec *tp);

如果不是clk_id这个参数,clock_gettime和clock_settime基本上是不用解释的,其概念和gettimeofday和settimeofday接口函数是完全类似的,除了精度是纳秒。clock就是时钟的意思,它记录了时间的流逝。clock ID当然就是识别system clock(系统时钟)的ID了,定义如下:

CLOCK_REALTIME
CLOCK_MONOTONIC
CLOCK_MONOTONIC_RAW
CLOCK_PROCESS_CPUTIME_ID
CLOCK_THREAD_CPUTIME_ID

根据应用的需求,内核维护了几个不同系统时钟。大家最熟悉的当然就是CLOCK_REALTIME这个系统时钟,因为它表示了真实世界的墙上时钟(前面两节的接口函数没有指定CLOCK ID,实际上获取的就是CLOCK_REALTIME的时间值)。CLOCK_REALTIME这个系统时钟允许用户对其进行设定(当然要有CAP_SYS_TIME权限),这也就表示在用户空间可以对该系统时钟进行修改,产生不连续的时间间断点。除此之外,也可以通过NTP对该时钟进行调整(不会有间断点,NTP调整的是local oscillator和上游服务器频率误差而已)。

仅仅从名字上就可以看出CLOCK_MONOTONIC的系统时钟应该是单调递增的,此外,该时钟也是真实世界的墙上时钟,只不过其基准点不一定是linux epoch(当然也可以是),一般会把系统启动的时间点设定为其基准点。随后该时钟会不断的递增。除了可以通过NTP对该时钟进行调整之外,其他任何程序不允许设定该时钟,这样也就保证了该时钟的单调性。

CLOCK_MONOTONIC_RAW具备CLOCK_MONOTONIC的特性,除了NTP调整。也就是说,clock id是CLOCK_MONOTONIC_RAW的系统时钟是一个完全基于本地晶振的时钟。不能设定,也不能对对晶振频率进行调整。

在调用clock_gettime和clock_settime接口函数时,如果传递clock id参数是CLOCK_REALTIME的话,那么这两个函数的行为和前两个小节描述的一致,除了是ns精度。读到这里,我相信广大人民群众不免要问:为何要有其他类型的系统时钟呢?MONOTONIC类型的时钟相对比较简单,如果你设定事件A之后5秒进行动作B,那么用MONOTONIC类型的时钟是一个比较好的选择,如果使用REALTIME的时钟,当用户在事件A和动作B之间插入时间设定的操作,那么你设定事件A之后5秒进行动作B将不能触发。此外,用户需要了解系统启动时间,这个需求需要使用MONOTONIC类型的时钟的时钟。需要指出的是MONOTONIC类型的时钟不是绝对时间的概念,多半是计算两个采样点之间的时间,并且保证采样点之间时间的单调性。MONOTONIC_RAW是一个底层工具,一般而言程序员不会操作它,使用MONOTONIC类型的时钟就够用了,当然,一些高级的应用场合,例如你想使用另外的方法(不是NTP)来调整时间,那么就可以使用MONOTONIC_RAW了。

有些应用场景使用real time的时钟(墙上时钟)是不合适的,例如当我们进行系统中各个应用程序的性能分析和统计的时候。正因为如此,kernel提供了基于进程或者线程的系统时钟,也就是CLOCK_PROCESS_CPUTIME_ID和CLOCK_THREAD_CPUTIME_ID了。当我们打算使用基于进程或者线程的系统时钟的时候,需要首先获取clock id:

#include 

int clock_getcpuclockid(pid_t pid, clockid_t *clock_id);

如果是线程的话,需要调用pthread_getcpuclockid接口函数:

#include 
#include 

int pthread_getcpuclockid(pthread_t thread, clockid_t *clock_id);

虽然这组函数接口的精度可以达到ns级别,但是实际的系统可以达到什么样的精度是实现相关的,因此,clock_getres用来获取系统时钟的精度。

4、系统时钟的调整

设定系统时间是一个比较粗暴的做法,一旦修改了系统时间,系统中的很多依赖绝对时间的进程会有各种奇奇怪怪的行为。正因为如此,系统提供了时间同步的接口函数,可以让外部的精准的计时服务器来不断的修正系统时钟。

(1)adjtime接口函数

int adjtime(const struct timeval *delta, struct timeval *olddelta);

该函数可以根据delta参数缓慢的修正系统时钟(CLOCK_REALTIME那个)。olddelta返回上一次调整中尚未完整的delta。

(2)adjtimex

#include 

int adjtimex(struct timex *buf);

RFC 1305定义了更复杂,更强大的时间调整算法,因此linux kernel通过sys_adjtimex支持这个算法,其用户空间的接口函数就是adjtimex。由于这个算法过去强大,这里就不再赘述,等有时间、有兴趣之后再填补这里的空白吧。

Linux内核提供了sys_adjtimex系统调用来支持上面两个接口函数。此外,还提供了sys_clock_adjtime的系统调用来支持POSIX clock tunning。

三、进程睡眠

1、秒级别的sleep函数:sleep

#include 

unsigned int sleep(unsigned int seconds);

调用该函数会导致当前进程sleep,seconds之后(基于CLOCK_REALTIME)会返回继续执行程序。该函数的返回值说明了进程没有进入睡眠的时间。例如如果我们想要睡眠8秒,但是由于siganl中断了睡眠,只是sleep了5秒,那么返回值就是3,表示有3秒还没有睡。

2、微秒级别的sleep函数:usleep

include

int usleep(useconds_t usec);

概念上和sleep一样,不过返回值的定义不同。usleep返回0表示执行成功,返回-1说明执行失败,错误码在errno中获取。

3、纳秒级别的sleep函数:nanosleep

#include 

int nanosleep(const struct timespec *req, struct timespec *rem);

usleep函数已经是过去式,不建议使用,取而代之的是nanosleep函数。req中设定你要sleep的秒以及纳秒值,然后调用该函数让当前进程sleep。返回0表示执行成功,返回-1说明执行失败,错误码在errno中获取。EINTR表示该函数被signal打断。rem参数是remaining time的意思,也就是说还有多少时间没有睡完。

linux kernel并没有提供sleep和usleep对应的系统调用,sleep和usleep的实现位于c lib。在有些系统中,这些实现是依赖信号的,也有的系统使用timer来实现的,对于GNU系统,sleep和usleep和nanosleep函数一样,都是通过kernel的sys_nanosleep的系统调用实现的(底层是基于hrtimer)。

4、更高级的sleep函数:clock_nanosleep

#include 

int clock_nanosleep(clockid_t clock_id, int flags, 
                    const struct timespec *request, 
                    struct timespec *remain);

clock_nanosleep接口函数需要传递更多的参数,当然也就是意味着它功能更强大。clock_id说明该接口函数不仅能基于real time clock睡眠,还可以基于其他的系统时钟睡眠。flag等于0或者1,分别指明request参数设定的时间值是相对时间还是绝对时间。

四、和timer相关的服务

1、alarm函数

#include 

unsigned int alarm(unsigned int seconds);

alarm函数是使用timer最简单的接口。在指定秒数(基于CLOCK_REALTIME)的时间过去后,向该进程发送SIGALRM信号。当然,调用该接口的程序需要设定signal handler。

2、Interval timer函数

#include 

int getitimer(int which, struct itimerval *curr_value); 
int setitimer(int which, const struct itimerval *new_value, 
              struct itimerval *old_value);

Interval timer函数的行为和alarm函数类似,不过功能更强大。每个进程支持3种timer,不同的timer定义了如何计时以及发送什么样的信号给进程,which参数指明使用哪个timer:

(1)ITIMER_REAL。基于CLOCK_REALTIME计时,超时后发送SIGALRM信号,和alarm函数一样。

(2)ITIMER_VIRTUAL。只有当该进程的用户空间代码执行的时候才计时,超时后发送SIGVTALRM信号。

(3)ITIMER_PROF。只有该进程执行的时候才计时,不论是执行用户空间代码还是陷入内核执行(例如系统调用),超时后发送SIGPROF信号。

struct itimerval定义如下:

struct itimerval { 
    struct timeval it_interval; /* next value */ 
    struct timeval it_value;    /* current value */ 
};

两个成员分别指明了本次和下次(超期后如何设定)的时间值。通过这样的定义,interval timer可以实现one shot类型的timer和periodic的timer。例如current value设定为5秒,next value设定为3秒,设定这样的timer后,it_value值会不断递减,直到5秒后触发,而随后it_value的值会被重新加载(使用it_interval的值),也就是等于3秒,之后会按照3为周期不断的触发。

old_value返回上次setitimer函数的设定值。getitimer函数获取当前的Interval timer的状态,其中的it_value成员可以得到当前时刻到下一次触发点的世时间信息,it_interval成员保持不变,除非你重新调用setitimer重新设定。

虽然interval timer函数也是POSIX标准的一部分,不过在新的POSIX标准中,interval timer接口函数被标注为obsolescent,取而代之的是POSIX timer接口函数。

3、更高级,更灵活的timer函数

上一节介绍的Interval timer函数还是有功能不足之处:例如一个进程只能有ITIMER_REAL、ITIMER_VIRTUAL和ITIMER_PROF三个timer,如果连续设定其中一种timer(例如ITIMER_REAL),这会导致第一个设定被第二次设定覆盖。此外,超时处理永远是用信号的方式,而且发送的signal不能修改。当mask信号处理的时候,虽然timer多次超期,但是signal handler只会调用一次,无法获取更详细的信息。最后一点,Interval timer函数精度是微秒级别,精度有进一步提升的空间。正因为传统的Interval timer函数的不足之处,POSIX标准定义了更高级,更灵活的timer函数,我们称之POSIX (interval)Timer。

(1)创建timer

#include 
#include 

int timer_create(clockid_t clockid, struct sigevent *sevp,   timer_t *timerid);

在这个接口函数中,clock id相信大家都很熟悉了, timerid一看就是返回的timer ID的句柄,就像open函数返回的文件描述符一样。因此,要理解这个接口函数重点是了解struct sigevent这个数据结构:

union sigval {          /* Data passed with notification */ 
    int     sival_int;         /* Integer value */ 
    void   *sival_ptr;         /* Pointer value */ 
};

typedef struct sigevent { 
    sigval_t sigev_value; 
    int sigev_signo; 
    int sigev_notify; 
    union { 
        int _pad[SIGEV_PAD_SIZE]; 
         int _tid;

        struct { 
            void (*_function)(sigval_t); 
            void *_attribute;    /* really pthread_attr_t */ 
        } _sigev_thread; 
    } _sigev_un; 
} sigevent_t;

sigev_notify定义了当timer超期后如何通知该进程,可以设定:

(a)SIGEV_NONE。不需要异步通知,程序自己调用timer_gettime来轮询timer的当前状态

(b)SIGEV_SIGNAL。使用sinal这样的异步通知方式。发送的信号由sigev_signo定义。如果发送的是realtime signal,该信号的附加数据由sigev_value定义。

(c)SIGEV_THREAD。创建一个线程执行timer超期callback函数,_attribute定义了该线程的属性。

(d)SIGEV_THREAD_ID。行为和SIGEV_SIGNAL类似,不过发送的信号被送达进程内的一个指定的thread,这个thread由_tid标识。

(2)设定timer

#include 

int timer_settime(timer_t timerid, int flags,   const struct itimerspec *new_value, 
                  struct itimerspec * old_value); 
int timer_gettime(timer_t timerid, struct itimerspec *curr_value);

timerid就是上一节中通过timer_create创建的timer。new_value和old_value这两个参数类似setitimer函数,这里就不再细述了。flag等于0或者1,分别指明new_value参数设定的时间值是相对时间还是绝对时间。如果new_value.it_value是一个非0值,那么调用timer_settime可以启动该timer。如果new_value.it_value是一个0值,那么调用timer_settime可以stop该timer。

timer_gettime函数和getitimer类似,可以参考上面的描述。

(3)删除timer

#include 

int timer_delete(timer_t timerid);

有创建就有删除,timer_delete用来删除指定的timer,释放资源。

转发请注明出处。蜗窝科技:

  • http://www.wowotech.net/timer_subsystem/timer_subsystem_userspace.html

  • 原始链接:http://www.wowotech.net/timer_subsystem/clock-id-in-linux.html

Linux的时钟

作者:linuxer 发布于:2017-5-17 18:55 分类:时间子系统

一、前言

时钟或者钟表(clock)是一种计时工具,每个人都至少有一块,可能在你的手机里,也可能佩戴在你的手腕上。如果Linux也是一个普通人的话,那么她的手腕上应该有十几块手表,包括:

  • CLOCK_REALTIME;
  • CLOCK_MONOTONIC
  • CLOCK_PROCESS_CPUTIME_ID
  • CLOCK_THREAD_CPUTIME_ID
  • CLOCK_MONOTONIC_RAW
  • CLOCK_REALTIME_COARSE
  • CLOCK_MONOTONIC_COARSE
  • CLOCK_BOOTTIME
  • CLOCK_REALTIME_ALARM
  • CLOCK_BOOTTIME_ALARM
  • CLOCK_TAI
    本文主要就是介绍Linux内核中的形形色色的“钟表”。
posix_clocks数组定义了系统支持的所有的clock,相关的定义如下:

#define CLOCK_REALTIME            0 
#define CLOCK_MONOTONIC            1 
#define CLOCK_PROCESS_CPUTIME_ID    2 
#define CLOCK_THREAD_CPUTIME_ID        3 
#define CLOCK_MONOTONIC_RAW        4 
#define CLOCK_REALTIME_COARSE        5 
#define CLOCK_MONOTONIC_COARSE        6 
#define CLOCK_BOOTTIME            7 
#define CLOCK_REALTIME_ALARM        8 
#define CLOCK_BOOTTIME_ALARM        9 
#define CLOCK_SGI_CYCLE            10    /* Hardware specific */ 
#define CLOCK_TAI            11

#define MAX_CLOCKS            16

struct k_clock { 
    int (*clock_getres) (const clockid_t which_clock, struct timespec *tp); 
    int (*clock_set) (const clockid_t which_clock, const struct timespec *tp); 
    int (*clock_get) (const clockid_t which_clock, struct timespec * tp); 
    int (*clock_adj) (const clockid_t which_clock, struct timex *tx); 
    int (*timer_create) (struct k_itimer *timer); 
    int (*nsleep) (const clockid_t which_clock, int flags, struct timespec *, struct timespec __user *); 
    long (*nsleep_restart) (struct restart_block *restart_block); 
    int (*timer_set) (struct k_itimer * timr, int flags, struct itimerspec * new_setting, 
                            struct itimerspec * old_setting); 
    int (*timer_del) (struct k_itimer * timr); 
    void (*timer_get) (struct k_itimer * timr, struct itimerspec * cur_setting); 
};
static struct k_clock posix_clocks[MAX_CLOCKS];

2、monotonic clock的定义如下:

// 转发请注明出处。蜗窝科技http://www.wowotech.net/timer_subsystem/posix-clock.html
struct k_clock clock_monotonic = { 
    .clock_getres    = hrtimer_get_res, 
    .clock_get    = posix_ktime_get_ts, 
    .nsleep        = common_nsleep, 
    .nsleep_restart    = hrtimer_nanosleep_restart, 
};

monotonic clock没有clock_set函数,不能被设定。通过ktime_get_ts这个timekeeping模块的接口可以获得monotonic clock的当前值。纳秒级别的sleep以及精度的获取函数和real time clock一样。

2019-12-17 Linux的时钟+纳秒定时器_第1张图片
http://www.wowotech.net/timer_subsystem/time-subsyste-architecture.html

转发请注明出处。蜗窝科技
http://www.wowotech.net/timer_subsystem/time-subsyste-architecture.html

2019-12-17 Linux的时钟+纳秒定时器_第2张图片

转载

《郭健: Linux时间子系统之ARM generic timer驱动代码分析》

  • 转载自:https://cloud.tencent.com/developer/article/1518249
  • 原始链接1(已经失效):http://www.wowotech.net/linux_kenrel/arm-generic-timer.html

一、前言

关注ARM平台上timer driver(clocksource chip driver和clockevent chip driver)的驱动工程师应该会注意到timer硬件的演化过程。在单核时代,各个SOC vendor厂商购买ARM core的IP,然后自己设计SOC上的peripherals,这里面就包括了timer的硬件。由于没有统一的标准,各个厂商的设计各不相同,这给驱动工程师带来了工作量。然而,如果仅仅是工作量的话就还好,实际上,不仅仅如此。linux的时间子系统要求硬件timer提供下面两种能力:一是free running的counter,此外需要能够在指定的counter值上产生中断的能力。有些硬件厂商会考虑到软件的需求(例如:PXA270的timer硬件),但是有些硬件厂商做的就不够,例如:S3C2451的timer硬件。我们在写PXA270的timer硬件驱动的时候是毫无压力的,而在写S3C2451的timer的驱动的时候,最大的愿望就是把三星的HW timer的设计人员拉出来打一顿

进入多核时代后,ARM公司提供了timer的硬件设计,集成在了自己的多核结构中。例如:在Cortex A15 MPcore的硬件体系结构中有一个HW block叫做Generic Timer(该硬件取代了A9中的global timer、private timer的功能),为系统提供了计时以及触发timer event的功能。

本文主要描述了Generic Timer的相关硬件知识以及在linux kernel中如何驱动该硬件。Generic Timer的代码位于linux-3.14/drivers/clocksource/目录下,该目录保存了所有clock source相关的driver,arm_arch_timer.c就是驱动Cortex A15 MPcore的Generic Timer的。

二、硬件描述

1、block diagram

正文请访问以下网址: https://cloud.tencent.com/developer/article/1518249

Linux内核源码arm_arch_time.c

https://elixir.bootlin.com/linux/latest/source/drivers/clocksource/arm_arch_timer.c

你可能感兴趣的:(2019-12-17 Linux的时钟+纳秒定时器)