Page tree
Skip to end of metadata
Go to start of metadata

什么是定时器

定时器,顾名思义就是用来定时的,通俗的可以将其看作一个闹钟。在Linux应用编程层上,当需要实现下列几种功能时,可考虑使用定时器。

  • 1.周期性执行某一项任务;
  • 2.在指定时间去执行某一项任务;

定时功能的API

sleep()
usleep()
nanosleep()
alarm()

sleep()和usleep(),Linux并没有提供系统调用,他们都是在库函数中实现的,是通过调用alarm()来设定报警时间,调用sigsuspend()将进程挂起在信号SIGALARM上。sleep()精度是1秒,usleep()精度是1微妙;使用这种方法缺点比较明显,在Linux系统中,sleep类函数不能保证精度,尤其在系统负载比较大时,一般都会有超时现象。
nanosleep()则是Linux中的系统调用,它是使用定时器来实现的,该函数使调用进程睡眠;
alarm()也是通过定时器实现的,其精度只精确到秒级,它设置的定时器执行函数是在指定时间向当前进程发送SIGALRM信号。


间隔定时器

间隔定时器(Interval Timer,简称itimer)是指定时器采用“间隔”值(interval)来作为计时方式,当定时器启动后,间隔值interval将不断减小。当interval值减到0时,说明该间隔定时器到期。
间隔定时器主要被应用在用户进程上。每个Linux进程都有三个相互关联的间隔定时器:

  • 真实间隔定时器(ITIMER_REAL-以系统真实的时间计算):这种间隔定时器在启动后,不管进程是否运行,每个时钟滴答都将其间隔计数器减1。当减到0值时,内核向进程发送SIGALRM信号。
  • 虚拟间隔定时器(ITIMER_VIRT-以该进程在用户态下花费的时间来计算):也称为进程的用户态间隔定时器。当虚拟间隔定时器启动后,只有当进程在用户态下运行时,一次时钟滴答才能使间隔计数器当前值it_virt_value减1。当减到0值时,内核向进程发送SIGVTALRM信号(虚拟闹钟信号),并将it_virt_value重置为初值it_virt_incr。
  • PROF间隔定时器(ITIMER_PROF-以该进程在用户态下和内核态下所费的时间来计算):当一个进程的PROF间隔定时器启动后,则只要该进程处于运行中,而不管是在用户态或核心态下执行,每个时钟滴答都使间隔计数器it_prof_value值减1。当减到0值时,内核向进程发送SIGPROF信号,并将it_prof_value重置为初值it_prof_incr。


POSIX定时器

POSIX定时器的是为了解决间隔定时器itimer的以下问题:

  • 一个进程同一时刻只能有一个同一种类型(ITIMER_REAL, ITIMER_PROF, ITIMER_VIRT)的itimer。POSIX定时器在一个进程中可以创建任意多个timer。
  • itimer定时器到期后,只能通过信号(SIGALRM,SIGVTALRM,SIGPROF)的方式通知进程,POSIX定时器到期后不仅可以通过信号进行通知,还可以使用自定义信号,还可以通过启动一个线程来进行通知。
  • itimer支持us级别,POSIX定时器支持ns级别。

号称最强大的定时器接口来自POSIX时钟系列。


int timer_create(clockid_t clockid, struct sigevent *sevp,timer_t timerid);
int timer_delete (timer_t timerid);
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);
int timer_getoverrun(timer_t timerid);


timer_create()函数

第一个参数clockid的取值有四种,如下表

Clock ID描述
CLOCK_REALTIMESettable system-wide real-time clock
CLOCK_MONOTONICNonsettable monotonic clock
CLOCK_PROCESS_CPUTIME_IDPer-process CPU-time clock
CLOCK_THREAD_CPUTIME_IDPer-thread CPU-time clock

CLOCK_REALTIME 时间是系统保存的时间,即可以由 date 命令显示的时间,该时间可以重新设置。比如当前时间为上午 10 点 10 分,Timer 打算在 10 分钟后到时。假如 5 分钟后,我用 date 命令修改当前时间为 10 点 10 分,那么 Timer 还会再等十分钟到期,因此实际上 Timer 等待了 15 分钟。假如您希望无论任何人如何修改系统时间,Timer 都严格按照 10 分钟的周期进行触发,那么就可以使用 CLOCK_MONOTONIC。

CLOCK_PROCESS_CPUTIME_ID 的含义与 setitimer 的 ITIMER_VIRTUAL 类似。计时器只记录当前进程所实际花费的时间;比如还是上面的例子,假设系统非常繁忙,当前进程只能获得 50%的 CPU 时间,为了让进程真正地运行 10 分钟,应该到 10 点 30 分才允许 Timer 到期。

CLOCK_THREAD_CPUTIME_ID 以线程为计时实体,当前进程中的某个线程真正地运行了一定时间才触发 Timer。

