linux kernel timer

init_timer 4.14 Linux前使用
timer_setup 4.14 Linux内核开始
我这边开发环境都是用docker搭建的,docker容器与宿主机共享内核,而我的宿主机版太低了,安装了 linux-kernel-devel 也无法找到 <linux/timer.h>,暂时不看内核的timer,后续有空搭建环境了再补上。

内核定时器简介
内核定时器实现机制

posix timer

linux内核提供了基于posix标准实现的定时器,主要涉及到的函数有:

几种不同的时间,timer支持其中的一部分:

  • CLOCK_REALTIME 系统实时时间,如果修改了系统时间,这个值会变
  • CLOCK_MONOTONIC 从系统启动这一刻起开始计时,不受系统时间被用户改变的影响
  • CLOCK_PROCESS_CPUTIME_ID 进程所消耗的用户和系统CPU时间。被调度出去则停止计时。
  • CLOCK_THREAD_CPUTIME_ID 线程所消耗的用户和系统CPU时间。被调度出去则停止计时。
  • CLOCK_MONOTONIC_RAW 和 CLOCK_MONOTONIC类似,单提供了对纯基于硬件时间的访问,不受NTP调整影响。只能读取时钟,不支持timer。
  • CLOCK_REALTIME_COARSE 类似于CLOCK_REALTIME,适用于以最小代价获取较低时间戳的程序,不会引发对硬件时钟的访问,返回值分辨率为jiffy。只能读取时钟,不支持timer。
  • CLOCK_MONOTONIC_COARSE 类似于CLOCK_MONOTONIC,适用于以最小代价获取较低时间戳的程序,不会引发对硬件时钟的访问,返回值分辨率为jiffy。只能读取时钟,不支持timer。
  • CLOCK_BOOTTIME 和CLOCK_MONOTONIC类似,但是累积suspend时间。
  • CLOCK_REALTIME_ALARM 借助RTC设备的唤醒计时
  • CLOCK_BOOTTIME_ALARM 借助RTC设备的唤醒计时
  • CLOCK_TAI 原子时钟时间

示例代码:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
#include <unistd.h>
#include <iostream>
#include <signal.h>
#include <time.h>

//通过信号 handler 触发
void signal_function_timer(int signalNum)
{

printf("get signal num: %u\n", signalNum);
}

//通过线程触发
void thread_function_timer(sigval_t t){
printf("get signal num by thread: %u\n", t.sival_int);
}

//设置为信号触发
void setup_evp_by_signal(sigevent &evp, timer_t& timer){
evp.sigev_value.sival_ptr = &timer;
//通过信号的方式触发,还可以通过线程的方式触发 SIGEV_THREAD、SIGEV_THREAD_ID
evp.sigev_notify = SIGEV_SIGNAL;
evp.sigev_signo = SIGUSR1;
//为信号绑定处理函数
signal(SIGUSR1, signal_function_timer);
}

//设置为线程触发,注意编译时需要链接上 -lpthread
void setup_evp_by_thread(sigevent &evp, timer_t& timer){
evp.sigev_value.sival_ptr = &timer;
evp.sigev_value.sival_int = SIGUSR1;
evp.sigev_notify = SIGEV_THREAD;
evp.sigev_notify_function = thread_function_timer;
//可以通过sigev_notify_attributes设置新建线程的参数,此结构体创建方式见:
//https://man7.org/linux/man-pages/man3/pthread_attr_init.3.html
//注意,这里即使不用,也要设置为一个空指针,否则不能触发
evp.sigev_notify_attributes = nullptr;

}

