我曾以为像定时器这样基础的功能,操作系统会有一个完备的实现。当需要开启一个定时任务的时候,会有一个优雅的、如下形式的接口:
typedef void (*callback)(void*);
void setTimeout(unsigned int second,callback cb,void* arg);
可是事与愿违,Linux下不存在这样的接口。
文章相关视频讲解:
如何实现分布式定时器及核心原理:如何实现分布式定时器及核心原理
详解Linux网络编程相关的细节处理:详解Linux网络编程相关的细节处理
C/C++ Linux服务器开发高级架构学习视频点击:C/C++Linux服务器开发/Linux后台架构师-学习视频
定时器的实现原理
定时器的实现依赖的是CPU时钟中断,时钟中断的精度就决定定时器精度的极限。一个时钟中断源如何实现多个定时器呢?对于内核,简单来说就是用特定的数据结构管理众多的定时器,在时钟中断处理中判断哪些定时器超时,然后执行超时处理动作。而用户空间程序不直接感知CPU时钟中断,通过感知内核的信号、IO事件、调度,间接依赖时钟中断。用软件来实现动态定时器常用数据结构有:时间轮、最小堆和红黑树。下面就是一些知名的实现:
Hierarchy 时间轮算法:Linux内核
红黑树最小堆算法:Asio C++ Library或nginx
Linux上的定时函数
要想使用上面那样的定时器功能,我们必须利用Linux上现有的定时通知函数,封装一个定时器。Linux上的定时通知函数五花八门,要封装我们自己的定时器,首先需要选用一个定时通知的函数。查阅资料整理出了Linux上所有的定时函数,如下表:
Function |
Type |
Precision |
Remark |
sleep(3) |
unsigned int |
second |
|
usleep(3) |
useconds_t |
microsecond |
|
nanosleep(2) |
struct timespec |
nanosecond |
|
clock_nanosleep(2) |
struct timespec |
nanosecond |
It differs in allowing the caller to select the clock against which the sleep interval is to be measured, and in allowingthe sleep interval to be specified as either an absolute or a relative value. |
alarm(2) |
unsigned int |
second |
SIGALRM |
setitimer(2) |
struct itimerval |
microsecond |
SIGALRM |
timer_settime(2) |
struct itimerspec |
nanosecond |
notify method : struct sigevent |
Timerfd API |
File descriptor |
nanosecond |
From linux kernel 2.6.25 |
前四个函数比较鸡肋,会让调用线程挂起,原地等待定时器超时,否定。
alarm()和setitimer(),它们的通知机制采用了信号SIGALRM,由于SIGALRM信号不可靠,会造成超时通知不可靠,而且多线程中处理信号也是一个麻烦事,也不考虑。
timer_create()/timer_settime()系列函数是POSIX规定,精度达到纳秒级,提供了一个数据结构struct sigevent可以指定一个实时信号作为通知信号,同时也可以设置线程ID,将信号传递到指定的线程。相比前两个函数,有了不小的改进,可以作为一个备选的实现,但是可以预见到封装起来不会很轻松。此外使用此系列的函数,需要链接librt库。
事实上,我们遗漏掉了几个同样具有定时的功