Linux 定时器介绍

我过去经常去沙县吃零食,就是为了买一碗4元面条,里面有葱、油和酱油。我听旁边的几个男孩总是说

[En]

I used to go to Shaxian County for snacks just to get a bowl of 4 yuan Noodles Mixed with Scallion, Oil and Soy Sauce. I listened to some of the boys on the side always say

华仔,有软硬之分。

事实上,编写代码也有这种讲究。

[En]

In fact, writing code also has this kind of fastidiousness.

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

这里我们主要讨论软定时器,而硬件定时器参考硬件手册,跳过此处。

[En]

Here we mainly talk about soft timers, while hardware timers refer to the hardware manual and skip here.

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

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

#include <linux jiffies.h>
</linux>

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

[En]

High beat count can calculate higher time accuracy, but it will trigger system interruptions frequently and sacrifice system efficiency.

定义定时器

struct timer_list {
    struct list_head entry; // &#x5B9A;&#x65F6;&#x5668;&#x94FE;&#x8868;&#x7684;&#x5165;&#x53E3;
    unsigned long expires; // &#x5B9A;&#x65F6;&#x5668;&#x8D85;&#x65F6;&#x8282;&#x62CD;&#x6570;
    struct tvec_base *base; // &#x5B9A;&#x65F6;&#x5668;&#x5185;&#x90E8;&#x503C;&#xFF0C;&#x7528;&#x6237;&#x4E0D;&#x8981;&#x4F7F;&#x7528;
    void (*function)(unsigned long); // &#x5B9A;&#x65F6;&#x5904;&#x7406;&#x51FD;&#x6570;
    unsigned long data; // &#x8981;&#x4F20;&#x9012;&#x7ED9;&#x5B9A;&#x65F6;&#x5904;&#x7406;&#x51FD;&#x6570;&#x7684;&#x53C2;&#x6570;
    int slack;
};

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

初始化定时器

void init_timer(struct timer_list *timer);

将计时器注册到内核并启动它

[En]

Register the timer to the kernel and start it

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);

如果程序在多处理器上运行,则此函数等待其他处理器在此计时器上完成操作。此外,此函数不能在中断上下文中使用。

[En]

If the program is running on a multiprocessor, this function waits for other processors to complete the operation on this timer. In addition, this function cannot be used in an interrupt context.

修改定时值并启动定时器

int mod_timer(struct timer_list *timer, unsigned long expires);

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

2. 应用层的alarm闹钟

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

[En]

When developing in the application layer, it is very convenient to set alarm clock parameters and start alarm clock timer.

#include<unistd.h>

unsigned int alarm(unsigned int seconds);
</unistd.h>

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

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

#include <signal.h>

typedef void (*sig_t)(int);
sig_t signal(int signum, sig_t handler);
</signal.h>

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

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

创建定时器

#include <time.h>

int timer_create(clockid_t clock_id, struct sigevent *evp, timer_t *timerid)
</time.h>

通过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 */

设置定时器,如第一次触发时间、循环触发周期等,设置完成后启动定时器。

[En]

Set the timer, such as the first trigger time, the cycle of the loop trigger, etc. Start the timer when the setting is complete.

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);

如果在定时器超时后发送的相同信号被挂起和未处理,则在下一个超时发生后,前一个信号将丢失,这是定时器超时。

[En]

If the same signal sent after the timer timeout is suspended and unprocessed, then the previous signal will be lost after the next timeout occurs, which is the timer timeout.

删除定时器

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));
    // &#x5B9A;&#x65F6;&#x5668;&#x8D85;&#x65F6;&#x89E6;&#x53D1;&#x4FE1;&#x53F7; 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);
    }

    // &#x8BBE;&#x7F6E;&#x521D;&#x59CB;&#x89E6;&#x53D1;&#x65F6;&#x95F4;4&#x79D2;&#xFF0C;&#x4E4B;&#x540E;&#x6BCF;2&#x79D2;&#x518D;&#x6B21;&#x89E6;&#x53D1;
    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;
}
</unistd.h></string.h></signal.h></stdlib.h></time.h></stdio.h>

上面的代码中注册信号响应回调用了函数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;
}
</unistd.h></stdlib.h></string.h></time.h></signal.h></stdio.h>

Original: https://www.cnblogs.com/englyf/p/16651865.html
Author: englyf八戒
Title: Linux 定时器介绍

原创文章受到原创版权保护。转载请注明出处:https://www.johngo689.com/522757/

转载文章受原作者版权保护。转载请注明原作者出处!

(0)

大家都在看

亲爱的 Coder【最近整理,可免费获取】👉 最新必读书单  | 👏 面试题下载  | 🌎 免费的AI知识星球