int main(){
//step1.定义posix定时器指针变量
timer_t timer;

//step2.创建 signal event
struct sigevent evp;
//通过signal触发
//setup_evp_by_signal(evp, timer);
setup_evp_by_thread(evp, timer);

//step3.以操作系统启动时间创建一个定时器
int ret = timer_create(CLOCK_MONOTONIC, &evp, &timer);
if( ret ) perror("timer_create error");

//step4.获取系统启动时间的高精度时间值
struct timespec spec;
clock_gettime(CLOCK_MONOTONIC, &spec);
//定时器相关时间配置
struct itimerspec time_value;
//每3秒触发一次
time_value.it_interval.tv_sec = 3;
time_value.it_interval.tv_nsec = 0;
//3秒后启动定时器
time_value.it_value.tv_sec = spec.tv_sec + 3;
time_value.it_value.tv_nsec = spec.tv_nsec + 0;
//step5.启动定时器
ret = timer_settime(timer, CLOCK_MONOTONIC, &time_value, NULL);
if( ret ) perror("timer_settime error");
while(1)
{
//读取一下还剩余多长时间触发定时器
itimerspec its;
ret = timer_gettime(timer, &its);
if( ret ) perror("timer_gettime error");
printf("get itimerspec left seconds: %u, it_interval: %u \n", its.it_value.tv_sec, its.it_interval.tv_sec);
//如果有之前的信号未处理,当前信号触发后会丢弃,称为 over run,这里会返回数字
int overRunNum = timer_getoverrun(timer);
printf("overRunNum: %u\n", overRunNum);
usleep(500*1000);
}
}

timerfd

可以以fd的形式提供一个文件描述符,通过read读取timeout事件,当不需要这个定时器时,用close关闭它即可。

可以使用阻塞读、非阻塞读来从fd中读取定时器触发事件,也可也使用epoll监听这个 timerfd,示例代码如下:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
#include <iostream>
#include <sys/timerfd.h>
#include <sys/epoll.h>
#include <unistd.h>

void ReadBlock(){
//step1.以操作系统启动时间创建一个定时器fd
int tfd = timerfd_create(CLOCK_MONOTONIC, 0);
if (tfd == -1) perror("timerfd_create");
//step2.为timerfd设置超时信息
itimerspec time_value;
//每3秒触发一次
time_value.it_interval.tv_sec = 3;
time_value.it_interval.tv_nsec = 0;
//设置启动时间,3s后启动
timespec t;
clock_gettime(CLOCK_MONOTONIC, &t);
time_value.it_value.tv_sec = t.tv_sec + 3;
time_value.it_value.tv_nsec = t.tv_nsec;
int ret = timerfd_settime(tfd, TFD_TIMER_ABSTIME, &time_value, NULL);
if(ret == -1) perror("timerfd_settime");
//step3.read读取超时次数,如果未超时则进入阻塞状态
uint64_t count;
while (true)
{
ret = read(tfd, &count, sizeof(count));
if(ret == -1){
perror("read timerfd");
break;
}
printf("read timeout count: %u\n", count);
}
}

void ReadNonblockByEpoll(){
//step1.以操作系统启动时间创建一个定时器fd,非阻塞模式
int tfd = timerfd_create(CLOCK_MONOTONIC, TFD_NONBLOCK);
if (tfd == -1) perror("timerfd_create");
//step2.为timerfd设置超时信息
itimerspec time_value;
//每3秒触发一次
time_value.it_interval.tv_sec = 3;
time_value.it_interval.tv_nsec = 0;
//设置启动时间,3s后启动
timespec t;
clock_gettime(CLOCK_MONOTONIC, &t);
time_value.it_value.tv_sec = t.tv_sec + 3;
time_value.it_value.tv_nsec = t.tv_nsec;
int ret = timerfd_settime(tfd, TFD_TIMER_ABSTIME, &time_value, NULL);
if(ret == -1) perror("timerfd_settime");
//step3.通过 epoll 监听 timerfd
uint64_t count;
int efd = epoll_create(10);
epoll_event ev;
ev.events = EPOLLIN;
ev.data.fd = tfd;
ret = epoll_ctl(efd, EPOLL_CTL_ADD, tfd, &ev);
if(ret ==-1) perror("epoll_ctl add");
epoll_event evs[1];
while (true)
{
ret = epoll_wait(efd, evs, 1, -1);
if(ret == -1) perror("epoll_wait");
if(ret == 0) continue;
ret = read(tfd, &count, sizeof(count));
printf("epoll get count: %u\n", count);
}
}

int main(){
//ReadBlock();
ReadNonblockByEpoll();
}

☞ 参与评论