3

linux kernel timer、posix timer 以及 timerfd

 2 years ago
source link: https://www.zoucz.com/blog/2022/06/20/17359a00-f0ad-11ec-9fa0-5dbc93f9d3ee/
Go to the source link to view the article. You can view the picture content, updated content and better typesetting reading experience. If the link is broken, please click the button below to view the snapshot at that time.
neoserver,ios ssh client

linux kernel timer、posix timer 以及 timerfd

作者: 邹成卓 2022-06-20 23:24:35 分类: linux

标签: linux

评论数:

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 原子时钟时间

示例代码:
我要说话

#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,示例代码如下: 我要说话

#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();
}

本文链接:https://www.zoucz.com/blog/2022/06/20/17359a00-f0ad-11ec-9fa0-5dbc93f9d3ee/我要说话

☞ 参与评论我要说话


About Joyk


Aggregate valuable and interesting links.
Joyk means Joy of geeK