linux timer


				
#include <sys/timerfd.h> 

 int timerfd_create(int 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 *curr_value);

这样,由于基于文件描述符,使得该接口可以支持 select(2),poll(2) 等异步接口,使得定时器的实现和使用更加的方便,更重要的是,支持 fork(2),exec(2) 这样多进程的语义,因此,可以用在多线程环境之中,它们的使用比 POSIX timer [ 2 ]更加的灵活,其根本原因在于定时器的管理统一到了 unix/linux 基本哲学之一 ---- “一切皆文件”之下。

 

//

 

 

timerfdLinux为用户程序提供的一个定时器接口。这个接口基于文件描述符,所以能够被用于select/poll的应用场景。

1.      使用方法

timerfd提供了如下接口供用户使用

timerfd_create

int timerfd_create(int clockid, int flags);

timerfd_create用于创建一个定时器文件。

参数clockid可以是CLOCK_MONOTONIC或者CLOCK_REALTIME

参数flags可以是0或者O_CLOEXEC/O_NONBLOCK

函数返回值是一个文件句柄fd

timerfd_settime

int timerfd_settime(int ufd, int flags, const struct itimerspec * utmr, struct itimerspec * otmr);

此函数用于设置新的超时时间,并开始计时。

参数ufdtimerfd_create返回的文件句柄。

参数flags1代表设置的是绝对时间;为0代表相对时间。

参数utmr为需要设置的时间。

参数otmr为定时器这次设置之前的超时时间。

函数返回0代表设置成功。

timerfd_gettime

int timerfd_gettime(int ufd, struct itimerspec * otmr);

此函数用于获得定时器距离下次超时还剩下的时间。如果调用时定时器已经到期,并且该定时器处于循环模式(设置超时时间时struct itimerspec::it_interval不为0),那么调用此函数之后定时器重新开始计时。

read

timerfd为阻塞方式时,read函数将被阻塞,直到定时器超时。

函数返回值大于0,代表定时器超时;否则,代表没有超时(被信号唤醒,等等)。

poll/close

pollclose与标准文件操作相同。

2.      内核实现

timerfd的内核实现代码在kernel/fs/timerfd.c,它的实现基于Linuxhrtimer

timerfd_create的实现

SYSCALL_DEFINE2(timerfd_create, int, clockid, int, flags)

l         做一些定时器的初始化工作

l         调用hrtimer_init初始化一个hrtimer

l         调用anon_inode_getfd分配一个dentry,并得到一个文件号fd,同时传入timerfd的文件操作指针struct file_operations timerfd_fopsanno_inode_getfd是文件系统anon_inodefs的一个帮助函数。anon文件系统比较简单,整个文件系统只有一个inode节点,其实现代码可以在fs/anon_inodes.c中找到。

timerfd_settime的实现

timerfd_settime最终会调用hrtimer_start启动定时器,其超时函数被设置为timerfd_tmrproc

timerfd_tmrproc

timefd_tmrproctimerfd的定时器超时函数。在timerfd超时时,该函数会设置定时器超时标记位;增加定时器超时次数(在设置定时器循环模式时,可能会出现多次超时没有被处理的情况);唤醒一个等待队列,从而唤醒可能存在的正被阻塞的readselect

timerfd_fops

static const struct file_operations timerfd_fops = {

       .release    = timerfd_release,

       .poll        = timerfd_poll,

       .read              = timerfd_read,

};

timerfd_read函数是文件操作read的内核实现,读到的是定时器的超时次数。该函数在阻塞模式下会把自身挂到timerfd的等待队列中,等待定时器超时时被唤醒。

timerfd_polltimerfd的等待队列登记到一个poll_table,从而在定时器超时时能唤醒select系统调用。

timerfd_release

timerfd_release函数释放timerfd_create函数中申请的资源,删除已分配的定时器。

 

 

 

=======================================================================

 

使用select来提供精确定时和休眠:

    int select(int n, fd_set *readfds, fd_set *writefds, fd_set *exceptfds,
    struct timeval *timeout);

    n指监视的文件描述符范围,通常设为所要select的fd+1,readfds,writefds和exceptfds分别是读,写和异常文件描述符集,timeout为超时时间。

    可能用到的关于文件描述符集操作的宏有:
    FD_CLR(int fd, fd_set *set); 清除fd
    FD_ISSET(int fd, fd_set *set); 测试fd是否设置
    FD_SET(int fd, fd_set *set); 设置fd
    FD_ZERO(fd_set *set); 清空描述符集


    我们此时用不到这些宏,因为我们并不关心文件描述符的状态,我们关心的是select超时。所以我们需要把readfds,writefds和exceptfds都设为NULL,只指定timeout时间就行了。至于n我们可以不关心,所以你可以把它设为任何非负值。实现代码如下:

 

    int msSleep(long ms) {

    struct timeval tv;

 

     tv.tv_sec = 0;

    tv.tv_usec = ms;

 

    return select(0, NULL, NULL, NULL, &tv);

    }

 

 

 

