目录
setitimer函数实现定时器
分析:实现定时器,通过itimerval结构体以及函数setitimer产生的信号,系统随之使用signal信号处理函数来处理产生的定时信号,从而实现定时器。
itimerval结构体
先看itimerval的结构体
struct itimerval
{
/* Value to put into `it_value' when the timer expires. */
//启动定时器后,每次触发定时器周期时间值
struct timeval it_interval;
/* Time to the next timer expiration. */
//程序跑到这之后,多久启动定时器
struct timeval it_value;
};
struct timeval
{
__time_t tv_sec; /* Seconds. */
__suseconds_t tv_usec; /* Microseconds. */
};
setitimer函数设置定时方式
setitimer()将value指向的结构体设为计时器的当前值,如果第三个参数oldvalue不是NULL(一般都设为NULL),将返回计时器原有值。
int setitimer (__itimer_which_t __which,
const struct itimerval *__restrict __new,
struct itimerval *__restrict __old)
which:三种类型
- ITIMER_REAL //内核宏定义为0,计时器的值实时递减,减完则发送信号SIGALRM(常用)。
- ITIMER_VIRTUAL //内核宏定义为1,进程执行时递减计时器的值,发送的信号是SIGVTALRM(了解)。
- ITIMER_PROF //内核宏定义为2,进程和系统执行时都递减计时器的值,发送的信号是SIGPROF(了解)。
settimer工作机制
-
先对it_value倒计时,当it_value为零时触发信号。然后重置为it_interval。继续对it_value倒计时。一直这样循环下去。
-
基于此机制。setitimer既能够用来延时运行,也可定时运行。
-
如果it_value为0,不会触发信号,所以要能触发信号,it_value得大于0;
-
如果it_interval为0,仅仅会延时。不会定时(也就是说仅仅会触发一次信号)。
-
old_value參数,通经常使用不上。设置为NULL,它是用来存储上一次setitimer调用时设置的new_value值。
signal信号处理
这边需要捕获对应的信号并进行逻辑处理 ,这个就像单片机的中断处理函数。
signal(SIGALRM,signal_handler);
返回说明:
成功执行时,返回0。失败返回-1
实现代码(1s后开启定时器,然后每隔1s打印hello)
/*该代码实现的功能是: 1s后开启定时器,然后每隔1s向终端打印hello*/
#include <stdio.h>
#include <sys/time.h>
#include <stdlib.h>
#include <signal.h>
static int i;
void signal_handler(int signum)
{
i++;
if(i == 2000){//0.5ms溢出一次,一共溢出了2000次,就是1s
printf("hello\n");
i = 0;
}
}
int main()
{
struct itimerval itv;
//1、设定定时时间:0.5ms
itv.it_interval.tv_sec = 0;
itv.it_interval.tv_usec = 500;
//2、多久启动定时器:1s后启动
itv.it_value.tv_sec = 1;
itv.it_value.tv_usec = 0;
//3、设定定时方式
if( -1 == setitimer(ITIMER_REAL, &itv, NULL)){
perror("error");
exit(-1);
}
//4、信号处理
signal(SIGALRM,signal_handler);
while(1);
return 0;
}
timer_create函数实现定时器(可以传参,可以删除定时器)
参考:linux定时器
timer_create创建定时器函数(Sigevent结构体)
int timer_create(clockid_t clock_id, struct sigevent *evp, timer_t *timerid)
成功创建返回0,失败返回-1,并更新错误码。参数中timerid 是创建成功的timer id,另两个参数则相对复杂:
参数 clock_id :(可选值如下)
CLOCK_REALTIME: 系统保存的时间,比如当前是10点10分,我们起了一个10min的定时器,5min后,我们将系统时间修改成10点10分,定时器还会再过10min到时。
CLOCK_MONOTONIC: Timer严格按照设定的时间定时,无法通过修改时间改变;
CLOCK_PROCESS_CPUTIME_ID: 计时器只记录当前进程所实际花费的时间;比如当前进程只能获得50%的 CPU 时间,为了让进程真正地运行 10 分钟,到10 点 30 分Timer才 到期。
CLOCK_THREAD_CPUTIME_ID: 以线程为计时实体,当前进程中的某个线程真正地运行了一定时间才触发 Timer
Sigevent结构体
struct sigevent {
int sigev_notify;
int sigev_signo;
union sigval sigev_value;
void (*sigev_notify_function) (union sigval);
void *sigev_notify_attributes;
pid_t sigev_notify_thread_id;
};
SIGEV_NONE: 到期时不产生通知;
SIGEV_SIGNAL: 到期时将给进程投递一个信号sigev_signo可以用来指定使用什么信号;
SIGEV_THREAD: 定时器到期时将启动新的线程进行处理,此种情况下需要设置 sigev_notify_function。
当 Timer 到期时,将使用该函数作为入口启动一个线程来处理信号;sigev_value保存了传入 sigev_notify_funct的参数。
sigev_notify_attributes:
如果非空,则应该是一个指向 pthread_attr_t 的指针,用来设置线程的属性
(比如 stack 大小,detach 状态等);
SIGEV_THREAD_ID:到期时将向指定线程发送信号,通常和 SIGEV_SIGNAL 联合使用,这样当
Timer 到期时,系统会向由 sigev_notify_thread_id 指定的线程发
送信号,否则可能进程中的任意线程都可能收到该信号
timer_settime启动定时器函数(itimespec结构体)
itimespec结构体
int timer_settime(timer_t timerid, int flags, const struct itimerspec *new_value,
struct itimerspec * old_value);
struct itimespec{
struct timespec it_interval;
struct timespec it_value;
};
struct timespec{
time_t tv_sec; //s
long tv_nsec; //ns
};
参数 timerid: 是调用timer_create函数成功创建的timerid
参数 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 : 配置定时器的时间参数
参数 * old_value : 获取上一次配置的定时器参数
timer_delete删除定时器函数
int timer_delete (timer_t timerid);
实现代码()
gcc 1.c -lrt//编译链接库 实时库(real time):shm_open系列
#include <stdio.h>
#include <signal.h>
#include <time.h>
#include <sys/time.h>
void tm_timeout(union sigval value)
{ //传递参数
printf("create time sifval:%X\n", value.sival_int);
}
void timeout1(int sig)
{
time_t times=0; //获取到当前时间
printf("tick time:%ld\n", time(×) );
}
void create_time_by_thread(void)
{
static timer_t times; //time id
struct itimerspec ts; //用于配置定时器时间
struct sigevent evp;
evp.sigev_notify = SIGEV_THREAD; //定时器到期时将启动新的线程进行处理
evp.sigev_notify_function = tm_timeout; //线程函数地址
evp.sigev_value.sival_int = 0xFEDC; //传递参数给tm_timeout函数
evp.sigev_notify_attributes = NULL;
timer_create(CLOCK_REALTIME, &evp, ×);
if(1)
{
//第一次调用后,每次调用间隔时间,若不需要周期性启动定时器可将下面两个参数设置值为0
ts.it_interval.tv_sec = 3;
ts.it_interval.tv_nsec = 0;
//第一次调用时间,即程序跑到这多久后启动定时器
ts.it_value.tv_sec =7;
ts.it_value.tv_nsec = 0;
if(0 != timer_settime(times, 0, &ts, NULL)) //3s调用一次tm_timeout
{
printf("create timer to start failed\n");
}
}
else
{
struct timespec now; //获取linux系统时间
clock_gettime(CLOCK_REALTIME, &now);
ts.it_value.tv_sec =now.tv_sec + 7;
ts.it_value.tv_nsec = now.tv_nsec + 0;
if(0 != timer_settime(times, TIMER_ABSTIME, &ts, NULL)) //绝对时间启动定时器
{
printf("create timer to start failed\n");
}
}
}
void create_timer_by_signal(void)
{
static timer_t times;
struct itimerspec ts; //用于配置定时器时间
struct timespec now;//获取linux系统时间
struct sigevent evp;
evp.sigev_notify = SIGEV_SIGNAL;
evp.sigev_signo = SIGUSR1;
evp.sigev_value.sival_ptr = ×
evp.sigev_notify_attributes = NULL;
timer_create(CLOCK_REALTIME, &evp, ×);
/*第一次调用后,每次调用间隔时间*/
ts.it_interval.tv_sec = 3;
ts.it_interval.tv_nsec = 0;
/*第一次调用时间,即程序跑到这多久后启动定时器*/
ts.it_value.tv_sec =5;
ts.it_value.tv_nsec = 0;
signal(SIGUSR1, timeout1);
timer_settime(times, 0, &ts, NULL);
}
int main(int argc, char *argv[])
{
create_time_by_thread();
create_timer_by_signal();
while(1);
return 0;
}