草庐IT

Linux 定时器介绍

ENG八戒 2023-03-28 原文

以下内容为本人的著作,如需要转载,请声明原文链接 微信公众号「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_T
typedef 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;
}

有关Linux 定时器介绍的更多相关文章

  1. Unity 热更新技术 | (三) Lua语言基本介绍及下载安装 - 2

    ?博客主页:https://xiaoy.blog.csdn.net?本文由呆呆敲代码的小Y原创,首发于CSDN??学习专栏推荐:Unity系统学习专栏?游戏制作专栏推荐:游戏制作?Unity实战100例专栏推荐:Unity实战100例教程?欢迎点赞?收藏⭐留言?如有错误敬请指正!?未来很长,值得我们全力奔赴更美好的生活✨------------------❤️分割线❤️-------------------------

  2. ruby - Ruby 性能中的计时器 - 2

    我正在寻找一个用ruby​​演示计时器的在线示例,并发现了下面的代码。它按预期工作,但这个简单的程序使用30Mo内存(如Windows任务管理器中所示)和太多CPU有意义吗?非常感谢deftime_blockstart_time=Time.nowThread.new{yield}Time.now-start_timeenddefrepeat_every(seconds)whiletruedotime_spent=time_block{yield}#Tohandle-vesleepinteravalsleep(seconds-time_spent)iftime_spent

  3. 【Linux操作系统】——网络配置与SSH远程 - 2

    Linux操作系统——网络配置与SSH远程安装完VMware与系统后,需要进行网络配置。第一个目标为进行SSH连接,可以从本机到VMware进行文件传送,首先需要进行网络配置。1.下载远程软件首先需要先下载安装一款远程软件:FinalShell或者xhell7FinalShellxhell7FinalShell下载:Windows下载http://www.hostbuf.com/downloads/finalshell_install.exemacOS下载http://www.hostbuf.com/downloads/finalshell_install.pkg2.配置CentOS网络安装好

  4. Linux磁盘分区中物理卷(PV)、卷组(VG)、逻辑卷(LV)创建和(LVM)管理 - 2

    文章目录一基础定义二创建逻辑卷2-1准备物理设备2-2创建物理卷2-3创建卷组2-4创建逻辑卷2-5创建文件系统并挂载文件三扩展卷组和缩减卷组3-1准备物理设备3-2创建物理卷3-3扩展卷组3-4查看卷组的详细信息以验证3-5缩减卷组四扩展逻辑卷4-1检查卷组是否有可用的空间4-2扩展逻辑卷4-3扩展文件系统五删除逻辑卷5-1备份数据5-2卸载文件系统5-3删除逻辑卷5-4删除卷组5-5删除物理卷六LVM逻辑卷缩容6-1缩容注意事项6-2标准缩容步骤一基础定义LVM,LogicalVolumeManger,逻辑卷管理,Linux磁盘分区管理的一种机制,建立在硬盘和分区上的一个逻辑层,提高磁盘分

  5. H2数据库配置及相关使用方式一站式介绍(极为详细并整理官方文档) - 2

    目录H2数据库入门以及实际开发时的使用1.H2数据库的初识1.1H2数据库介绍1.2为什么要使用嵌入式数据库?1.3嵌入式数据库对比1.3.1性能对比1.4技术选型思考2.H2数据库实战2.1H2数据库下载搭建以及部署2.1.1H2数据库的下载2.1.2数据库启动2.1.2.1windows系统可以在bin目录下执行h2.bat2.1.2.2同理可以通过cmd直接使用命令进行启动:2.1.2.3启动后控制台页面:2.1.3spring整合H2数据库2.1.3.1引入依赖文件2.1.4数据库通过file模式实际保存数据的位置2.2H2数据库操作2.2.1Mysql兼容模式2.2.2Mysql模式

  6. ruby - 如何在 Ruby 中获取 linux 系统信息 - 2

    如何在Ruby中获取linux系统(这必须适用于Fedora、Ubuntu等)的软件/硬件信息? 最佳答案 Chef背后的优秀人才,拥有一颗名为Ohai的优秀gemhttps://github.com/opscode/ohai以散列形式返回系统信息,例如操作系统、内核、规范、fqdn、磁盘、空间、内存、用户、接口(interface)、sshkey等。它非常完整,非常好。它还会安装命令行二进制文件(也称为ohai)。 关于ruby-如何在Ruby中获取linux系统信息,我们在Stack

  7. ruby - rbenv:在 Linux Mint 上找不到 gem 命令 - 2

    我在LinuxMint17.2上。我最近使用apt-getpurgeruby​​删除了ruby​​。然后我安装了rbenv然后rbenvinstall2.3.0所以现在,~/.rbenv/versions/2.3.0/bin/ruby存在。但是现在,我无法执行geminstallrubocop。我明白了:$geminstallrubocoprbenv:gem:commandnotfoundThe`gem'commandexistsintheseRubyversions:2.3.0但是我可以~/.rbenv/versions/2.3.0/bin/geminstallrubocop。但是,

  8. ruby - 在 Linux 上编译 Ruby 1.9.2 所需的先决条件? - 2

    我是Ruby和RoR的新手。我有一个带有Ubuntu镜像的干净Linode实例,我想从源代码编译Ruby而不是使用apt-get。我已经在谷歌上搜索了执行此操作的说明,但经过一些尝试后,当我尝试运行一些教程示例时,我不断收到有关缺少zlib和其他一些包的错误。任何人都可以给我详细的说明(或链接),教我如何在从源代码编译Ruby之前安装必要的必备包吗?我的目的是编译Ruby的最新稳定版本,然后安装Rubygems和Rails。提前感谢您的帮助!!! 最佳答案 Thisblogpost涵盖从源代码编译ruby​​所需的包和安装过程;它引

  9. Linux网络编程必备的POSIX API的细节 - 2

    目录POSIXAPI大集合五元组三次握手的过程,内核协议栈分析listen函数DDOS攻击,洪水攻击DDOS攻击的应对措施数据发送 怎么保证顺序?如何保证包地顺序到达(序号+确认应答机制+重传)TCP断开连接的过程问题1.大量的CLOSE_WAIT+FIN_WAIT2是为啥?time_wait状态存在的原因?POSIXAPI大集合五元组(sip,sport,dip,dport,protocol)三次握手的过程,内核协议栈分析内核协议栈中是有内核数据结构的.  我们send/write数据,都是先发送到内核协议栈中,然后由内核协议栈封装发送到物理介质中传输到对端的对端的接收过程也是经有内核协议栈

  10. Linux export 命令及如何删除export设置的环境变量 - 2

    背景:Linuxexport命令用于设置或显示环境变量。在shell中执行程序时,shell会提供一组环境变量。export可新增,修改或删除环境变量,供后续执行的程序使用。export的效力仅限于该次登陆操作。语法:export[-fnp][变量名称]=[变量设置值]参数说明:-f 代表[变量名称]中为函数名称。-n 删除指定的变量。变量实际上并未删除,只是不会输出到后续指令的执行环境中。-p 列出所有的shell赋予程序的环境变量。实例:列出当前所有的环境变量#export-p//列出当前的环境变量值定义环境变量赋值#exportMYENV=7//定义环境变量并赋值添加环境变量:默认保存在

随机推荐