0.序言
在linux系统中定时器有分为软定时和硬件定时器。硬件定时器通常指的是CPU的一种底层寄存器,它负责根据固定时间频度形成中断讯号,产生讯号源。基于硬件提供的讯号源,系统就可以根据讯号中断来计数,计数在固定频度下对应固定的时间,按照预设的时间参数即可形成定时中断讯号,这就是软定时。
本文主要整理Linux系统开发中常使用的软定时器,而硬件定时器涉及到硬件指南这儿略过。
本文会在持续更新过程上将常用定时器逐一整理下来。
1.alarm()
#include
unsigned int alarm(unsigned int __seconds);
当时间抵达__seconds秒后,进程会遭到一个SIGALRM的讯号。当__seconds设置为0时linux 定时器 精度,当前的alarm定时器将退出。
返回值是一个无符号整型类型。返回之前闹铃的剩余秒数,假如之前未设闹铃则返回0。
注意:
可以通过函数signal注册该讯号的反弹处理函数callback_fun:
#include
typedef void (*sighandler_t)(int);
sig_t signal(int signum, sighandler_t handler);
举例:
#include
#include
#include
#include
/*闹钟信号处理函数*/
void sig_handler(int signal) {
printf("hello world: %dn", signal);
}
/*主函数*/
int main() {
int i;
signal(SIGALRM, sig_handler);
alarm(5);
for (i = 0; i < 8; i++)
{
printf(" sleep % d ... n", i);
sleep(1);
}
return 0;
}
该事例中,首先通过signale()捕捉SIGALRM讯号,并通过sig_handler()进行处理,接着通过函数alarm()注册了一个5s的定时器,之后通过一个for循环进行睡眠等待SIGALRM到来。当讯号SIGALRM到来后进程会转入sig_handler()处执行,执行完该函数后才能回到main()中继续执行。
执行结果如下:
sleep 0 ...
sleep 1 ...
sleep 2 ...
sleep 3 ...
sleep 4 ...
hello world: 14
sleep 5 ...
sleep 6 ...
sleep 7 ...
其他讯号相关的信息可以查看:《进程间通讯——信号(Signal)》
2.setitimer()
setitimer()类似于alarm(),同样是通过闹铃linux应用程序,只不过该函数可以精确到毫秒。
#include
int getitimer(int which, struct itimerval* current_value);
int setitimer(int which, const struct itimerval* new_value, struct itimerval* old_value);
参数:
返回时:
下边来看下数据结构itimerval:
#include
struct timeval {
time_t tv_sec;
suseconds_t tv_usec;
};
struct itimerval {
struct timeval it_interval;
struct timeval it_value;
};
假如it_value中两个值都为0,表示关掉定时器;假如it_value中起码一个不为0,则表示打开定时器;
假如it_interval中两个值都为0,表示定时器只执行1次;假如it_interval中起码一个不为0,则表示定时器是周期性工作;
将前面的实例进行简单的更改:
#include
#include
#include
#include
#include
/*闹钟信号处理函数*/
void sig_handler(int signal) {
printf("hello world: %dn", signal);
}
/*主函数*/
int main() {
int i;
signal(SIGALRM, sig_handler);
struct itimerval new_timer;
new_timer.it_interval.tv_sec = 1;
new_timer.it_interval.tv_usec = 0;
new_timer.it_value.tv_sec = 2;
new_timer.it_value.tv_usec = 0;
setitimer(ITIMER_REAL, &new_timer, NULL);
for (i = 0; i < 8; i++)
{
printf(" sleep % d ... n", i);
sleep(1);
}
return 0;
}
该事例中,首先通过signale()捕捉SIGALRM讯号,并通过sig_handler()进行处理,接着通过函数setitimer()注册了一个1s的定时器红旗linux6.0教程,2s后开始执行,此处与alarm()不同,alarm()指定的定时器触发时进行sig_handler()处理,处理完后回到main()中,而此处setitimer()设定完定时器的时间后就回到了main()函数中等待定时器触发。
执行结果如下:
sleep 0 ...
sleep 1 ...
hello world: 14
sleep 2 ...
hello world: 14
sleep 3 ...
hello world: 14
sleep 4 ...
hello world: 14
sleep 5 ...
hello world: 14
sleep 6 ...
hello world: 14
sleep 7 ...
hello world: 14
2s后定时器触发linux 定时器 精度,开始处理sig_handler()且每位1s触发一次,而main函数中for循环也会同步执行。
3.timer_create()
另外一种依赖讯号的定时器为timer_create(),较setitimer()愈发灵活,且时间可以精确到毫秒。
#include
int timer_create(clockid_t clockid, struct sigevent* event, timer_t* timer_ptr);
int timer_delete(timer_t timer);
int timer_settime(timer_t timer, int flags,
const struct itimerspec* new_value,
struct itimerspec* old_value);
int timer_gettime(timer_t timer, struct itimerspec* ts);
3.1timer_create()
timer_create()用以创建一个POSIX内部定时器,将定时器的标示ID储存到timer_ptr中。
参数:
注意:假如event被设置为NULL,相当于SIGEV_SIGNAL,讯号是SIGALRM;
返回值:
下边时sigevent数据结构:
#include
typedef struct sigevent {
sigval_t sigev_value;
int sigev_signo;
int sigev_notify;
union {
int _pad[SIGEV_PAD_SIZE];
int _tid;
struct {
void (*_function)(sigval_t);
void *_attribute; /* really pthread_attr_t */
} _sigev_thread;
} _sigev_un;
} sigevent_t;
3.2timer_settime()
timer_settime()用以启动或停止一个定时器。
参数:
返回值:
下边来看下数据结构itimerspec:
#include
struct timespec {
time_t tv_sec;
long tv_nsec;
};
struct itimerspec {
struct timespec it_interval; /*定时器的时间周期,interval*/
struct timespec it_value; /*定时器的时间值,timeout*/
};
假如it_value中两个值都为0,表示关掉定时器;假如it_value中起码一个不为0,则表示打开定时器;
假如it_interval中两个值都为0,表示定时器只执行1次;假如it_interval中起码一个不为0,则表示定时器是周期性工作;
3.3timer_delete()
用以删掉定时器。参数时timer_create()时创建的timer惟一标示;
3.4timer_gettime()
timer_gettime()用于查询timer对应定时器设定的当前时间值。
参数:
返回值:
3.5举例
#include
#include
#include
#include
#include
#include
#include
/*闹钟信号处理函数*/
void timer_process(int signal) {
printf("hello world: %dn", signal);
}
timer_t timer_;
bool create_timer(sigset_t *sigset) {
struct sigevent sevent;
sigemptyset(sigset);
sigaddset(sigset, SIGALRM);
if (sigprocmask(SIG_BLOCK, sigset, NULL)) {
printf("sigprocmask failed: %sn", strerror(errno));
return false;
}
sevent.sigev_notify = SIGEV_SIGNAL;
sevent.sigev_signo = SIGALRM;
if (timer_create(CLOCK_MONOTONIC, &sevent, &timer_)) {
printf("timer_create failed: %sn", strerror(errno));
return false;
}
return true;
}
bool start() {
struct itimerspec new_timer;
new_timer.it_value.tv_sec = 2;
new_timer.it_value.tv_nsec = 0;
new_timer.it_interval.tv_sec = 1;
new_timer.it_interval.tv_nsec = 0;
if (timer_settime(timer_, 0, &new_timer, NULL)) {
printf("timer_settime failed: %sn", strerror(errno));
return false;
}
return true;
}
/*主函数*/
int main() {
sigset_t sigset;
int signum;
if (!create_timer(&sigset)) {
printf("timer creation failed!n");
return 0;
}
start();
while (true) {
if (sigwait(&sigset, &signum) == -1) {
printf("sigwait failed: %sn", strerror(errno));
}
timer_process(signum);
}
return 0;
}
该事例核心处理有三个地方:
4.timerfd
这是以文件描述符的方式窃听时间变化,一般跟select/poll/epoll配合使用。timerfd涉及三个插口函数:
#include
int timerfd_create(clockid_t clockid, int flags);
int timerfd_settime(int fd, int flags,
const struct itimerspec* new_value,
struct itimerspec* old_value);
int timerfd_gettime(int fd, struct itimerspec* current_value);
4.1timerfd_create()
timerfd_create()用以创建一个定时器描述符。
参数:
返回值:
4.2timerfd_settime()
timerfd_settime()用以启动或停止一个定时器。
参数:
返回值:
下边来看下数据结构itimerspec:
#include
struct timespec {
time_t tv_sec;
long tv_nsec;
};
struct itimerspec {
struct timespec it_interval; /*定时器的时间周期,interval*/
struct timespec it_value; /*定时器的时间值,timeout*/
};
假如it_value中两个值都为0,表示关掉定时器;假如it_value中起码一个不为0,则表示打开定时器;
假如it_interval中两个值都为0,表示定时器只执行1次;假如it_interval中起码一个不为0,则表示定时器是周期性工作;
4.3timerfd_gettime()
timerfd_gettime()用于查询fd对应定时器设定的当前时间值。
参数:
返回值:
4.4read()和close()
timerfd归根就是一个文件描述符,当配合poll/epoll收到窃听定时器超时,须要通过read()读取文件描述符中的buffer,该buffer是一个无符号8bytes的整型数(uint64_t),表示该定时器超时的次数。假如没有超时,read()将会进行阻塞,阻塞到下一次定时器超时。另外,假如提供的buffer大小<8bytes,read()将返回EINVAL,read()成功则返回8.
在不须要定时器的时侯,记得通过close()进行关掉。
4.5举例
#include
#include
#include
#include
#include
#include
#include
#include
#define EPOLL_SIZE_HINT 128
int mEpollFd = -1;
int fd_process()
{
struct epoll_event eventItems[EPOLL_SIZE_HINT];
int eventCount = epoll_wait(mEpollFd, eventItems, EPOLL_SIZE_HINT, -1);
int timerFd = -1;
int eventIndex = 0;
uint64_t readCounter;
if (eventCount < 0) {
printf("Poll failed with an unexpected error: %sn", strerror(errno));
return -1;
}
for (; eventIndex < eventCount; ++eventIndex) {
timerFd = eventItems[eventIndex].data.fd;
int retRead = read(timerFd, &readCounter, sizeof(uint64_t));
if (retRead < 0) {
printf("read %d failed...n", timerFd);
continue;
} else {
printf("SUCCESS.....n");
}
}
return 0;
}
/*主函数*/
int main()
{
mEpollFd = epoll_create(EPOLL_SIZE_HINT);
int fd = timerfd_create(CLOCK_REALTIME, TFD_NONBLOCK);
if (fd < 0) {
printf("Could not create timer fd: %sn", strerror(errno));
return 0;
}
itimerspec timerSet;
timerSet.it_interval.tv_sec = 1;
timerSet.it_interval.tv_nsec = 0;
timerSet.it_value.tv_sec = 2;
timerSet.it_value.tv_nsec = 0;
if (timerfd_settime(fd, 0, &timerSet, NULL) != 0) {
printf("timerfd_settime failed: %sn", strerror(errno));
close(fd);
return 0;
}
struct epoll_event eventItem;
memset(&eventItem, 0, sizeof(epoll_event));
eventItem.events = EPOLLIN | EPOLLET;
eventItem.data.fd = fd;
int result = epoll_ctl(mEpollFd, EPOLL_CTL_ADD, fd, &eventItem);
if (result != 0) {
printf("Could not add timer fd(%d) to epoll instance: %sn", fd, strerror(errno));
}
while(true) {
if (fd_process() < 0)
break;
}
return 0;
}
在main()函数中创建了epoll,通过timerfd_create()创建了timerfd,并通过timerfd_settime()创建定时器,定时器的周期为1s,2s后工作。最后通过while循环等待定时器工作fd_process(),执行结果为:
SUCCESS.....
SUCCESS.....
SUCCESS.....
SUCCESS.....
SUCCESS.....
SUCCESS.....
SUCCESS.....
由于定时器周期为1s,所以每隔1s会通过read()读取到定时器超时。
详尽的epoll使用原理可以查看:《Linux中的epoll原理及使用》
参考: