写点什么

Linux 定时器介绍

作者:englyf
  • 2022-12-03
    广东
  • 本文字数:3165 字

    阅读完需:约 10 分钟

Linux 定时器介绍

以下内容为本人的著作,如需要转载,请声明原文链接 微信公众号「englyf」https://mp.weixin.qq.com/s/YZTaGkKDRXzE7sMcrfxS-w




曾经常去沙县小吃,就为了蹭上一碗 4 块钱的葱油拌面,听着边上的几位小哥老说


华仔,有软硬之分。
复制代码


其实写代码也有这种讲究。




在 linux 系统中定时器有分为软定时和硬件定时器,硬件定时器一般指的是 CPU 的一种底层寄存器,它负责按照固定时间频率产生中断信号,形成信号源。基于硬件提供的信号源,系统就可以按照信号中断来计数,计数在固定频率下对应固定的时间,根据预设的时间参数即可产生定时中断信号,这就是软定时。


这里主要讲软定时器,而硬件定时器涉及到硬件手册这里略过。

1. 利用内核节拍器相关定时器实现定时

linux 内核有可调节的系统节拍,由于节拍依据硬件定时器的定时中断计数得来,节拍频率设定后,节拍周期恒定,根据节拍数可以推得精确时间。从系统启动以来记录的节拍数存放在全局变量 jiffies 中,系统启动时自动设置 jiffies 为 0。


#include <linux/jiffies.h>
复制代码


高节拍数可以计算更高的时间精度,但是会频繁触发系统中断,牺牲系统效率。


定义定时器


