Linux 中的几种定时器

这篇具有很好参考价值的文章主要介绍了Linux 中的几种定时器。希望对大家有所帮助。如果存在错误或未考虑完全的地方,请大家不吝赐教,您也可以点击"举报违法"按钮提交疑问。

0. 前言

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

本文主要整理 Linux 系统开发中常使用的软定时器,而硬件定时器涉及到硬件手册这里略过。

本文会在持续更新过程中将常用定时器逐一整理出来。

1. alarm()


#include <unistd.h>
unsigned int alarm(unsigned int __seconds);

当时间到达 __seconds秒后,进程会受到一个 SIGALRM 的信号。当 __seconds 设置为0时,当前的 alarm定时器将退出。

返回值是一个无符号整型类型。返回之前闹钟的剩余秒数,如果之前未设闹钟则返回0。

注意:

  • 每个进程只允许设置一个闹钟,重复设置会覆盖前一个闹钟;

  • 当经过指定的 __seconds 之后,信号由内核产生;

可以通过函数signal注册该信号的回调处理函数callback_fun:


#include <signal.h>

typedef void (*sighandler_t)(int);
sig_t signal(int signum, sighandler_t handler);

举例:


#include <stdlib.h>
#include <stdio.h>
#include <unistd.h>
#include <signal.h>

/*闹钟信号处理函数*/
void sig_handler(int signal) {
 
    printf("hello world: %d\n", signal);
}
 
/*主函数*/
int main() {
    int i;
    signal(SIGALRM, sig_handler);

    alarm(5);

    for (i = 0; i < 8; i++)
    {
        printf(" sleep % d ... \n", i);
        sleep(1);
    }

    return 0;
}

该例子中,首先通过 signale() 捕捉 SIGALRM 信号,并通过 sig_handler() 进行处理,接着通过函数 alarm() 注册了一个 5s 的定时器,然后通过一个 for 循环进行睡眠等待 SIGALRM 到来。当信号 SIGALRM 到来后进程会转到sig_handler() 处执行,执行完该函数后才会回到 main() 中继续执行。

执行结果如下:


 sleep  0 ...
 sleep  1 ...
 sleep  2 ...
 sleep  3 ...
 sleep  4 ...
hello world: 14
 sleep  5 ...
 sleep  6 ...
 sleep  7 ...

其他信号相关的信息可以查看:《进程间通信——信号(Signal)》

2. setitimer()

setitimer() 类似于 alarm(),同样是通过闹钟,只不过该函数可以精确到微秒。


#include <sys/time.h>

int getitimer(int which, struct itimerval* current_value);
int setitimer(int which, const struct itimerval* new_value, struct itimerval* old_value);

参数:

  • which 定时器计时的方式:

  • ITIMER_REAL: 真实时间,时间到达时发送 SIGALRM;

  • ITIMER_VIRTUAL: 用户时间,时间到达时发送 SIGVTALRM;

  • ITIMER_PROF: 以该进程在用户态和内核态下所消耗的时间来计算,时间到达时发送 SIGPROF信号;

  • new_value 定时器新的定时值;

  • old_value 定时器旧的定时值,最开始设置为 NULL;

返回时:

  • 设置成功时,返回0;

  • 设置失败时,返回-1,并设置 errno;

下面来看下数据结构 itimerval:


#include <linux/time.h>

struct timeval {
    time_t tv_sec;
    suseconds_t tv_usec;
};

struct itimerval {
    struct timeval it_interval;
    struct timeval it_value;
};

如果it_value 中两个值都为0,表示关闭定时器;如果it_value 中至少一个不为0,则表示打开定时器;

如果it_interval中两个值都为0,表示定时器只执行1次;如果 it_interval 中至少一个不为0,则表示定时器是周期性工作;

将上面的实例进行简单的修改:


#include <stdlib.h>
#include <stdio.h>
#include <unistd.h>
#include <signal.h>
#include <sys/time.h>

/*闹钟信号处理函数*/
void sig_handler(int signal) {
 
    printf("hello world: %d\n", signal);
}
 
