所有timer_xxxx() 都要Link with -lrt
timer_create
int timer_create(clockid_t clockid, struct sigevent *restrict sevp,
timer_t *restrict timerid );
timer_create()
创建一个新的间隔定时器,新定时器的 ID在timerid指向的缓冲区中存储,该缓冲区必须是非空指针;此ID在进程中是唯一的,直到定时器被删除;新定时器的初始状态是未激活的。
clockid
参数指定新定时器用于衡量时间的时钟。它可以指定为下列值之一:
CLOCK_REALTIME
:墙上时间时钟,自1970-01-01起经历的时间,是可设置的。CLOCK_MONOTONIC
:单调时钟,从开机算起为0,不可设置,一直增加。CLOCK_PROCESS_CPUTIME_ID
:一个记录调用进程(process)所耗费的CPU时间(用户态+内核态)的时钟。即,对这个时钟调用clock_gettime()
得到的是当前进程所耗费的CPU时间。CLOCK_THREAD_CPUTIME_ID
:一个记录调用线程(thread)所耗费的CPU时间(用户态+内核态)的时钟。即,对这个时钟调用clock_gettime()
得到的是当前线程所耗费的CPU时间。其他CLOCK类型详见man手册
:略- 关于各种时钟的详细信息,可以查看clock_getres(2)。除了上述提到的各种clock,
clockid
也可以指定为由clock_getcpuclockid(3)
或pthread_getcpuclockid(3)
得到的clockid。
sevp
参数指向了一个sigevent结构体,用来指定当定时器到期时调用者如何进行响应。详细信息参见sigevent(7)
或【linux C】sigevent结构体
sevp->sigev_notify
字段可以设置为以下值:
SIGEV_NONE
:定时器到期后不进行异步通知,定时器的计时状态可以通过timer_gettime(2)
查看。SIGEV_SIGNAL
:在计时器过期时,为进程生成sigev_signo信号。详细信息参见sigevent(7)
或【linux C】sigevent结构体。siginfo_t结构体的si_code字段将被设置为SI_TIMER。在任何时刻,最多只有一个信号可以挂到指定的timer上,详情参见timer_getoverrun(2)。SIGEV_THREAD
:定时器到期时,就像一个新线程的入口函数一样调用sigev_notify_function
。详细信息参见sigevent(7)
或【linux C】sigevent结构体SIGEV_THREAD_ID
(linux特有):和SIGEV_SIGNAL
不同的是,信号会发送到pid(clone()
或getpid()
的返回值)为sigev_notify_thread_id的线程,该线程必须和调用线程在同一进程内。
将sevp
指定为 NULL 等同于指定一个指向一个sigevent
结构,其中sigev_notify是SIGEV_SIGNAL
,
sigev_signo是SIGALRM,sigev_value.sival_int是定时器ID身份证。
成功返回0,并将新定时器ID存储在timerid;失败返回-1,并设置errno。
timer_delete
int timer_delete(timer_t timerid);
timer_delete()
删除ID为timerid
的计时器。如果在此调用时计时器已经启动(激活),则它将被取消激活。任何由已删除的定时器生成的未决信号的处理方法,是未定义的。
成功返回0;失败返回-1,并设置errno。
timer_settime/timer_gettime
int timer_settime(timer_t timerid, int flags,
const struct itimerspec *restrict new_value,
struct itimerspec *restrict old_value);
int timer_gettime(timer_t timerid, struct itimerspec *curr_value);
timer_settime()
激活/取消激活由timerid
指定的定时器。new_value
指向一个itimerspec结构体用来指定新定时器的初始值(第一次定时器到期的时间)和新的间隔值(第一次到期之后,周期性到期时间)。itimerspec结构体如下:
struct timespec {
time_t tv_sec; /* Seconds */
long tv_nsec; /* Nanoseconds */
};
struct itimerspec {
struct timespec it_interval; /* Timer interval */
struct timespec it_value; /* Initial expiration */
};
这些结构体的值,将被timer_create
指定的时钟使用。
如果new_value -> it_value
指定了一个非零值(tv_sec或tv_nsec不为0) ,则timer_settime()
会激活(启动) 这个定时器,设置它的初始到期时间为new_value -> it_value
。如果定时器已经处于激活状态(启动的),那么之前的设置的初始值将会被覆盖。如果new_value->it_value
的值为0(即,tv_sec和tv_nsec均为0) ,那么定时器将会被取消激活(取消启动)。
new_value->it_interval
指定了定时器的周期。如果不为0,那么每当定时器到期后,定时器会重新装载new_value->it_interval
的值作为下一次的到期时间。如果new_value->it_interval
为0,那么这个定时器只会在new_value->it_value
指定的时刻到期一次,然后就处于“未激活”的状态了。
默认情况下,在new_value->it_value
中指定的初始值是相对时间,相对于timer_settime()
调用时定时器时钟上的时间。这可以通过在flags
中指定TIMER_ABSTIME
来修改,在这种情况下,new_value->it_value
可以理解为定时器时钟所衡量的绝对值;也就是说,当时间流逝到new_value->it_value
指定的绝对时间值时,计时器将到期。如果指定的绝对时间已经过去,那么计时器立即到期(所谓到期,即调用sigev_notify指定的行为,然后定时器进入未激活状态),如有溢出发生,溢出计数(参见timer_getoverrun(2))将被正确设置。
当一个定时器依赖时钟CLOCK_REALTIME
的绝对时间(TIMER_ABSTIME
),且该定时器已经激活(正在等待时间流逝,到期则触发定时器的通知sigev_notify
),如果此时时钟CLOCK_REALTIME
的值(墙上时间,yy年mm月dd日 hh:mm:ss)被修改的话,那么会对定时器造成影响。比如,当前时间为2022年4月7日13:51:00,定时器设置到期时间为2022年4月7日13:53:00,那么定时器将会在2分钟后到期;若突然(一瞬间,忽略修改时间所花费的时间)修改日历时间为2022年4月7日13:52:00,那么定时器就不是2分钟后到期了,而是1分钟后就到期;若是突然修改日历时间为2022年4月7日13:53:00,则定时器立即到期,会立即执行sigev_notify
所规定的动作。修改日历时间不会对相对时间的定时器有影响。
如果old_value
不为NULL,那么它指向一个buffer用来存储:在本次timer_settime()
对定时器修改之前,定时器的间隔值(即旧间隔值,存储在old_value->it_interval);在本次timer_settime()
对定时器修改之前,定时器的剩余到期时间(即再经过多长时间定时器就会到期,存储在old_value->it_value)。实测得到:当timer_settime()
用于激活一个处于未激活态的timer(新创建的timer或者已到期但间隔值为0的timer,都处于未激活态)时,old_value
如果不为空,old_value
的成员it_interval和it_value都为0。
timer_gettime()
返回timerid
所指定的定时器的剩余到期时间和间隔值,存储在curr_value
中。剩余到期时间存储在curr_value->it_value
,且该值总是一个相对值,无论定时器激活时是否使用flag TIMER_ABSTIME。如果curr_value->it_value
为0,则表示该定时器处于“未激活”的状态。定时器的间隔值存储在curr_value->it_interval
,若该值为0,则表示这个定时器是个“单次”定时器。
成功返回0,失败返回-1,errno被设置。
timer_getoverrun
int timer_getoverrun(timer_t timerid);
timer_getoverrun()
返回timerid
指定的定时器的溢出计数。程序可以使用溢出计数来精确计算在特定间隔值下发生的计时器过期次数。以信号形式通知到期(SIGEV_SIGNAL)和以线程形式通知到期(SIGEV_THREAD)的定时器都有可能发生定时器溢出。
当定时器以信号形式通知到期时,溢出发生原理如下:
- 不管通知信号是否是实时信号,每个定时器上最多只能挂一个信号在队列上。(这是POSIX.1所指定的行为。对应的,若是在每次定时器过期时都让一个信号排队,这很容易导致系统中排队信号的数量超出系统所允许的最大限制。)
- 由于系统调度延迟、信号临时阻塞,在通知信号产生和被捕获(即,被信号处理函数捕获)、被接受(如,sigwaitinfo(2))这段时间之内,会有一个时间延迟。在此期间,也可能发生定时器到期。溢出计数统计的就是从信号产生到被递达(捕获)、被接受这段时间内发生的定时器到期的次数。
线程通知的定时器,发生溢出:
- 由于从定时器到期到线程被调度存在不确定的延时,在此期间发生的定时器到期,都会被计入定时器溢出计数。
成功返回溢出计数;失败返回-1,errno被设置。