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_create 创建定时器
- timer_delete 删除定时器
- timer_settime 启动/停止/重置 定时器
- timer_gettime 获得定时器的到期时间和间隔
- timer_getoverrun 获取超限次数(上次触发信号未处理挂起,则本次触发直接丢弃,称为超限)
几种不同的时间,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关闭它即可。
- timerfd_create 创建定时器
- timerfd_settime 设置定时器
- timerfd_gettime 获取定时器距离下次触发的事件
可以使用阻塞读、非阻塞读来从fd中读取定时器触发事件,也可也使用epoll监听这个 timerfd,示例代码如下:
1 | #include <iostream> |
本文链接:https://www.zoucz.com/blog/2022/06/20/17359a00-f0ad-11ec-9fa0-5dbc93f9d3ee/