/*主函数*/
int main() {
    int i;
    signal(SIGALRM, sig_handler);

    struct itimerval new_timer;
    new_timer.it_interval.tv_sec = 1;
    new_timer.it_interval.tv_usec = 0;
    new_timer.it_value.tv_sec = 2;
    new_timer.it_value.tv_usec = 0;
    setitimer(ITIMER_REAL, &new_timer, NULL);

    for (i = 0; i < 8; i++)
    {
        printf(" sleep % d ... \n", i);
        sleep(1);
    }

    return 0;
}

该例子中,首先通过 signale() 捕捉 SIGALRM 信号,并通过 sig_handler() 进行处理,接着通过函数 setitimer() 注册了一个 1s 的定时器,2s后开始执行,此处与 alarm() 不同,alarm() 指定的定时器触发时进行 sig_handler() 处理,处理完后回到 main() 中,而此处 setitimer() 设定完定时器的时间后就回到了 main() 函数中等待定时器触发。

执行结果如下:


 sleep  0 ...
 sleep  1 ...
hello world: 14
 sleep  2 ...
hello world: 14
 sleep  3 ...
hello world: 14
 sleep  4 ...
hello world: 14
 sleep  5 ...
hello world: 14
 sleep  6 ...
hello world: 14
 sleep  7 ...
hello world: 14

2s 后定时器触发,开始处理 sig_handler() 且每个 1s 触发一次,而main函数中 for 循环也会同步执行。

3. timer_create()

另外一种依赖信号的定时器为 timer_create(),较 setitimer() 更加灵活,且时间可以精确到纳秒。


#include <time.h>

int timer_create(clockid_t clockid, struct sigevent* event, timer_t* timer_ptr);
int timer_delete(timer_t timer);

int timer_settime(timer_t timer, int flags,
                       const struct itimerspec* new_value,
                       struct itimerspec* old_value);

int timer_gettime(timer_t timer, struct itimerspec* ts);

3.1 timer_create()

timer_create() 用以创建一个 POSIX 内部定时器,将定时器的标识 ID 存放到 timer_ptr 中。

参数:

  • clockid定义了定时器计时的方法,有如下几个值 (定义在 linux/time.h 中):

  • CLOCK_REALTIME : 可设置的系统范围的实时时钟;

  • CLOCK_MONOTONIC : 单调递增的时钟,系统启动后不会被改变;

  • CLOCK_PROCESS_CPUTIME_ID : 用于测量当前进程(包括所有线程)CPU占用时间,包含用户调用和系统调用;

  • CLOCK_THREAD_CPUTIME_ID : 用于测量当前线程CPU占用时间,包含用户调用和系统调用;

  • event 指出该如何通知调用者定时器超时信息,详细的数据结构如下。

  • sigev_notify 指定定时器超时处理的方式;

  • SIGEV_NONE : 定时器超时后不使用异步通知,可能的情况是使用timer_gettime来监控定时器;

  • SIGEV_SIGNAL : 一旦超时,产生一个信号,任何时候,至多只有一个信号会发送到队列里面,可以使用timer_getoverrun来获取超时次数;

  • SIGEV_THREAD : 新建一个线程去处理,该线程执行sigev_notif_function为入口函数;

  • SIGEV_THREAD_ID : linux独有,发出一个信号,和SIG_NAL类似,只不过该信号发送到指定的线程,如果 sigev_notify 设置该值时,需要同时指定 _sigev_un._tid 的值,例如使用 gettid();

  • sigev_signo 用以指定定时器超时时发出的信号的值;例如当sigev_notify 指定 SIGEV_THREAD_ID时,需要有信号发出进行处理,此时 sigev_signo 可以设定 SIGALRM

  • timer_ptr 定时器的标识 ID;

注意:如果event被设置为NULL,相当于SIGEV_SIGNAL,信号是SIGALRM;

返回值:

  • 创建成功时,返回0;

  • 创建失败时,返回-1,并设置 errno;

下面时 sigevent 数据结构:


#include <uapi/asm-generic/siginfo.h>

