Linux应用层的定时器
1.定时器理解:
可以将其看作一个闹钟。在Linux应用编程层上,当需要实现下列几种功能时,可考虑使用定时器。
- 周期性执行某一项任务
- 在指定时间去执行某一项任务
2.几种定时器介绍
- 具有定时功能的库函数API接口
1.sleep()
2.usleep()
3.nanosleep()
4.alarm()
sleep和usleep,Linux并没有提供系统调用,他们都是在库函数中实现的;是通过调用alarm()来设定报警时间,调用sigsuspend()将进程挂起在信号SIGALARM上。(如果不忽略或不捕捉该信号,该进程会被终止???)sleep精度是1秒,usleep精度是1微妙;使用这种方法缺点比较明显,在Linux系统中,sleep类函数不能保证精度,尤其在系统负载比较大时,一般都会有超时现象。nanosleep()则是Linux中的系统调用,它是使用定时器来实现的,该调用使调用进程睡眠;
alarm()也是通过定时器实现的,其精度只精确到秒级,它设置的定时器执行函数是在指定时间向当前进程发送SIGALRM信号;
在业务项目中,对于系统提供的定时器API往往很难满足我们的需求:
函数alarm本质上设置的是低精确、非重载的ITIMER_REAL类定时器,它只能精确到秒,并且每次设置只能产生一次定时。函数setitimer 设置的定时器则不同,它们不但可以计时到微妙(理论上),还能自动循环定时。在一个Unix进程中,不能同时使用alarm和ITIMER_REAL类定时器。
- 进程间隔定时器itimer
所谓“间隔定时器(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
itimer在进程中每种timer类型(ITIMER_REAL, ITIMER_PROF, ITIMER_VIRT)只能使用一个,另外一点就是他是基于signal进行超时提醒,不仅和alarm,sleep这些api冲突,而且在业务代码中signal是个很不可控的机制,尽量减少使用
头文件:
#include <sys/time.h>
结构体说明:
struct itimerval
{
/* Interval for periodic timer */
struct timeval it_interval;
/* Time until next expiration */
struct timeval it_value;
};
struct timeval
{
time_t tv_sec; /* seconds */
suseconds_t tv_usec; /* microseconds 1/1000000 seconds */
};
接口说明:
int getitimer(int which, struct itimerval *curr_value);
int setitimer(int which, const struct itimerval *new_value,struct itimerval *old_value);
- POSIX定时器(号称最强大的定时器接口来自POSIX时钟系列)
POSIX定时器的是为了解决间隔定时器itimer的以下问题
- 一个进程同一时刻只能有一个同一种类型(ITIMER_REAL, ITIMER_PROF, ITIMER_VIRT)的itimer。POSIX定时器在一个进程中可以创建任意多个timer
- itimer定时器到期后,只能通过信号(SIGALRM,SIGVTALRM,SIGPROF)的方式通知进程,POSIX定时器到期后不仅可以通过信号进行通知,还可以使用自定义信号,还可以通过启动一个线程来进行通知
- itimer支持us级别,POSIX定时器支持ns级别
头文件:
#include <time.h>
结构体说明:
struct sigevent
{
int sigev_notify; //设置定时器到期后的行为
int sigev_signo; //设置产生信号的信号码
union sigval sigev_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
}
接口说明:
- int timer_create(clockid_t clockid, struct sigevent *sevp,timer_t timerid)
功能: 创建定时器
描述:
函数 timer_create 会创建一个timer(每进程), 返回的timer id 在调用进程中是唯一的, 创建后的timer处于停止(disarmed)状态
| 参数 | 必选 | 类型 | 说明 |
| clock_id | ture | IN | 说明定时器是基于哪个时钟的,可有以下取值: |
| *sevp | true | IN | 设置了定时器到期时的通知方式和处理方式(结构体详细定义参见下方) |
| *timerid | true | OUT | 创建的timer的id 通过这个指针返回. |
| 返回值 | true | OUT | On success,return 0.On error,-1 is returned,and errno is set to indicate the error. |
如果sevp传入NULL,那么定时器到期会产生默认的信号,对CLOCK_REALTIMER来说,默认信号就是SIGALRM,如果要产生除默认信号之外的其他信号,程序必须将evp->sigev_signo设置为期望的信号码
- SIGEV_NONE:定时器到期后什么都不做,只提供通过timer_gettime和timer_getoverrun查询超时信息
- SIGEV_SIGNAL:定时器到期后,内核会将sigev_signo所指定的信号,传送给进程,在信号处理程序中,si_value会被设定为sigev_value的值
- SIGEV_THREAD:定时器到期后,内核会以sigev_notification_attributes为线程属性创建一个线程,线程的入口地址为sigev_notify_function,传入sigev_value作为一个参数
- int timer_settime(timer_t timerid, int flags,const struct itimerspec *new_value,struct itimerspec *old_value)
功能: 启动/停止或重置定时器
| 参数 | 必选 | 类型 | 说明 |
| timerid | true | IN | 指定的timer |
| flags | true | IN | 0 :new_value->it_value 表示希望timer首次到期时的时间与启动timer的时间间隔. |
| new_value | true | IN | new_value 有2个子域: it_value 和 it_interval |
| old_value | true | OUT | 取得上一次的设置的new_value |
| 返回值 | true | OUT | On success,return 0.On error,-1 is returned,and errno is set to indicate the error. |
- 自定义定时器
使用select()
使用gettimeofday、difftime
需要注意,当系统中的一个模块需要频繁的获取系统时间,使用linux中内置的函数开销过大,使用gettimeofday做定时器的弊端也就呈现。
参考:
https://www.jianshu.com/p/f367de55ab52
https://blog.csdn.net/weixin_30808253/article/details/96362124
示例:posix
#include <stdio.h>
#include "timer.h"
#include <signal.h>
#include <limits.h>
TimerEvent_t obj,obj1,obj2;
void handle(void * context)
{
TimerStop(&obj);
}
int main()
{
TimerInit(&obj,handle);
TimerSetValue(&obj,10);
TimerStart(&obj);
while(1);
return 0;
}
#include "timer.h"
#include <signal.h>
#include <time.h>
#include <sys/time.h>
void TimerInit( TimerEvent_t *obj, void ( *callback )( void *context ) )
{
int ret;
memset (&(obj->sevp), 0, sizeof (obj->sevp));
obj->sevp.sigev_value.sival_ptr = &(obj->timerid);
obj->sevp.sigev_notify = SIGEV_THREAD;
obj->sevp.sigev_notify_function = callback;
ret = timer_create(CLOCK_REALTIME, &(obj->sevp), &(obj->timerid));
printf("TimerInit----timerid:%d\n",obj->timerid);
if( ret == 0)
printf("timer_create\n");
}
void TimerStart( TimerEvent_t *obj )
{
int ret;
obj->new_value.it_interval.tv_sec = 0;
obj->new_value.it_interval.tv_nsec = 0;
ret = timer_settime(obj->timerid, CLOCK_REALTIME, &(obj->new_value), NULL);
// printf("TimerStart-----timerid:%d\n",obj->timerid);
// if( ret )
// printf("TimerStart\n");
}
void TimerStop( TimerEvent_t *obj )
{
int ret;
obj->new_value.it_interval.tv_sec = 0;
obj->new_value.it_interval.tv_nsec = 0;
obj->new_value.it_value.tv_sec = 0;
obj->new_value.it_value.tv_nsec = 0;
ret = timer_settime(&(obj->timerid), CLOCK_REALTIME, &(obj->new_value), NULL);
// if( ret )
// printf("TimerStop\n");
}

2292

被折叠的 条评论
为什么被折叠?