第二个参数 struct sigevent 用来设置定时器到时时的通知方式。该数据结构如下:

struct sigevent {
	int sigev_notify; /* Notification method */
 	int sigev_signo; /* Notification signal */
 	union sigval sigev_value; /* Data passed with notification */
	void (*sigev_notify_function) (union sigval);
	/* Function used for thread notification (SIGEV_THREAD) */
	void *sigev_notify_attributes;
 	/* Attributes for notification thread (SIGEV_THREAD) */
	pid_t sigev_notify_thread_id;
	/* ID of thread to signal (SIGEV_THREAD_ID) */
 };

sigev_notify 表示通知方式,有如下几种:

通知方式描述
SIGEV_NONE定时器到期时不产生通知
SIGEV_SIGNAL定时器到期时将给进程投递一个信号,sigev_signo 可以用来指定使用什么信号
SIGEV_THREAD定时器到期时将启动新的线程进行需要的处理
SIGEV_THREAD_ID(仅针对 Linux)定时器到期时将向指定线程发送信号

如果采用 SIGEV_NONE 方式,使用者必须调用timer_gettime()函数主动读取定时器已经走过的时间,类似轮询方式。

如果采用 SIGEV_SIGNAL 方式,使用者可以选择使用什么信号,用 sigev_signo 表示信号值,比如 SIG_ALARM。

如果使用 SIGEV_THREAD 方式,则需要设置 sigev_notify_function,当 Timer 到期时,将使用该函数作为入口启动一个线程来处理信号。sigev_value 保存了传入 sigev_notify_function 的参数。sigev_notify_attributes 如果非空,则应该是一个指向 pthread_attr_t 的指针,用来设置线程的属性(比如 stack 大小,detach 状态等)。

SIGEV_THREAD_ID 通常和 SIGEV_SIGNAL 联合使用,这样当 Timer 到期时,系统会向由 sigev_notify_thread_id 指定的线程发送信号,否则可能进程中的任意线程都可能收到该信号。这个选项是 Linux 对 POSIX 标准的扩展,目前主要是 GLibc 在实现 SIGEV_THREAD 的时候使用到,应用程序很少会需要用到这种模式。

sigev_signo在sigev_notify = SIGEV_SIGNAL 时使用,指定信号的种别(number)。

sigev_value在sigev_notify = SIGEV_THREAD 时使用,作为sigev_notify_function 的参数。union sigval的结构体如下:

union sigval
{
    int sival_int;
    void *sival_ptr;
};

(*sigev_notify_function)(union sigval)函数指针(指向通知执行函数),在sigev_notify = SIGEV_THREAD 时使用, 其他情况下置为NULL.

sigev_notify_attributes指向线程属性的指针,在sigev_notify = SIGEV_THREAD 时使用,指定创建线程的属性, 其他情况下置为NULL. 

timer_settime()函数

第二个参数flags,flags取值只有2个 : 0 和 TIMER_ABSTIME。

  • 当 flags 为 0 时, new_value->it_value 表示希望timer首次到期时的时间与启动timer的时间间隔(例如,希望timer 在 2秒后到期)
  • 当flags为 TIMER_ABSTIME 时, new_value->it_value 表示希望timer首次到期的绝对时间(例如希望timer 在 01:23:45 到期)

   如果new_value->it_value 设定的绝对时间 早于 当前的绝对时间, 那么timer会立即到期.

   如果时钟 CLOCK_REALTIME 被调整了,那么timer的首次过期时间也会适当调整.

第三个参数new_value的结构提如下,有2个子域: it_value 和 it_interval

struct itimerspec 
{
    struct timespec it_interval;  /* Timer interval(timer循环时间间隔) */
    struct timespec it_value;     /* Initial expiration(timer初次到期时间间隔) */
};


  • it_value : 用于设置首次timer到期的时间, 也是 启动/停止 timer的控制域
  • it_interval : 用于设置timer循环的时间间隔, 如果其值不为0(秒和纳秒至少有一个不为0),每次timer到期时,timer会使用new_value->it_interval的值重新加载timer;

如果其值为0, timer只会在it_value指定的时间到期一次,之后不会重新加载timer.

启动timer

前提:timer处于停止(disarmed)状态,否则就是重置timer

设置:new_value->it_value的值是非0值(秒和纳秒都不等于0或者其中一个不等于0)

结果:timer变为启动(armed)状态.

停止timer

设置:new_value->it_value的的值为0(秒和纳秒都为0)

结果:timer变为停止(disarmed)状态.

重置timer

前提:timer处于已启动(armed)状态,否则就是启动timer

设置:new_value->it_value的的值不为0(秒和纳秒至少有一个不为0)

结果:timer仍为(armed)状态.之前的参数(即new_value(it_value 和 it_interval))设置会被覆盖.



  • No labels