2.1 常见定时功能的API:sleep系列
在介绍itimer和POSIX定时器之前,我们先看看我们经常遇到过具有定时功能的库函数API接口:
alarm()sleep()usleep()nanosleep()
alarm:
alarm()函数可以设置一个定时器,在特定时间超时,并产生SIGALRM信号,如果不忽略或不捕捉该信号,该进程会被终止 。
#include <unistd.h>unsigned int alarm(unsigned int seconds);return:0或到期剩余秒数
那么alarm在是如何实现的?Glibc中alarm是基于间隔定时器itimer来实现的(文章后面会说到itimer是基于hrtimer实现的) 。如下alarm在库函数下的实现,alarm调用了setitimer系统调用:
unsigned int alarm (seconds)unsigned int seconds;{...if (__setitimer (ITIMER_REAL, &new, &old) < 0)return 0;...}libc_hidden_def (alarm)
sleep:
sleep和alarm的功能类似,不过sleep会让进程挂起,在定时器超时内核会产生SIGALRM信号,如果不忽略或不捕捉该信号,该进程会被终止 。
那么sleep是如何实现的?Glibc的sleep实现如下:可见其实调用alarm实现的,在alarm的基础上注册了SIGALRM信号处理函数,用于在定时器到期时,捕获到信号,回到睡眠的地方 。所以其实可以看出sleep就是对alarm的特化 。
unsigned int__sleep (unsigned int seconds){...struct sigaction act, oact;...//注册信号回调函数act.sa_handler = sleep_handler;act.sa_flags = 0;act.sa_mask = oset;if (sigaction (SIGALRM, &act, &oact) < 0)return seconds;...//调用alarm API进行操作remaining = alarm (seconds);}weak_alias (__sleep, sleep)
usleep:
usleep支持精度更高的微妙级别的定时操作,
int usleep (useconds_t useconds){struct timespec ts = { .tv_sec = (long int) (useconds / 1000000),.tv_nsec = (long int) (useconds % 1000000) * 1000ul };/* Note the usleep() is a cancellation point.But since we callnanosleep() which itself is a cancellation point we do not haveto do anything here.*/return __nanosleep (&ts, NULL);}
Bsd的usleep实现如下:
int usleep (useconds)useconds_t useconds;{struct timeval delay;delay.tv_sec = 0;delay.tv_usec = useconds;return __select (0, (fd_set *) NULL, (fd_set *) NULL, (fd_set *) NULL,&delay);}
nanosleep:
nanosleep()glibc的API是直接调用linux内核的nanosleep,内核的nanosleep采用了hrtimer进行实现 。
alarm(), sleep()系列,以及后面的间隔定时器itimer都是基于SIGALRM信号进行触发的 。所以它们是不能同时使用的 。
2.2 间隔定时器itimer
间隔定时器的接口如下:
#include <sys/time.h>int getitimer(int which, struct itimerval *curr_value);int setitimer(int which, const struct itimerval *new_value,struct itimerval *old_value);结构定义:struct itimerval {struct timeval it_interval; /* next value :间隔时间*/struct timeval it_value;/* current value:到期时间*/};struct timeval {time_ttv_sec;/* seconds */s
可以通过调用上面两个API接口来设置和获取间隔定时器信息 。系统为每个进程提供了3种itimer,每种定时器在不同时间域递减,当定时器超时,就会向进程发送一个信号,并且重置定时器 。3种定时器的类型,如下表所示:
![Linux内核时钟系统和定时器实现](http://img.jiangsulong.com/220430/2349195432-1.jpg)
文章插图
表1 参数which与定时器类型
在Linux 2.6.16 之前,itimer的实现是基于内核定时器timer wheel封装成的定时器接口 。内核封装的定时器接口是提供给其他内核模块使用,也是其他定时器的基础 。itimer通过内核定时器的封装,生成提供给用户层使用的接口setitimer和getitimer 。内核定时器timer wheel提供的内核态调用接口为:可参考
add_timer() del_timer() init_timer()
在Linux 2.6.16 以来,itimer不再采用基于timer wheel的内核定时器进行实现 。而是换成了高精度的内核定时器hrtimer进行实现 。如果定时器超时时,进程是处于运行状态,那么超时的信号会被立刻传送给该进程,否则信号会被延迟传送,延迟时间要根据系统的负载情况 。所以这里有一个BUG:当系统负载很重的情况下,对于ITIMER_REAL定时器有可能在上一次超时信号传递完成前再次超时,这样就会导致第二次超时的信号丢失 。
每个进程中同一种定时器只能使用一次 。
该系统调用在POSIX.1-2001中定义了,但在POSIX.1-2008中已被废弃 。所以建议使用POSIX定时器API(timer_gettime, timer_settime)代替 。
函数alarm本质上设置的是低精确、非重载的ITIMER_REAL类定时器,它只能精确到秒,并且每次设置只能产生一次定时 。函数setitimer 设置的定时器则不同,它们不但可以计时到微妙(理论上),还能自动循环定时 。在一个Unix进程中,不能同时使用alarm和ITIMER_REAL类定时器 。
推荐阅读
- linux之秘钥登录
- Linux抄袭Unix?官司打了18年,IBM赔了9200万后,全剧终
- linux之lscpu命令
- 怎样在 Linux 终端下使用 dd 命令创建一个临场 USB 驱动器
- Linux安装调试Nginx
- linux安装安全狗
- linux高性能服务器开发十大必须掌握的核心技术
- linux之chroot命令
- Linux中su,sudo,sudo su,sudo -i命令的使用和区别
- 北京|北京5月3日起核酸检测对市民免费:乘车须持7日内核酸阴性证明