typedef struct sigevent {
    sigval_t sigev_value;
    int sigev_signo;
    int sigev_notify;
    union {
        int _pad[SIGEV_PAD_SIZE];
         int _tid;

        struct {
            void (*_function)(sigval_t);
            void *_attribute;    /* really pthread_attr_t */
        } _sigev_thread;
    } _sigev_un;
} sigevent_t;

3.2 timer_settime()

timer_settime() 用以启动或停止一个定时器。

参数:

  • timer,由 timer_create() 创建,为定时器的唯一标识;

  • flags,设置定时器时间标识

  • 0,启动一个相对定时器,基于当前时间 + 指定的 new_value;

  • TFD_TIMER_ABSTIME,使用绝对时间的定时器,由参数 new_value 决定;

  • TFD_TIMER_CANCEL_ON_SET,如果实时时钟发生改变,退出绝对时间定时器;

  • new_value,定时器新的定时值,根据flags 不同有不同的含义;

  • old_value,定时器旧的定时值,最开始设置为NULL,如果为非NULL,表示之前设置过;

返回值:

  • 创建成功时,返回0;

  • 创建失败时,返回-1,并设置 errno;

下面来看下数据结构 itimerspec:


#include <uapi/linux/time.h>

struct timespec {
    time_t tv_sec;
    long   tv_nsec;
};

struct itimerspec {
    struct timespec it_interval;  /*定时器的时间周期,interval*/
    struct timespec it_value;     /*定时器的时间值,timeout*/
};

如果it_value 中两个值都为0,表示关闭定时器;如果it_value 中至少一个不为0,则表示打开定时器;

如果it_interval中两个值都为0,表示定时器只执行1次;如果 it_interval 中至少一个不为0,则表示定时器是周期性工作;

3.3 timer_delete()

用以删除定时器。参数时 timer_create() 时创建的 timer 唯一标识;

3.4 timer_gettime()

timer_gettime() 用于查询 timer 对应定时器设定的当前时间值。

参数:

  • timer,由 timer_create() 创建,为定时器的唯一标识;

  • ts,返回的当前时间值;

返回值:

  • 查询成功时,返回0;

  • 查询失败时,返回-1,并设置 errno;

3.5 举例


#include <stdlib.h>
#include <stdio.h>
#include <unistd.h>
#include <signal.h>
#include <time.h>
#include <errno.h>
#include <string.h>

/*闹钟信号处理函数*/
void timer_process(int signal) {
 
    printf("hello world: %d\n", signal);
}

timer_t timer_;

bool create_timer(sigset_t *sigset) {
    struct sigevent sevent;

    sigemptyset(sigset);
    sigaddset(sigset, SIGALRM);
    if (sigprocmask(SIG_BLOCK, sigset, NULL)) {
        printf("sigprocmask failed: %s\n", strerror(errno));
        return false;
    }

    sevent.sigev_notify = SIGEV_SIGNAL;
    sevent.sigev_signo = SIGALRM;
    if (timer_create(CLOCK_MONOTONIC, &sevent, &timer_)) {
        printf("timer_create failed: %s\n", strerror(errno));
        return false;
    }

    return true;
}

bool start() {
    struct itimerspec new_timer;


    new_timer.it_value.tv_sec = 2;
    new_timer.it_value.tv_nsec = 0;
    new_timer.it_interval.tv_sec = 1;
    new_timer.it_interval.tv_nsec = 0;

    if (timer_settime(timer_, 0, &new_timer, NULL)) {
        printf("timer_settime failed: %s\n", strerror(errno));
        return false;
    }

    return true;
}

/*主函数*/
int main() {
    sigset_t sigset;
    int signum;
    
    if (!create_timer(&sigset)) {
        printf("timer creation failed!\n");
        return 0;
    }
    
    start();
    
    while (true) {
        if (sigwait(&sigset, &signum) == -1) {
            printf("sigwait failed: %s\n", strerror(errno));
        }

        timer_process(signum);
    }

    return 0;
}

