Linux 定时器介绍

作者: ENG八戒 | 来源:发表于2022-07-31 21:52 被阅读0次

    以下内容为本人的著作,如需要转载,请声明原文链接 微信公众号「englyf」https://www.jianshu.com/p/0f2400fb1eb0


    曾经常去沙县小吃,就为了蹭上一碗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 定时器介绍

        本文链接:https://www.haomeiwen.com/subject/ytklwrtx.html