上一篇 linux 下 alarm(), setitimer 定时器与 POSIX 定时器 timer_settime()对比总结 (上)总结了 alarm 和setitimer定时器的用法和注意事项。alarm不适用于精度要求比较高的场景,而setitimer也有一个缺点:因为setitimer 是配合SIGALRM中断信号使用的,而SIGALRM的中断信号会终止sleep,因为sleep就是用SIGALRM信号量实现的,因此也就有了POSIX 定时器出场的机会:
POSIX 定时器 主要分创建,初始化以及删除三个操作,对应的函数分别为:
timer_create()(创建定时器)
timer_settime()(初始化定时器)
timer_delete(销毁定时器)
首先需要创建一个定时器对象,设置通知类型,一般包括信号、脉冲或线程创建,并创建通知结构(结构sigevent),设置定时类型 (相对与绝对,一次与周期),最后启动它。
1. 创建定时器
int timer_create(clockid_t clock_id, struct sigevent *evp, timer_t *timerid)
进程可以通过调用timer_create()创建特定的定时器,定时器是每个进程自己的,不是在fork时继承的。clock_id说明定时器是基于哪个时钟的,*timerid装载的是被创建的定时器的ID。该函数创建了定时器,并将他的ID 放入timerid指向的位置中。参数evp指定了定时器到期要产生的异步通知。如果evp为NULL,那么定时器到期会产生默认的信号,对 CLOCK_REALTIMER来说,默认信号就是SIGALRM。如果要产生除默认信号之外的其它信号,程序必须将 evp->sigev_signo设置为期望的信号码。struct sigevent 结构中的成员evp->sigev_notify说明了定时器到期时应该采取的行动。通常,这个成员的值为SIGEV_SIGNAL,这个值说明在定时器到期时,会产生一个信号。程序可以将成员evp->sigev_notify设为SIGEV_NONE来防止定时器到期时产生信号。
如果几个定时器产生了同一个信号,处理程序可以用 evp->sigev_value来区分是哪个定时器产生了信号。要实现这种功能,程序必须在为信号安装处理程序时,使用struct sigaction的成员sa_flags中的标志符SA_SIGINFO。
clock_id取值为以下:
CLOCK_REALTIME :Systemwide realtime clock. 标准POSIX定义的时钟,如果它处于省电模式,基于这个时钟的定时器可以唤醒处理器
CLOCK_SOFTTIME: 这个时钟只在处理器不处于节电模式时才激活。例如,使用CLOCK_SOFTTIME计时器休眠的应用程序不会在应用程序应该唤醒时唤醒处理器。这将允许处理器进入省电模式。
CLOCK_MONOTONIC:Represents monotonic time. Cannot be set. 这个时钟总是固定定时增加,无法调整
CLOCK_PROCESS_CPUTIME_ID :High resolution per-process timer.
CLOCK_THREAD_CPUTIME_ID :Thread-specific timer.
CLOCK_REALTIME_HR :High resolution version of CLOCK_REALTIME.
CLOCK_MONOTONIC_HR :High resolution version of CLOCK_MONOTONIC.
第二个参数:sigevent数据结构的指针。这个数据结构用于通知内核,在“触发”时,计时器应该传递什么类型的事件。
struct sigevent
{
int sigev_notify; //notification type
int sigev_signo; //signal number
union sigval sigev_value; //signal value
void (*sigev_notify_function)(union sigval);
pthread_attr_t *sigev_notify_attributes;
}
union sigval
{
int sival_int; //integer value
void *sival_ptr; //pointer value
}
通过将evp->sigev_notify设定为如下值来定制定时器到期后的行为:
SIGEV_NONE:什么都不做,只提供通过timer_gettime和timer_getoverrun查询超时信息。
SIGEV_SIGNAL: 当定时器到期,内核会将sigev_signo所指定的信号传送给进程。在信号处理程序中,si_value会被设定会sigev_value。
SIGEV_THREAD: 当定时器到期,内核会(在此进程内)以sigev_notification_attributes为线程属性创建一个线程,并且让它执行sigev_notify_function,传入sigev_value作为为一个参数。
2. 设置定时器
timer_create()所创建的定时器并未启动。要将它关联到一个到期时间以及启动时钟周期。使用timer_settime()的参数组合完成,timer_settime()函数用于实际启动计时器:
int timer_settime (timer_t timerid, int flags, struct itimerspec *value, struct itimerspec *oldvalue);
timerid参数是从timer_create()函数调用中得到的值,创建一组计时器,然后在它们上分别调用timer_settime()来设置和启动它们。
flags参数就是指定绝对与相对标志。如果传递常量TIMER_ABSTIME,则value所指定的时间值会被解读成绝对值(此值的默认的解读方式为相对于当前的时间)。这个经修改的行为可避免取得当前时间、计算“该时间”与“所期望的未来时间”的相对差额以及启动定时器期间造成竞争条件。在希望计时器关闭的时候传递实际的日期和时间。如果传递一个0,那么计时器将被视为相对于当前时间。
value 参数指定时间。以下是两个数据结构的关键部分():
struct timespec
{ long tv_sec,
tv_nsec;
};
struct itimerspec
{
struct timespec it_value,//单次启动时间
it_interval;//定时时间
};
如同settimer(),it_value用于指定当前的定时器到期时间。当定时器到期,it_value的值会被更新成it_interval 的值。如果it_interval的值为0,则定时器不是一个时间间隔定时器,一旦it_value到期就会回到未启动状态。timespec的结构提供了纳秒级分辨率:
ovalue的值不是NULL,则之前的定时器到期时间会被存入其所提供的itimerspec。如果定时器之前处在未启动状态,则此结构的成员全都会被设定成0。
获得一个活动定时器的剩余时间:
int timer_gettime(timer_t timerid,struct itimerspec *value);
取得一个定时器的超限运行次数:
有可能一个定时器到期了,而同一定时器上一次到期时产生的信号还处于挂起状态。在这种情况下,其中的一个信号可能会丢失。这就是定时器超限。程序可以通过调用timer_getoverrun来确定一个特定的定时器出现这种超限的次数。定时器超限只能发生在同一个定时器产生的信号上。由多个定时器,甚至是那些使用相同的时钟和信号的定时器,所产生的信号都会排队而不会丢失。
int timer_getoverrun(timer_t timerid);
执行成功时,timer_getoverrun()会返回定时器初次到期与通知进程(例如通过信号)定时器已到期之间额外发生的定时器到期次数。举例来说,在我们之前的例子中,一个1ms的定时器运行了10ms,则此调用会返回9。如果超限运行的次数等于或大于DELAYTIMER_MAX,则此调用会返回DELAYTIMER_MAX。
执行失败时,此函数会返回-1并将errno设定会EINVAL,这个唯一的错误情况代表timerid指定了无效的定时器
3. 删除定时器
int timer_delete (timer_t timerid);
一次成功的timer_delete()调用会销毁关联到timerid的定时器并且返回0。执行失败时,此调用会返回-1并将errno设定会 EINVAL,这个唯一的错误情况代表timerid不是一个有效的定时器。
4. 示例
4.1 采用通知方式为信号(signal)的处理方式
4.1.1:使用SIGALRM信号量定时
上一篇讲到,如果使用信号量SIGALRM,(对 CLOCK_REALTIMER来说,默认信号就是SIGALRM)sleep()函数使用的就是实时时钟CLOCK_REALTIMER
所以使用信号值SIGALRM会中断sleep(int second)函数的休眠;
下面给出一个例子:
//timercreate_demo.cpp
#include <unistd.h>
#include <stdio.h>
#include <signal.h>
#include <time.h>
void SignHandler(int iSignNo);
void testTimerSign();
void printTime();
int main() {
testTimerSign();
while(true){
int left = sleep(5);
printTime();
printf("sleep(5)(left=%d)\n", left);
}
return 0;
}
void SignHandler(int iSignNo){
//printTime();
if(iSignNo == SIGUSR1){
printf("Capture sign no : SIGUSR1\n");
}else if(SIGALRM == iSignNo){
//printf("Capture sign no : SIGALRM\n");
}else{
printf("Capture sign no:%d\n",iSignNo);
}
}
void testTimerSign(){
struct sigevent evp;
struct itimerspec ts;
timer_t timer;
int ret;
evp.sigev_value.sival_ptr = &timer;
evp.sigev_notify = SIGEV_SIGNAL;
evp.sigev_signo = SIGALRM;
signal(evp.sigev_signo, SignHandler);
ret = timer_create(CLOCK_REALTIME, &evp, &timer);
if(ret) {
perror("timer_create");
}
ts.it_interval.tv_sec = 1;
ts.it_interval.tv_nsec = 0;
ts.it_value.tv_sec = 1;
ts.it_value.tv_nsec = 0;
printTime();
printf("start\n");
ret = timer_settime(timer, 0, &ts, NULL);
if(ret) {
perror("timer_settime");
}
}
void printTime(){
struct tm *cursystem;
time_t tm_t;
time(&tm_t);
cursystem = localtime(&tm_t);
char tszInfo[2048] ;
sprintf(tszInfo, "%02d:%02d:%02d",
cursystem->tm_hour,
cursystem->tm_min,
cursystem->tm_sec);
printf("[%s]",tszInfo);
}
因为timer_settime()中定时器间隔时间为1秒
于是sleep(5)每次都被打断不能按时休眠,剩余4秒未能执行;
4.1.2 示例2 SIGALRM信号量不同线程的影响
因为休眠sleep(unsigned int)为线程内操作
所以如果不同线程,信号量SIGALRM是不能中断sleep();
下面用一个例子说明:
//timercreate_demo.cpp
#include <unistd.h>
#include <stdio.h>
#include <signal.h>
#include <time.h>
#include <pthread.h>
void SignHandler(int iSignNo);
void testTimerSign();
void printTime();
void *function(void *arg);
int main() {
pthread_t thread1;
pthread_create(&thread1,NULL,function,(char*)"111");
testTimerSign();
while(true);
return 0;
}
void SignHandler(int iSignNo){
if(iSignNo == SIGUSR1){
printf("Capture sign no : SIGUSR1\n");
}else if(SIGALRM == iSignNo){
//printf("Capture sign no : SIGALRM\n");
}else{
printf("Capture sign no:%d\n",iSignNo);
}
}
void testTimerSign(){
struct sigevent evp;
struct itimerspec ts;
timer_t timer;
int ret;
evp.sigev_value.sival_ptr = &timer;
evp.sigev_notify = SIGEV_SIGNAL;
evp.sigev_signo = SIGALRM;
signal(evp.sigev_signo, SignHandler);
ret = timer_create(CLOCK_REALTIME, &evp, &timer);
if(ret) {
perror("timer_create");
}
ts.it_interval.tv_sec = 1;
ts.it_interval.tv_nsec = 0;
ts.it_value.tv_sec = 1;
ts.it_value.tv_nsec = 0;
printTime();
printf("start\n");
ret = timer_settime(timer, 0, &ts, NULL);
if(ret) {
perror("timer_settime");
}
}
void printTime(){
struct tm *cursystem;
time_t tm_t;
time(&tm_t);
cursystem = localtime(&tm_t);
char tszInfo[2048] ;
sprintf(tszInfo, "%02d:%02d:%02d",
cursystem->tm_hour,
cursystem->tm_min,
cursystem->tm_sec);
printf("[%s]",tszInfo);
}
void *function(void *arg){
char *m;
m = (char *)arg;
while(true) {
while(true){
int left = sleep(3);
printTime();
printf("sleep(3)(left=%d)\n", left);
}
}
}
注意编译的时候添加 -lpthread
输出结果:
可以看到主线程的定时器中的信号量SIGALRM是无法中断子线程thread1的休眠;
4.1.3 示例3 使用SIGUSR1信号量定时
//signalDemo.cpp
//compile : gcc signalDemo.cpp -o testSignal -lrt
#include <unistd.h>
#include <stdio.h>
#include <signal.h>
#include <time.h>
void testTimerSign();
void SignHandler(int iSignNo);
void printTime();
int main() {
testTimerSign();
while(true) {
sleep(1);
}
return 0;
}
void SignHandler(int iSignNo){
printTime();
if(iSignNo == SIGUSR1){
printf("Capture sign No.=SIGUSR1\n");
}else{
printf("Capture sign No.=%d\n",iSignNo);
}
}
void testTimerSign(){
struct sigevent evp;
struct itimerspec ts;
timer_t timer;
int ret;
evp.sigev_value.sival_ptr = &timer;
evp.sigev_notify = SIGEV_SIGNAL;
evp.sigev_signo = SIGUSR1;
signal(evp.sigev_signo, SignHandler);
ret = timer_create(CLOCK_REALTIME, &evp, &timer);
if(ret) {
perror("timer_create");
}
ts.it_interval.tv_sec = 1; // the spacing time
ts.it_interval.tv_nsec = 0;
ts.it_value.tv_sec = 2; // the delay time start
ts.it_value.tv_nsec = 0;
printTime();
printf("start\n");
ret = timer_settime(timer, 0, &ts, NULL);
if(ret) {
perror("timer_settime");
}
}
void printTime(){
struct tm *cursystem;
time_t tm_t;
time(&tm_t);
cursystem = localtime(&tm_t);
char tszInfo[2048] ;
sprintf(tszInfo, "%02d:%02d:%02d",
cursystem->tm_hour, cursystem->tm_min,
cursystem->tm_sec);
printf("[%s]",tszInfo);
}
输出结果:
4.1.4 示例4 信号量为signaction的处理函数
#include <stdio.h>
#include <time.h>
#include <stdlib.h>
#include <signal.h>
#include <string.h>
#include <unistd.h>
#define CLOCKID CLOCK_REALTIME
void sig_handler(int signo)
{
printf("timer_signal function! %d\n", signo);
}
int main()
{
//XXX int sigaction(int signum, const struct sigaction *act, struct sigaction *oldact);
// signum--指定的信号编号,可以指定SIGKILL和SIGSTOP以外的所有信号编号
// act结构体--设置信号编号为signum的处理方式
// oldact结构体--保存上次的处理方式
//
// struct sigaction
// {
// void (*sa_handler)(int); //信号响应函数地址
// void (*sa_sigaction)(int, siginfo_t *, void *); //但sa_flags为SA——SIGINFO时才使用
// sigset_t sa_mask; //说明一个信号集在调用捕捉函数之前,会加入进程的屏蔽中,当捕捉函数返回时,还原
// int sa_flags;
// void (*sa_restorer)(void); //未用
// };
//
timer_t timerid;
struct sigevent evp;
struct sigaction act;
memset(&act, 0, sizeof(act));
act.sa_handler = sig_handler;
act.sa_flags = 0;
// XXX int sigaddset(sigset_t *set, int signum); //将signum指定的信号加入set信号集
// XXX int sigemptyset(sigset_t *set); //初始化信号集
sigemptyset(&act.sa_mask);
if (sigaction(SIGUSR1, &act, NULL) == -1)
{
perror("fail to sigaction");
exit(-1);
}
memset(&evp, 0, sizeof(struct sigevent));
evp.sigev_signo = SIGUSR1;
evp.sigev_notify = SIGEV_SIGNAL;
if (timer_create(CLOCK_REALTIME, &evp, &timerid) == -1)
{
perror("fail to timer_create");
exit(-1);
}
struct itimerspec it;
it.it_interval.tv_sec = 2;
it.it_interval.tv_nsec = 0;
it.it_value.tv_sec = 1;
it.it_value.tv_nsec = 0;
if (timer_settime(timerid, 0, &it, 0) == -1)
{
perror("fail to timer_settime");
exit(-1);
}
pause();
return 0;
}
输出结果:
4.2 采用新线程派驻的通知方式
4.2.1 示例5 简单函数线程的处理方式
#include <stdio.h>
#include <signal.h>
#include <time.h>
#include <string.h>
#include <stdlib.h>
#include <unistd.h>
#define CLOCKID CLOCK_REALTIME
void timer_thread(union sigval v)
{
printf("timer_thread function! %d\n", v.sival_int);
}
int main()
{
// XXX int timer_create(clockid_t clockid, struct sigevent *evp, timer_t *timerid);
// clockid--值:CLOCK_REALTIME,CLOCK_MONOTONIC,CLOCK_PROCESS_CPUTIME_ID,CLOCK_THREAD_CPUTIME_ID
// evp--存放环境值的地址,结构成员说明了定时器到期的通知方式和处理方式等
// timerid--定时器标识符
timer_t timerid;
struct sigevent evp;
memset(&evp, 0, sizeof(struct sigevent)); //清零初始化
evp.sigev_value.sival_int = 111; //也是标识定时器的,这和timerid有什么区别?回调函数可以获得
evp.sigev_notify = SIGEV_THREAD; //线程通知的方式,派驻新线程
evp.sigev_notify_function = timer_thread; //线程函数地址
if (timer_create(CLOCKID, &evp, &timerid) == -1)
{
perror("fail to timer_create");
exit(-1);
}
// XXX int timer_settime(timer_t timerid, int flags, const struct itimerspec *new_value,struct itimerspec *old_value);
// timerid--定时器标识
// flags--0表示相对时间,1表示绝对时间,通常使用相对时间
// new_value--定时器的新初始值和间隔,如下面的it
// old_value--取值通常为0,即第四个参数常为NULL,若不为NULL,则返回定时器的前一个值
//第一次间隔it.it_value这么长,以后每次都是it.it_interval这么长,就是说it.it_value变0的时候会装载it.it_interval的值
//it.it_interval可以理解为周期
struct itimerspec it;
it.it_interval.tv_sec = 1; //间隔1s
it.it_interval.tv_nsec = 0;
it.it_value.tv_sec = 1;
it.it_value.tv_nsec = 0;
if (timer_settime(timerid, 0, &it, NULL) == -1)
{
perror("fail to timer_settime");
exit(-1);
}
pause();
return 0;
}
/*
* int timer_gettime(timer_t timerid, struct itimerspec *curr_value);
* 获取timerid指定的定时器的值,填入curr_value
*
*/
输出结果:
4.2.2 示例6 使用pthread 创建线程的处理方法
#include <stdio.h>
#include <unistd.h>
#include <stdlib.h>
#include <signal.h>
#include <time.h>
#include <error.h>
#include <errno.h>
#include <string.h>
#include <pthread.h>
#include <sys/wait.h>
void timer_thread(union sigval v)
{
printf("timer_thread function! %d\n", v.sival_int);
}
int init_timer(timer_t *timerid, struct sigevent *evp, struct itimerspec *it)
{
if ( !evp || !it )
return -1;
memset(evp, 0, sizeof(struct sigevent)); //清零初始化
evp->sigev_value.sival_int = 111; //也是标识定时器的,这和timerid有什么区别?回调函数可以获得
evp->sigev_notify = SIGEV_THREAD; //线程通知的方式,派驻新线程
evp->sigev_notify_function = timer_thread; //线程函数地址
if (timer_create(CLOCK_REALTIME, evp, timerid) == -1)
{
perror("fail to timer_create");
return -1;;
}
printf("timer_create timerid = %d\n", *timerid);
it->it_interval.tv_sec = 1; // 后续按照该时间间隔
it->it_interval.tv_nsec = 0;
it->it_value.tv_sec = 3; // 最初开始时间间隔
it->it_value.tv_nsec = 0;
return 0;
}
int start_timer(timer_t *timerid, struct itimerspec *it)
{
if (it == NULL){
return -1;
}
if (timer_settime(*timerid, 0, it, NULL) == -1)
{
perror("fail to timer_settime");
return -1;
}
return 0;
}
void* thread_func(void *param)
{
int a = *(int *)param;
while(1){
sleep(1);
printf("This is thread..\n");
}
a = 100;
printf("param = %d\n", a);
}
int main(int argc, const char *argv[])
{
pid_t pid = 0;
pthread_t thread;
timer_t timerid = 0;
int ret;
struct sigevent evp;
struct itimerspec it;
int a = 10;
#if 0
int ret = init_timer(&timerid, &evp, &it);
if (ret < 0){
printf("init_timer failed\n");
return -1;
}
#endif
if ((pid = fork()) < 0)
{
printf("fork failed.\n");
return -1;
}
else if ( pid == 0){
printf("child proc..\n");
ret = pthread_create(&thread, NULL, thread_func, &a);//最后一个参数为void*类型,传递变量的地址,其实其他的类型也遵循这个原则
int ret = init_timer(&timerid, &evp, &it);
if (ret < 0){
printf("init_timer failed\n");
return -1;
}
sleep(2);
printf("child timer_Id addr = %d\n", timerid);
start_timer(&timerid, &it);
sleep(10);
exit(0);
}
else{
printf("I'm parent proc..\n");
printf("parent timer_Id addr = %d\n", timerid);
printf("pthread_id = %d\n", thread);
do {
ret = waitpid(pid, NULL, WNOHANG);
if (ret == 0){
printf("No child exit\n");
sleep(2);
}
else if (ret == pid){
printf("Successly get child %d\n", pid);
}
else
printf("something error\n");
}while(ret == 0);
/*ret = waitpid(pid, NULL, 0);*/
/*if (ret == pid)*/
/*printf("successly get child %d\n", pid);*/
}
pause();
return 0;
}
输出结果:
————————————————
版权声明:本文为CSDN博主「ppipp1109」的原创文章,遵循CC 4.0 BY-SA版权协议,转载请附上原文出处链接及本声明。
原文链接:https://blog.csdn.net/p942005405/article/details/100883568