该例子核心处理有三个地方:

  • create_timer() 封装了创建 timer 的过程,这里使用信号集指定 SIGALRM 信号,接着使用 timer_create() 创建定时器;

  • start() 封装了定时器时间的设定,主要是调用 timer_settime() 启动定时器;

  • while() 循环利用sigwait() 进行 SIGALRM 等待,如果捕获到信号,则会调用 timer_process() 进行处理流程;

4. timerfd

这是以文件描述符的形式监听时间变化,通常跟select/poll/epoll 配合使用。timerfd 涉及三个接口函数:


#include <sys/timerfd.h>

int timerfd_create(clockid_t clockid, int flags);

int timerfd_settime(int fd, int flags,
                        const struct itimerspec* new_value,
                        struct itimerspec* old_value);

int timerfd_gettime(int fd, struct itimerspec* current_value);

4.1 timerfd_create()

timerfd_create() 用以创建一个定时器描述符。

参数:

  • clockid定义了定时器计时的方法,有如下几个值 (定义在 linux/time.h 中):

  • CLOCK_REALTIME : 可设置的系统范围的实时时钟;

  • CLOCK_MONOTONIC : 单调递增的时钟,系统启动后不会被改变;

  • CLOCK_PROCESS_CPUTIME_ID : 用于测量当前进程(包括所有线程)CPU占用时间,包含用户调用和系统调用;

  • CLOCK_THREAD_CPUTIME_ID : 用于测量当前线程CPU占用时间,包含用户调用和系统调用;

  • flags 描述符创建标识

  • TFD_NONBLOCK 以非阻塞形式打开描述符,节约额外调用 fcntl() 函数;

  • TFD_CLOEXEC 为新打开的描述符设置 close-on-exec 选项,在 fork + exec后新进程自动关闭该 fd。同样的可以节约额外 open() 调用;

返回值:

  • 创建成功时,返回新的 fd;

  • 创建失败时,返回-1,并设置 errno;

4.2 timerfd_settime()

timerfd_settime() 用以启动或停止一个定时器。

参数:

  • fd,由 timerfd_create() 创建,为定时器的文件描述符;

  • flags,设置定时器时间标识

  • 0,启动一个相对定时器,基于当前时间 + 指定的 new_value;

  • TFD_TIMER_ABSTIME,使用绝对时间的定时器,由参数 new_value 决定;

  • TFD_TIMER_CANCEL_ON_SET,如果实时时钟发生改变,退出绝对时间定时器;

  • new_value,定时器新的定时值,根据flags 不同有不同的含义;

  • old_value,定时器旧的定时值,最开始设置为NULL,如果为非NULL,表示之前设置过;

返回值:

  • 创建成功时,返回0;

  • 创建失败时,返回-1,并设置 errno;

下面来看下数据结构 itimerspec:


#include <uapi/linux/time.h>

struct timespec {
    time_t tv_sec;
    long   tv_nsec;
};

struct itimerspec {
    struct timespec it_interval;  /*定时器的时间周期,interval*/
    struct timespec it_value;     /*定时器的时间值,timeout*/
};

如果it_value 中两个值都为0,表示关闭定时器;如果it_value 中至少一个不为0,则表示打开定时器;

如果it_interval中两个值都为0,表示定时器只执行1次;如果 it_interval 中至少一个不为0,则表示定时器是周期性工作;

4.3 timerfd_gettime()

timerfd_gettime() 用于查询 fd对应定时器设定的当前时间值。

参数:

  • fd,由 timerfd_create() 创建,为定时器的文件描述符;

  • current_value,返回的当前时间值;

返回值:

  • 查询成功时,返回0;

  • 查询失败时,返回-1,并设置 errno;

4.4 read() 和 close()

timerfd 归根就是一个文件描述符,当配合 poll/epoll 收到监听定时器超时,需要通过read() 读取文件描述符中的buffer,该buffer 是一个无符号 8bytes 的整型数(uint64_t),表示该定时器超时的次数。如果没有超时,read() 将会进行阻塞,阻塞到下一次定时器超时。另外,如果提供的buffer 大小 < 8bytes,read() 将返回EINVAL,read()成功则返回 8.