struct timer_list {    struct list_head entry; // 定时器链表的入口    unsigned long expires; // 定时器超时节拍数    struct tvec_base *base; // 定时器内部值,用户不要使用    void (*function)(unsigned long); // 定时处理函数    unsigned long data; // 要传递给定时处理函数的参数    int slack;};
复制代码


设置节拍数 expires 时,可以使用函数 msecs_to_jiffies 将毫秒值转化为节拍数。


初始化定时器


void init_timer(struct timer_list *timer);
复制代码


注册定时器到内核,并启动


void add_timer(struct timer_list *timer);
复制代码


删除定时器


int del_timer(struct timer_list *timer);
复制代码


如果程序运行在多核处理器上,此函数有可能导致运行出错,建议改用 del_timer_sync。


同步删除定时器


int del_timer_sync(struct timer_list *timer);
复制代码


如果程序运行在多处理器上,此函数会等待其它处理器对此定时器的操作完成。另外,此函数不能用在中断上下文中。


修改定时值并启动定时器


int mod_timer(struct timer_list *timer, unsigned long expires);
复制代码


注意:在应用层开发过程中,一般不会使用内核的函数来设定定时器。

2. 应用层的 alarm 闹钟

在应用层开发时,设置闹钟参数,并启动闹钟定时器非常方便


#include<unistd.h>
unsigned int alarm(unsigned int seconds);
复制代码


注意:每个进程只允许设置一个闹钟,重复设置会覆盖前一个闹钟。


当时间到达 seconds 秒后,会有 SIGALRM 信号发送给当前进程,可以通过函数 signal 注册该信号的回调处理函数 callback_fun


#include <signal.h>
typedef void (*sig_t)(int);sig_t signal(int signum, sig_t handler);
复制代码

3. 利用 POSIX 中内置的定时器接口

设定闹钟适用的情形比较简单,而为了更灵活地使用定时功能,可以用到 POSIX 中的定时器功能。


创建定时器


#include <time.h>
int timer_create(clockid_t clock_id, struct sigevent *evp, timer_t *timerid)
复制代码


通过 clock_id 可以指定时钟源,evp 传入超时通知配置参数,timerid 返回被创建的定时器的 id。evp 如果为 NULL,超时触发时,默认发送信号 SIGALRM 通知进程。


clock_id 是枚举值,如下


CLOCK_REALTIME :Systemwide realtime clock.CLOCK_MONOTONIC:Represents monotonic time. Cannot be set.CLOCK_PROCESS_CPUTIME_ID :High resolution per-process timer.CLOCK_THREAD_CPUTIME_ID :Thread-specific timer.CLOCK_REALTIME_HR :High resolution version of CLOCK_REALTIME.CLOCK_MONOTONIC_HR :High resolution version of CLOCK_MONOTONIC.
复制代码


结构体 sigevent


union sigval{    int sival_int; //integer value    void *sival_ptr; //pointer value}
struct sigevent{ int sigev_notify; //notification type int sigev_signo; //signal number union sigval sigev_value; //signal value void (*sigev_notify_function)(union sigval); pthread_attr_t *sigev_notify_attributes;}
复制代码


类型 timer_t


#ifndef _TIMER_T#define _TIMER_Ttypedef int timer_t; /* timer identifier type */#endif /* ifndef _TIMER_T */
复制代码


设置定时器,比如初次触发时间,循环触发的周期等。设置完成后启动定时器。


int timer_settime(timer_t timerid, int flags, const struct itimerspec *value, struct itimerspec *ovalue);
struct timespec{ time_t tv_sec; long tv_nsec; };
struct itimerspec { struct timespec it_interval; struct timespec it_value; };
复制代码


获取定时剩余时间


int timer_gettime(timer_t timerid, struct itimerspec *value);
复制代码


获取定时器超限的次数


int timer_getoverrun(timer_t timerid);
复制代码


定时器超时后发送的同一个信号如果挂起未处理,那么在下次超时发生后,上一个信号会丢失,这就是定时器的超限。


删除定时器


int timer_delete (timer_t timerid);
复制代码


示例:超时触发信号


#include <stdio.h>#include <time.h>#include <stdlib.h>#include <signal.h>#include <string.h>#include <unistd.h>
void sig_handler(int signo){ time_t t; char str[32];
time(&t); strftime(str, sizeof(str), "%T", localtime(&t));
printf("handler %s::%d\n", str, signo);}
int main(){ struct sigaction act; memset(&act, 0, sizeof(act)); act.sa_handler = sig_handler; act.sa_flags = 0;
sigemptyset(&act.sa_mask); if (sigaction(SIGUSR1, &act, NULL) == -1) { perror("fail to sigaction"); exit(-1); }
timer_t timerid; struct sigevent evp; memset(&evp, 0, sizeof(evp)); // 定时器超时触发信号 SIGUSR1 evp.sigev_notify = SIGEV_SIGNAL; evp.sigev_signo = SIGUSR1; if (timer_create(CLOCK_REALTIME, &evp, &timerid) == -1) { perror("fail to timer_create"); exit(-1); }
// 设置初始触发时间4秒,之后每2秒再次触发 struct itimerspec its; its.it_value.tv_sec = 4; its.it_value.tv_nsec = 0; its.it_interval.tv_sec = 2; its.it_interval.tv_nsec = 0; if (timer_settime(timerid, 0, &its, 0) == -1) { perror("fail to timer_settime"); exit(-1); }
while(1); return 0;}
复制代码


上面的代码中注册信号响应回调用了函数 sigaction,其实这里用函数 signal 也可以的。


示例:超时启动子线程


#include <stdio.h>#include <signal.h>#include <time.h>#include <string.h>#include <stdlib.h>#include <unistd.h>
void timer_thread(union sigval v){ time_t t; char str[32];
time(&t); strftime(str, sizeof(str), "%T", localtime(&t));
printf("timer_thread %s::%d\n", str, v.sival_int);}
int main(){ timer_t timerid; struct sigevent evp; memset(&evp, 0, sizeof(evp)); evp.sigev_notify = SIGEV_THREAD; evp.sigev_value.sival_int = 123; evp.sigev_notify_function = timer_thread; if (timer_create(CLOCK_REALTIME, &evp, &timerid) == -1) { perror("fail to timer_create"); exit(-1); }
struct itimerspec its; its.it_value.tv_sec = 4; its.it_value.tv_nsec = 0; its.it_interval.tv_sec = 2; its.it_interval.tv_nsec = 0; if (timer_settime(timerid, 0, &its, 0) == -1) { perror("fail to timer_settime"); exit(-1); }
while(1); return 0;}
复制代码


发布于: 刚刚阅读数: 3
用户头像

englyf

关注

我的微信公众号 englyf 2018-06-01 加入

欢迎关注我的微信公众号 englyf 一起交流学习,每周至少更新一篇各类原创技术笔记,闲来也听我嗑唠嗑唠……

评论

发布
暂无评论
Linux 定时器介绍_c_englyf_InfoQ写作社区