============================================================================

 

 

  #include <sys/timerfd.h>
       #include <time.h>
       #include <unistd.h>
       #include <stdlib.h>
       #include <stdio.h>
       #include <stdint.h>        /* Definition of uint64_t */

       #define handle_error(msg) /
               do { perror(msg); exit(EXIT_FAILURE); } while (0)

       static void
       print_elapsed_time(void)
       {
           static struct timespec start;
           struct timespec curr;
           static int first_call = 1;
           int secs, nsecs;

           if (first_call) {
               first_call = 0;
               if (clock_gettime(CLOCK_MONOTONIC, &start) == -1)
                   handle_error("clock_gettime");
           }

           if (clock_gettime(CLOCK_MONOTONIC, &curr) == -1)
               handle_error("clock_gettime");

           secs = curr.tv_sec - start.tv_sec;
           nsecs = curr.tv_nsec - start.tv_nsec;
           if (nsecs < 0) {
               secs--;
               nsecs += 1000000000;
           }
           printf("%d.%03d: ", secs, (nsecs + 500000) / 1000000);
       }

       int
       main(int argc, char *argv[])
       {
           struct itimerspec new_value;
           int max_exp, fd;
           struct timespec now;
           uint64_t exp, tot_exp;
           ssize_t s;

           if ((argc != 2) && (argc != 4)) {
               fprintf(stderr, "%s init-secs [interval-secs max-exp]/n",
                       argv[0]);
               exit(EXIT_FAILURE);
           }

           if (clock_gettime(CLOCK_REALTIME, &now) == -1)
               handle_error("clock_gettime");

           /* Create a CLOCK_REALTIME absolute timer with initial
              expiration and interval as specified in command line */

           new_value.it_value.tv_sec = now.tv_sec + atoi(argv[1]);
           new_value.it_value.tv_nsec = now.tv_nsec;
           if (argc == 2) {
               new_value.it_interval.tv_sec = 0;
               max_exp = 1;
           } else {
               new_value.it_interval.tv_sec = atoi(argv[2]);
               max_exp = atoi(argv[3]);
           }
           new_value.it_interval.tv_nsec = 0;

           fd = timerfd_create(CLOCK_REALTIME, 0);
           if (fd == -1)
               handle_error("timerfd_create");

           if (timerfd_settime(fd, TFD_TIMER_ABSTIME, &new_value, NULL) == -1)
               handle_error("timerfd_settime");

           print_elapsed_time();
           printf("timer started/n");

           for (tot_exp = 0; tot_exp < max_exp;) {
               s = read(fd, &exp, sizeof(uint64_t));
               if (s != sizeof(uint64_t))
                   handle_error("read");

               tot_exp += exp;
               print_elapsed_time();
               printf("read: %llu; total=%llu/n",
                       (unsigned long long) exp,
                       (unsigned long long) tot_exp);
           }

           exit(EXIT_SUCCESS);
       }

 

 

 

===================================================

 

Linux 时间函数

Linux 的计时函数,用于获得当前时间:

  • time(2) / time_t (秒)
  • ftime(3) / struct timeb (毫秒)
  • gettimeofday(2) / struct timeval (微秒)
  • clock_gettime(2) / struct timespec (纳秒)
  • gmtime / localtime / timegm / mktime / strftime / struct tm (这些与当前时间无关)

定时函数,用于让程序等待一段时间或安排计划任务:

  • sleep
  • alarm
  • usleep
  • nanosleep
  • clock_nanosleep
  • getitimer / setitimer
  • timer_create / timer_settime / timer_gettime / timer_delete
  • timerfd_create / timerfd_gettime / timerfd_settime

我的取舍如下:

  • (计时)只使用 gettimeofday 来获取当前时间。
  • (定时)只使用 timerfd_* 系列函数来处理定时。

gettimeofday 入选原因:(这也是 muduo::Timestamp class 的主要设计考虑)

  1. time 的精度太低,ftime 已被废弃,clock_gettime 精度最高,但是它系统调用的开销比 gettimeofday 大。
  2. 在 x86-64 平台上,gettimeofday 不是系统调用,而是在用户态实现的(搜 vsyscall),没有上下文切换和陷入内核的开销。
  3. gettimeofday 的分辨率 (resolution) 是 1 微秒,足以满足日常计时的需要。muduo::Timestamp 用一个 int64_t 来表示从 Epoch 到现在的微秒数,其范围可达上下 30 万年。

timerfd_* 入选的原因:

  1. sleep / alarm / usleep 在实现时有可能用了信号 SIGALRM,在多线程程序中处理信号是个相当麻烦的事情,应当尽量避免。(近期我会写一篇博客仔细讲讲“多线程、RAII、fork() 与信号”)
  2. nanosleep 和 clock_nanosleep 是线程安全的,但是在非阻塞网络编程中,绝对不能用让线程挂起的方式来等待一段时间,程序会失去响应。正确的做法是注册一个时间回调函数。
  3. getitimer 和 timer_create 也是用信号来 deliver 超时,在多线程程序中也会有麻烦。timer_create 可以指定信号的接收方是进程还是线程,算是一个进步,不过在信号处理函数(signal handler)能做的事情实在很受限。
  4. timerfd_create 把时间变成了一个文件描述符,该“文件”在定时器超时的那一刻变得可读,这样就能很方便地融入到 select/poll 框架中,用统一的方式来处理 IO 事件和超时事件,这也正是 Reactor 模式的长处。我在一年前发表的《Linux 新增系统调用的启示》中也谈到这个想法,现在我把这个想法在 muduo 网络库中实现了。
  5. 传统的 Reactor 利用 select/poll/epoll 的 timeout 来实现定时功能,但 poll 和 epoll 的定时精度只有毫秒,远低于 timerfd_settime 的定时精度。

必须要说明,在 Linux 这种非实时多任务操作系统中,在用户态实现完全精确可控的计时和定时是做不到的,因为当前任务可能会被随时切换出去,这在 CPU 负载大的时候尤为明显。但是,我们的程序可以尽量提高时间精度,必要的时候通过控制 CPU 负载来提高时间操作的可靠性,在程序在 99.99% 的时候都是按预期执行的。这或许比换用实时操作系统并重新编写并测试代码要经济一些。


 

 

  • 0
    点赞
  • 5
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值