在不需要定时器的时候,记得通过 close() 进行关闭。

4.5 举例


#include <stdlib.h>
#include <stdio.h>
#include <errno.h>
#include <string.h>
#include <sys/eventfd.h>
#include <sys/timerfd.h>
#include <sys/epoll.h>
#include <unistd.h>

#define EPOLL_SIZE_HINT    128

int mEpollFd = -1;

int fd_process()
{
    struct epoll_event eventItems[EPOLL_SIZE_HINT];
    int eventCount = epoll_wait(mEpollFd, eventItems, EPOLL_SIZE_HINT, -1);
        
    int timerFd = -1;
    int eventIndex = 0;
    uint64_t readCounter;

    if (eventCount < 0) {
        printf("Poll failed with an unexpected error: %s\n", strerror(errno));
        return -1;
    }

    for (; eventIndex < eventCount; ++eventIndex) {
        timerFd = eventItems[eventIndex].data.fd;

        int retRead = read(timerFd, &readCounter, sizeof(uint64_t));
        if (retRead < 0) {
            printf("read %d failed...\n", timerFd);

            continue;
        } else {
            printf("SUCCESS.....\n");
        }
    }
    
    return 0;
}

/*主函数*/
int main()
{
    mEpollFd = epoll_create(EPOLL_SIZE_HINT);
    
    int fd = timerfd_create(CLOCK_REALTIME, TFD_NONBLOCK);
    if (fd < 0) {
        printf("Could not create timer fd: %s\n", strerror(errno));
        return 0;
    }

    itimerspec timerSet;
    timerSet.it_interval.tv_sec = 1;
    timerSet.it_interval.tv_nsec = 0;
    timerSet.it_value.tv_sec = 2;
    timerSet.it_value.tv_nsec = 0;
    if (timerfd_settime(fd, 0, &timerSet, NULL) != 0) {
        printf("timerfd_settime failed: %s\n", strerror(errno));
        close(fd);
        return 0;
    }

    struct epoll_event eventItem;
    memset(&eventItem, 0, sizeof(epoll_event));
    eventItem.events = EPOLLIN | EPOLLET;
    eventItem.data.fd = fd;
    int result = epoll_ctl(mEpollFd, EPOLL_CTL_ADD, fd, &eventItem);
    if (result != 0) {
        printf("Could not add timer fd(%d) to epoll instance: %s\n", fd, strerror(errno));
    }
    
    while(true) {
        if (fd_process() < 0)
            break;
    }

    return 0;
}

在main() 函数中创建了epoll,通过 timerfd_create() 创建了 timerfd,并通过 timerfd_settime() 创建定时器,定时器的周期为1s,2s 后工作。最后通过 while 循环等待定时器工作 fd_process(),执行结果为:


SUCCESS.....
SUCCESS.....
SUCCESS.....
SUCCESS.....
SUCCESS.....
SUCCESS.....
SUCCESS.....

因为定时器周期为 1s,所以每隔 1s 会通过 read() 读取到定时器超时。

详细的 epoll 使用原理可以查看:《Linux 中的 epoll 原理及使用》

参考:

http://t.zoukankan.com/houjun-p-4885148.html文章来源地址https://www.toymoban.com/news/detail-754923.html

到了这里,关于Linux 中的几种定时器的文章就介绍完了。如果您还想了解更多内容,请在右上角搜索TOY模板网以前的文章或继续浏览下面的相关文章,希望大家以后多多支持TOY模板网!

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处: 如若内容造成侵权/违法违规/事实不符,请点击违法举报进行投诉反馈,一经查实,立即删除!

领支付宝红包 赞助服务器费用

