我是靠谱客的博主 健壮背包,最近开发中收集的这篇文章主要介绍Linux中的几种定时器0. 前言1. alarm()2. setitimer()3. timer_create()4. timerfd,觉得挺不错的,现在分享给大家,希望可以做个参考。

概述

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: %dn", 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: %dn", 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: %dn", 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: %sn", strerror(errno));
        return false;
    }

    sevent.sigev_notify = SIGEV_SIGNAL;
    sevent.sigev_signo = SIGALRM;
    if (timer_create(CLOCK_MONOTONIC, &sevent, &timer_)) {
        printf("timer_create failed: %sn", 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: %sn", 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: %sn", 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: %sn", 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: %sn", 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: %sn", 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: %sn", 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

最后

以上就是健壮背包为你收集整理的Linux中的几种定时器0. 前言1. alarm()2. setitimer()3. timer_create()4. timerfd的全部内容,希望文章能够帮你解决Linux中的几种定时器0. 前言1. alarm()2. setitimer()3. timer_create()4. timerfd所遇到的程序开发问题。

如果觉得靠谱客网站的内容还不错,欢迎将靠谱客网站推荐给程序员好友。

本图文内容来源于网友提供,作为学习参考使用,或来自网络收集整理,版权属于原作者所有。
点赞(43)

评论列表共有 0 条评论

立即
投稿
返回
顶部