相关文章

  • 09_Linux内核定时器

    目录 Linux时间管理和内核定时器简介 内核定时器简介 Linux内核短延时函数 定时器驱动程序编写 编写测试APP 运行测试          学习过UCOS或FreeRTOS的同学应该知道, UCOS或FreeRTOS是需要一个硬件定时器提供系统时钟, 一般使用Systick作为系统时钟源。同理 , Linux要运行 , 也是需

    2024年02月13日
    浏览(41)
  • Linux内核 -高精度定时器

    高精度定时器使用示例

    2024年01月19日
    浏览(54)
  • linux驱动开发 - 08_内核定时器

    链接: C/C++Linux服务器开发/后台架构师【零声教育】-学习视频教程-腾讯课堂 1.1 内核时间管理简介 Linux 内核中有大量的函数需要时间管理,比如周期性的调度程序、延时程序、对于驱动编写者来说最常用的定时器。 硬件定时器提供时钟源,时钟源的频率可以设置, 设置好以

    2024年02月02日
    浏览(34)
  • Linux 内核定时器(高级字符设备五)

      在 Linux 内核中很多函数是基于定时器进行驱动的,但是内核定时器的精度并不高,所以不能作为高精度定时器使用。并且内核定时器的运行没有周期性,到达计时终点后会自动关闭。如果要实现周期性定时,就要在定时处理函数中重新开启定时器。   Linux 内核中使用

    2024年02月08日
    浏览(42)
  • <Linux开发>驱动开发 -之-内核定时器与中断

    <Linux开发>驱动开发 -之-内核定时器与中断 交叉编译环境搭建: <Linux开发> linux开发工具-之-交叉编译环境搭建 uboot移植可参考以下: <Linux开发> -之-系统移植 uboot移植过程详细记录(第一部分) <Linux开发> -之-系统移植 uboot移植过程详细记录(第二部分) <Linux开

    2024年02月08日
    浏览(48)
  • 【Linux 裸机篇(八)】I.MX6U EPIT 定时器中断、定时器按键消抖

    EPIT 的全称是: Enhanced Periodic Interrupt Timer,直译过来就是增强的周期中断定时器,它主要是完成周期性中断定时的。学过 STM32 的话应该知道, STM32 里面的定时器还有很多其它的功能,比如输入捕获、 PWM 输出等等。但是 I.MX6U 的 EPIT 定时器只是完成周期性中断定时的,仅此一

    2024年02月02日
    浏览(64)
  • Linux学习第21天:Linux内核定时器驱动开发: 流淌的时间长河

    Linux版本号4.1.15   芯片I.MX6ULL                                    大叔学Linux    品人间百味  思文短情长           在人类的发展进化中,时间是一个非常重要神秘的物质量。任何事物都是在时间的长河中流淌发生、发展、变化。我们进行驱动开发中对时间的定义和使

    2024年02月07日
    浏览(42)
  • [驱动开发]Linux内核定时器与中断的简单应用

    首先介绍一下定时器原理。 在linux系统中定时器有分为软定时和硬件定时器。 以海思某款芯片为例,定时器模块又称为Timer模块,主要实现定时、计数功能。 Timer 具有以下特点: 带可编程 8 位预分频器的 32bit/16bit 减法定时器/计数器。 Timer 的计数时钟为 3MHz 时钟。 支持 3 种

    2024年02月20日
    浏览(42)
  • linux下实现定时器的三种简单方式

    目录 一. sleep()和usleep() 1.sleep() 2.usleep() 3.毫秒级延时 二. signal与alarm() 三. select 四. 一些总结         优点是简单便捷,直接调用即可,但是缺点也很明显,精度不够,特别是在系统负载比较大时,会发生超时现象。 1.sleep() #include unistd.h unsigned int sleep(unsigned int   secon

    2024年02月16日
    浏览(45)
  • RK3399平台开发系列讲解(基础篇)Linux 传统间隔定时器

    🚀返回专栏总目录 沉淀、分享、成长,让自己和他人都能有所收获!😄 📢本篇将详细 介绍 Linux 传统间隔定时器。 Linux 的传统间隔定时器设置接口是 setitimer,它可以设定在未来某个时

    2023年04月11日
    浏览(42)

觉得文章有用就打赏一下文章作者

支付宝扫一扫打赏

博客赞助

微信扫一扫打赏

请作者喝杯咖啡吧~博客赞助

支付宝扫一扫领取红包,优惠每天领

二维码1

领取红包

二维码2

领红包