精华内容
下载资源
问答
  • 一个非常简单易用的linux下的C语言的定时器封装接口,使用查询方式,可同时使用不限个数的定时器,还附带有详细的使用demo。
  • Linux常用的时间相关函数 获取当前时间: time(0) ------ time_t ------ 秒 gettimeofday ------ timeval ------ 微妙 可以认为这两个函数都是线程安全的,获取到的时间戳是不包括闰秒的 获取当地时间: localtime_r...
    Linux常用的时间相关函数

    获取当前时间:
    time(0) ------ time_t ------ 秒
    gettimeofday ------ timeval ------ 微妙
    可以认为这两个函数都是线程安全的,获取到的时间戳是不包括闰秒

    获取当地时间:
    localtime_r函数,_r后缀是线程安全的

    定时器相关函数
    timerfd_create timerfd_gettime timerfd_settime (改用文件描述符方式取代了信号)
    关于定时器接口的演进参考文章:Linux 下定时器的实现方式分析

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

    Linux定时器管理

    原先信号通知的方式只支持注册一个定时器,因此必须在用户空间进行管理。后面新的POSIX timer支持了多个定时器,但也是由内核空间代为组织管理。最后演变出的基于文件描述符的定时器,文件描述符的数量是有限的,因此也必须进行管理。

    Linux内核定时器的管理是使用了基于时间轮的方式

    定时器的管理方式对比(Linux下定时器的设计与实现
    在这里插入图片描述

    各种网络库的定时器实现

    muduo网络库
    直接是使用了STL中的set容器,多线程竞争的问题它是采用了各个线程处理各自的定时器,因此几乎没有竞争,具体实现过程如下:

    1. 每个线程都有一个EventLoop对象,EventLoop对象初始化的时候会new一个TimerQueue,因此每个IO线程会有一个TimerQueue,TimerQueue对象初始化的时候就会分配好Timerfd和channel等,当Timerfd有可读事件发生时,就会从队列中拿出到时事件处理
    2. 在增加和删除定时器时,通过runInLoop方法保证了能够在非IO线程给指定EventLoop的IO线程增加和删除定时器。因此,保存定时器的队列也不需要加锁
    3. TimerQueue有一个cancelingTimers_ 成员,其作用是:当在定时队列中找不到删除的定时器,而此时正在执行定时器的回调函数,那么就将此定时器记录下来。在此次所有的定时器执行完成之后会调用reset 函数,如果是个周期性的定时器且不在cancelingTimers_ 中,则可以将其重新加入我们的TimerQueue中。
    4. 在reset函数中,如果不是周期性定时器就会被delete掉,在cancel函数和TimerQueue析构函数中都会delete掉Timer对象。(可用unique_ptr解决这个问题,使用std:move 应该能够实现转移)
    5. runInLoop的实现中调用了queueinloop方法,该方法会往目标IO线程的EventLoop对象的pendingFunctors_增加元素(typedef std::function<void()> Functor 参数为空,所有返回值类型都兼容),同时唤醒该线程(eventfd可读),而在EventLoop的循环中就会定期执行这些函数。

    libco协程库
    使用的是时间轮的方式

    brpc框架
    timer_keeping.md

    RPC框架的特性:

    1. 大部分定时器都是RPC调用超时定时器,绝大部分场景下是不需要用到的
    2. 多线程框架,并且任何线程都可能被阻塞,因此需要独立的线程做定时任务才能保证定时器的准确性
    3. 相互竞争的线程设定的时间往往聚集在同一个区域,因为程序的超时大都是一个值,加上当前时间后都差不多。
    展开全文
  • 创建、初始化以及删除一个定时器的行动被分为三个不同的函数:timer_create()(创建定时器)、timer_settime()(初始化定时器)以及timer_delete(销毁它)。   (1)创建一个定时器: int timer_create(clockid_t ...

    POSIX时钟系列:

    创建、初始化以及删除一个定时器的行动被分为三个不同的函数:timer_create()(创建定时器)、timer_settime()(初始化定时器)以及timer_delete(销毁它)。

     

    (1)创建一个定时器:

    int timer_create(clockid_t clock_id, struct sigevent *evp, timer_t *timerid)

        进程可以通过调用timer_create()创建特定的定时器,定时器是每个进程自己的,不是在fork时继承的。

    参数clock_id说明定时器是基于哪个时钟的。

    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.

    参数*timerid装载的是被创建的定时器的ID。该函数创建了定时器,并将他的ID 放入timerid指向的位置中。

    参数evp指定了定时器到期要产生的异步通知。

    如果evp为NULL,那么定时器到期会产生默认的信号,对 CLOCK_REALTIMER来说,默认信号就是SIGALRM。

    如果要产生除默认信号之外的其它信号,程序必须将 evp->sigev_signo设置为期望的信号码。

    struct sigevent 结构中的成员evp->sigev_notify说明了定时器到期时应该采取的行动。通常,这个成员的值为SIGEV_SIGNAL,这个值说明在定时器到期时,会产生一个信号。程序可以将成员evp->sigev_notify设为SIGEV_NONE来防止定时器到期时产生信号。如果几个定时器产生了同一个信号,处理程序可以用 evp->sigev_value来区分是哪个定时器产生了信号。要实现这种功能,程序必须在为信号安装处理程序时,使用struct sigaction的成员sa_flags中的标志符SA_SIGINFO。

     
    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;
    }
    union sigval
    {
    int sival_int; //integer value
    void *sival_ptr; //pointer value
    }
    通过将evp->sigev_notify设定为如下值来定制定时器到期后的行为:
    SIGEV_NONE:什么都不做,只提供通过timer_gettime和timer_getoverrun查询超时信息。
    SIGEV_SIGNAL: 当定时器到期,内核会将sigev_signo所指定的信号传送给进程。在信号处理程序中,si_value会被设定会sigev_value。
    SIGEV_THREAD: 当定时器到期,内核会(在此进程内)以sigev_notification_attributes为线程属性创建一个线程,并且让它执行sigev_notify_function,传入sigev_value作为为一个参数。
     
    (2)启动一个定时器:
        timer_create()所创建的定时器并未启动。要将它关联到一个到期时间以及启动时钟周期,可以使用timer_settime()。
    int timer_settime(timer_t timerid, int flags, const struct itimerspec *value, struct itimerspect *ovalue);
     
    struct itimespec{
        struct timespec it_interval; 
        struct timespec it_value;   
    }; 
        如同settimer(),it_value用于指定当前的定时器到期时间。当定时器到期,it_value的值会被更新成it_interval 的值。如果it_interval的值为0,则定时器不是一个时间间隔定时器,一旦it_value到期就会回到未启动状态。timespec的结构提供了纳秒级分辨率:
    struct timespec{
        time_t tv_sec;
        long tv_nsec;  
    };
        如果flags的值为TIMER_ABSTIME,则value所指定的时间值会被解读成绝对值(此值的默认的解读方式为相对于当前的时间)。这个经修改的行为可避免取得当前时间、计算“该时间”与“所期望的未来时间”的相对差额以及启动定时器期间造成竞争条件。
        如果ovalue的值不是NULL,则之前的定时器到期时间会被存入其所提供的itimerspec。如果定时器之前处在未启动状态,则此结构的成员全都会被设定成0。
     
     
    (3)获得一个活动定时器的剩余时间:
    int timer_gettime(timer_t timerid,struct itimerspec *value);
     
    取得一个定时器的超限运行次数:
        有可能一个定时器到期了,而同一定时器上一次到期时产生的信号还处于挂起状态。在这种情况下,其中的一个信号可能会丢失。这就是定时器超限。程序可以通过调用timer_getoverrun来确定一个特定的定时器出现这种超限的次数。定时器超限只能发生在同一个定时器产生的信号上。由多个定时器,甚至是那些使用相同的时钟和信号的定时器,所产生的信号都会排队而不会丢失。
    int timer_getoverrun(timer_t timerid);
        执行成功时,timer_getoverrun()会返回定时器初次到期与通知进程(例如通过信号)定时器已到期之间额外发生的定时器到期次数。举例来说,在我们之前的例子中,一个1ms的定时器运行了10ms,则此调用会返回9。如果超限运行的次数等于或大于DELAYTIMER_MAX,则此调用会返回DELAYTIMER_MAX。
        执行失败时,此函数会返回-1并将errno设定会EINVAL,这个唯一的错误情况代表timerid指定了无效的定时器。
     
    (4)删除一个定时器:
    int timer_delete (timer_t timerid);
        一次成功的timer_delete()调用会销毁关联到timerid的定时器并且返回0。执行失败时,此调用会返回-1并将errno设定会 EINVAL,这个唯一的错误情况代表timerid不是一个有效的定时器。
     
    例1:
    void  handle()
    {
     time_t t;
     char p[32];
     time(&t);
     strftime(p, sizeof(p), "%T", localtime(&t));
     printf("time is %s\n", p);
    }
     
    int main()
    {
     struct sigevent evp;
     struct itimerspec ts;
     timer_t timer;
     int ret;
     evp.sigev_value.sival_ptr = &timer;
     evp.sigev_notify = SIGEV_SIGNAL;
     evp.sigev_signo = SIGUSR1;
     signal(SIGUSR1, handle);
     ret = timer_create(CLOCK_REALTIME, &evp, &timer);
     if( ret )
      perror("timer_create");
     ts.it_interval.tv_sec = 1;
     ts.it_interval.tv_nsec = 0;
     ts.it_value.tv_sec = 3;
     ts.it_value.tv_nsec = 0;
     ret = timer_settime(timer, 0, &ts, NULL);
     if( ret )
      perror("timer_settime");
     while(1);
    }
     
    例2:
    void  handle(union sigval v)
    {
     time_t t;
     char p[32];
     time(&t);
     strftime(p, sizeof(p), "%T", localtime(&t));
     printf("%s thread %lu, val = %d, signal captured.\n", p, pthread_self(), v.sival_int);
     return;
    }
     
    int main()
    {
     struct sigevent evp;
     struct itimerspec ts;
     timer_t timer;
     int ret;
     memset   (&evp,   0,   sizeof   (evp));
     evp.sigev_value.sival_ptr = &timer;
     evp.sigev_notify = SIGEV_THREAD;
     evp.sigev_notify_function = handle;
     evp.sigev_value.sival_int = 3;   //作为handle()的参数
     ret = timer_create(CLOCK_REALTIME, &evp, &timer);
     if( ret)
      perror("timer_create");
     ts.it_interval.tv_sec = 1;
     ts.it_interval.tv_nsec = 0;
     ts.it_value.tv_sec = 3;
     ts.it_value.tv_nsec = 0;
     ret = timer_settime(timer, TIMER_ABSTIME, &ts, NULL);
     if( ret )
      perror("timer_settime");
     while(1);
    }
    展开全文
  • 本文简述如何使用linux里的定时器,包括周期性中断,停止和启动的控制。 日常常用的几个定时器相关linux API函数: init_timer(); 初始化定时器 add_timer(); 启动定制器 del_timer();停止定时器 mod_timer(); 重新...

    本文简述如何使用linux里的定时器,包括周期性中断,停止和启动的控制。

    日常常用的几个定时器相关linux API函数:

    1. init_timer(); 初始化定时器
    2. add_timer(); 启动定制器
    3. del_timer();停止定时器
    4. mod_timer(); 重新修改定时器当前计数时间

    这些API位于:kernel\timer.c中,该源文件里还包括了常用的msleep(),schedule_timeout()等常用的延时调度函数。

    下面以一个实例驱动介绍linux里的timer的使用(基于3.10.0-123内核)。


    案例如下:

    1. 创建一定时器,完成每3s一次周期性中断并打印。
    2. 通过字符设备接口与用户层交互,写0则关闭(停止)定时器,写1则开启(启动)定时器
      (即:当echo 1 >/dev/demo_deva启动定时器,当echo 0 >/dev/demo_deva 停止定时器)。
    #include <linux/module.h>
    #include <linux/kernel.h>
    #include <linux/fs.h>
    #include <linux/init.h>
    #include <linux/moduleparam.h>
    #include <linux/delay.h>
    #include <linux/ioctl.h>
    #include <linux/errno.h>
    #include <linux/types.h>
    #include <linux/slab.h>
    #include <linux/device.h> 
    #include <asm/io.h>
    #include <asm/uaccess.h>
    #include <asm/irq.h>
    #include "timer_drv.h"
    
    /*
    功能:
    	定时器3秒定时中断一次打印。
    	当echo 1 >/dev/demo_deva开启定时器,当echo 0 >/dev/demo_deva 关闭定时器
    */
    
    #define DRV_VERSION  "V1.0"
    MODULE_LICENSE("GPL");
    MODULE_AUTHOR("LUDY");
    MODULE_DESCRIPTION("This is timer demo");
    MODULE_VERSION(DRV_VERSION);
    
    //static volatile int time_count = 0;
    static void handel_irq_do_timer(unsigned long arg);
    u8 delay = 3; //3s
    /* 设备要使用的定时器 */
    static struct timer_list my_timer;//也可以:TIMER_INITIALIZER(handel_irq_do_timer, 0, 0); 直接快速初始化结构体内容
    
    /* 定时器中断处理函数 */
    static void handel_irq_do_timer(unsigned long arg)
    {
    	// struct xxx_device *dev = (struct xxx_device *)(arg);
    	/* 修改调度定时器,3s再执行 */
    	mod_timer(&my_timer, jiffies + delay*HZ); //*HZ即转换成jiffies单位值
    	printk("timer arrival\n");
    }
    
    /* 定时器初始化*/
    void timer_init_run(void* pri_data)
    {
    
    	//device *dev =  (device*)pri_data;
        /* 初始化定时器 */
        init_timer(&my_timer);   
    	/* 设备结构体指针作为定时器处理函数参数 */
        my_timer.function = &handel_irq_do_timer; //中断处理函数
    	my_timer.expires =  delay*HZ;  //定时时间delay秒  
        //my_timer.data = (unsigned long)dev;
    }
    
    /*
    写函数;
    定时器启动和停止控制
    */
    ssize_t  demodrv_write(struct file *file, const char __user *buf, size_t count, loff_t *ppos)
    {
    	char rev_data;
          /*只接受一个字符的写入*/
    	if(count >2)  //count包括了/0字符
    		return -EFAULT;
    	//copy_from_user(rev_data1, buf, count);
    	if(get_user(rev_data, buf))
    	{
    		return -EFAULT;
    	}
    	printk("driver: device write:%c  count:%d\n" ,rev_data, count);
    
    	switch(rev_data)
    	{
    		case '0':
    		    /* 删除(停止)定时器 */
    			del_timer(&my_timer);
    			break;
    		case '1':
    			/* 添加(注册) 启动定时器 */
    			add_timer(&my_timer);  //add添加后,定制器开始运行
    			break;
    		default:
    			PRINT_ERR("write EINVAL:%c\n" ,rev_data);
    			break;
    	}
    	return count;
    }
    
    
    ssize_t demodrv_read(struct file *file, char __user *buf, size_t size, loff_t *ppos)
    {
    	return 1;
    }
    
    static int demodrv_open(struct inode *inode, struct file *file)
    {
    	printk("driver: device open\n");
    	return 0;
    }
    
    
    int demodrv_close(struct inode *inode, struct file *file)
    {
    	printk("driver: device close\n");
    	return 0;
    }
    
    
    static struct file_operations demo_drv_fops = {
        .owner   =  THIS_MODULE, 
        .open    =  demodrv_open,     
    	.read	 =	demodrv_read,
    	.write	 =	demodrv_write,		
    	.release =  demodrv_close,
    };
    
    int major;
    static struct class *demo_drv_class;
    static struct class_device	*demo_class_dev;
    
    static int __init demo_drv_init(void)
    {
    	major = register_chrdev(0, "demo_drv", &demo_drv_fops);//注册字符驱动获取设备号
    	demo_drv_class = class_create(THIS_MODULE, "demo_drv_class"); //sys/class下创建类
    	demo_class_dev = device_create(demo_drv_class, NULL, MKDEV(major, 0), NULL, "demo_dev"); /*类下创建设备文件绑定到设备号 /dev/demo_dev */
    
    	timer_init_run(demo_class_dev);
    	return 0;
    }
    
    
    static void __exit demo_drv_exit(void)
    {
    	unregister_chrdev(major, "demodrv");
    	device_unregister(demo_class_dev);
    	class_destroy(demo_drv_class);
        /* 删除(停止)定时器 */
        del_timer(&my_timer);
    
    	return 0;
    }
    module_init(demo_drv_init);
    module_exit(demo_drv_exit);
    
    

    如上源码可知,使用一个定时器通常需要如下步骤:

    1. 定义一个timer_list 结构体的定时器对象。并可以通过TIMER_INITIALIZER快速赋值对象里的成员(中断函数;定时时间等);
    2. init_timer 初始化该定制器(此时定时器还不能走);
    3. 可以自定义为定时器对象里的成员赋值(中断函数;定时时间;私有数据等);
    4. add_timer 启动该定时器,定时器开始倒计时;
    5. mod_timer 修改当前定时器的数值(复位定时器);
    6. 如果要停止(删除)一个定时器,执行del_timer,需要重新启动则继续执行add_timer。
    展开全文
  • Linux定时器的实现方式分析

    千次阅读 2017-02-04 10:09:58
    不管是用户空间的程序开发,还是内核空间的程序开发,很多时候都需要有定时器作为基础组件的支持,但使用场景的不同,对定时器的实现考虑也不尽相同,本文讨论了在 Linux 环境下,应用层和内核层的定时器的各种实现...

    概论

    定时器属于基本的基础组件,不管是用户空间的程序开发,还是内核空间的程序开发,很多时候都需要有定时器作为基础组件的支持,但使用场景的不同,对定时器的实现考虑也不尽相同,本文讨论了在 Linux 环境下,应用层和内核层的定时器的各种实现方法,并分析了各种实现方法的利弊以及适宜的使用环境。

    首先,给出一个基本模型,定时器的实现,需要具备以下几个行为,这也是在后面评判各种定时器实现的一个基本模型 [1]:

    StartTimer(Interval, TimerId, ExpiryAction)

    注册一个时间间隔为 Interval 后执行 ExpiryAction 的定时器实例,其中,返回 TimerId 以区分在定时器系统中的其他定时器实例。

    StopTimer(TimerId)

    根据 TimerId 找到注册的定时器实例并执行 Stop 。

    PerTickBookkeeping()

    在一个 Tick 内,定时器系统需要执行的动作,它最主要的行为,就是检查定时器系统中,是否有定时器实例已经到期。注意,这里的 Tick 实际上已经隐含了一个时间粒度 (granularity) 的概念。

    ExpiryProcessing()

    在定时器实例到期之后,执行预先注册好的 ExpiryAction 行为。

    上面说了基本的定时器模型,但是针对实际的使用情况,又有以下 2 种基本行为的定时器:

    Single-Shot Timer

    这种定时器,从注册到终止,仅仅只执行一次。

    Repeating Timer

    这种定时器,在每次终止之后,会自动重新开始。本质上,可以认为 Repeating Timer 是在 Single-Shot Timer 终止之后,再次注册到定时器系统里的 Single-Shot Timer,因此,在支持 Single-Shot Timer 的基础上支持 Repeating Timer 并不算特别的复杂。

    基于链表和信号实现定时器 (2.4 版内核情况下 )

    在 2.4 的内核中,并没有提供 POSIX timer [ 2 ]的支持,要在进程环境中支持多个定时器,只能自己来实现,好在 Linux 提供了 setitimer(2) 的接口。它是一个具有间隔功能的定时器 (interval timer),但如果想在进程环境中支持多个计时器,不得不自己来管理所有的计时器。 setitimer(2) 的定义如下:

    清单 1. setitimer 的原型
    #include <sys/time.h> 
    
     int setitimer(int which, const struct itimerval *new_value,struct itimerval *old_value);

    setitimer 能够在 Timer 到期之后,自动再次启动自己,因此,用它来解决 Single-Shot Timer 和 Repeating Timer 的问题显得很简单。该函数可以工作于 3 种模式:

    ITIMER_REAL 以实时时间 (real time) 递减,在到期之后发送 SIGALRM 信号

    ITIMER_VIRTUAL 仅进程在用户空间执行时递减,在到期之后发送 SIGVTALRM 信号

    ITIMER_PROF 进程在用户空间执行以及内核为该进程服务时 ( 典型如完成一个系统调用 ) 都会递减,与 ITIMER_VIRTUAL 共用时可度量该应用在内核空间和用户空间的时间消耗情况,在到期之后发送 SIGPROF 信号

    定时器的值由下面的结构定义:

    清单 2. setitimer 定时器的值定义
    struct itimerval { 
     struct timeval it_interval; /* next value */ 
     struct timeval it_value;     /* current value */ 
     }; 
    
     struct timeval { 
            long tv_sec;                /* seconds */ 
            long tv_usec;               /* microseconds */ 
     };

    setitimer() 以 new_value 设置特定的定时器,如果 old_value 非空,则它返回 which 类型时间间隔定时器的前一个值。定时器从 it_value 递减到零,然后产生一个信号,并重新设置为 it_interval,如果此时 it_interval 为零,则该定时器停止。任何时候,只要 it_value 设置为零,该定时器就会停止。

    由于 setitimer() 不支持在同一进程中同时使用多次以支持多个定时器,因此,如果需要同时支持多个定时实例的话,需要由实现者来管理所有的实例。用 setitimer() 和链表,可以构造一个在进程环境下支持多个定时器实例的 Timer,在一般的实现中的 PerTickBookkeeping 时,会递增每个定时器的 elapse 值,直到该值递增到最初设定的 interval 则表示定时器到期。

    基于链表实现的定时器可以定义为:

    清单 3. 基于链表的定时器定义
    typedef int timer_id; 
    
     /** 
     * The type of callback function to be called by timer scheduler when a timer 
     * has expired. 
     * 
     * @param id                The timer id. 
     * @param user_data        The user data. 
     * $param len               The length of user data. 
     */ 
     typedef int timer_expiry(timer_id id, void *user_data, int len); 
    
     /** 
     * The type of the timer 
     */ 
     struct timer { 
            LIST_ENTRY(timer) entries;/**< list entry               */ 
    
            timer_id id;                /**< timer id                  */ 
    
            int interval;               /**< timer interval(second) */ 
            int elapse;                 /**< 0 -> interval             */ 
    
            timer_expiry *cb;          /**< call if expiry            */ 
            void *user_data;           /**< callback arg               */ 
            int len; 	                    /**< user_data length          */ 
     };

    定时器的时间间隔以 interval 表示,而 elapse 则在 PerTickBookkeeping() 时递增,直到 interval 表示定时器中止,此时调用回调函数 cb 来执行相关的行为,而 user_data 和 len 为用户可以传递给回调函数的参数。

    所有的定时器实例以链表来管理:

    清单 4. 定时器链表
    /** 
     * The timer list 
     */ 
     struct timer_list { 
            LIST_HEAD(listheader, timer) header;  /**< list header         */ 
            int num; 	                                   /**< timer entry number */ 
            int max_num;                               /**< max entry number    */ 
    
            void (*old_sigfunc)(int);                /**< save previous signal handler */ 
            void (*new_sigfunc)(int);                /**< our signal handler              */ 
    
            struct itimerval ovalue;                 /**< old timer value */ 
            struct itimerval value;                  /**< our internal timer value */ 
     };

    这里关于链表的实现使用了 BSD 风格关于链表的一组宏,避免了再造轮子;该结构中,old_sigfunc 在 init_timer 初始定时器链表时候用来保存系统对 SIGALRM 的处理函数,在定时器系统 destory 时用来恢复到之前的处理函数; ovalue 的用途与此类似。

    清单 5. 定时器链表的创建和 Destroy
    /** 
     * Create a timer list. 
     * 
     * @param count  The maximum number of timer entries to be supported initially. 
     * 
     * @return        0 means ok, the other means fail. 
     */ 
     int init_timer(int count) 
     { 
            int ret = 0; 
    
            if(count <=0 || count > MAX_TIMER_NUM) { 
                   printf("the timer max number MUST less than %d.\n", MAX_TIMER_NUM); 
                   return -1; 
            } 
    
            memset(&timer_list, 0, sizeof(struct timer_list)); 
            LIST_INIT(&timer_list.header); 
            timer_list.max_num = count; 
    
            /* Register our internal signal handler and store old signal handler */ 
            if ((timer_list.old_sigfunc = signal(SIGALRM, sig_func)) == SIG_ERR) { 
                    return -1; 
            } 
            timer_list.new_sigfunc = sig_func; 
    
         /*Setting our interval timer for driver our mutil-timer and store old timer value*/ 
            timer_list.value.it_value.tv_sec = TIMER_START; 
            timer_list.value.it_value.tv_usec = 0; 
            timer_list.value.it_interval.tv_sec = TIMER_TICK; 
            timer_list.value.it_interval.tv_usec = 0; 
            ret = setitimer(ITIMER_REAL, &timer_list.value, &timer_list.ovalue); 
    
            return ret; 
     } 
    
    
     /** 
     * Destroy the timer list. 
     * 
     * @return          0 means ok, the other means fail. 
     */ 
     int destroy_timer(void) 
     { 
            struct timer *node = NULL; 
    
            if ((signal(SIGALRM, timer_list.old_sigfunc)) == SIG_ERR) { 
                    return -1; 
            } 
    
            if((setitimer(ITIMER_REAL, &timer_list.ovalue, &timer_list.value)) < 0) { 
                    return -1; 
            } 
    
            while (!LIST_EMPTY(&timer_list.header)) {     /* Delete. */ 
    		 node = LIST_FIRST(&timer_list.header); 
                    LIST_REMOVE(node, entries); 
                    /* Free node */ 
    		 printf("Remove id %d\n", node->id); 
                    free(node->user_data); 
                    free(node); 
            } 
    
            memset(&timer_list, 0, sizeof(struct timer_list)); 
    
            return 0; 
     }

    添加定时器的动作非常的简单,本质只是一个链表的插入而已:

    清单 6. 向定时器链表中添加定时器
    /** 
     * Add a timer to timer list. 
     * 
     * @param interval  The timer interval(second). 
     * @param cb  	    When cb!= NULL and timer expiry, call it. 
     * @param user_data Callback's param. 
     * @param len  	    The length of the user_data. 
     * 
     * @return          The timer ID, if == INVALID_TIMER_ID, add timer fail. 
     */ 
     timer_id  add_timer(int interval, timer_expiry *cb, void *user_data, int len) 
     { 
            struct timer *node = NULL; 
    
            if (cb == NULL || interval <= 0) { 
                    return INVALID_TIMER_ID; 
            } 
    
    	 if(timer_list.num < timer_list.max_num) { 
    		 timer_list.num++; 
    	 } else { 
    		 return INVALID_TIMER_ID; 
    	 } 
    
    	 if((node = malloc(sizeof(struct timer))) == NULL) { 
    		 return INVALID_TIMER_ID; 
    	 } 
    	 if(user_data != NULL || len != 0) { 
    		 node->user_data = malloc(len); 
    		 memcpy(node->user_data, user_data, len); 
    		 node->len = len; 
    	 } 
    
    	 node->cb = cb; 
    	 node->interval = interval; 
    	 node->elapse = 0; 
    	 node->id = timer_list.num; 
    
    	 LIST_INSERT_HEAD(&timer_list.header, node, entries); 
    
        return node->id; 
     }

    注册的信号处理函数则用来驱动定时器系统:

    清单 7. 信号处理函数驱动定时器
    /* Tick Bookkeeping */ 
     static void sig_func(int signo) 
     { 
            struct timer *node = timer_list.header.lh_first; 
            for ( ; node != NULL; node = node->entries.le_next) { 
                    node->elapse++; 
                    if(node->elapse >= node->interval) { 
                            node->elapse = 0; 
                            node->cb(node->id, node->user_data, node->len); 
                    } 
            } 
     }

    它主要是在每次收到 SIGALRM 信号时,执行定时器链表中的每个定时器 elapse 的自增操作,并与 interval 相比较,如果相等,代表注册的定时器已经超时,这时则调用注册的回调函数。

    上面的实现,有很多可以优化的地方:考虑另外一种思路,在定时器系统内部将维护的相对 interval 转换成绝对时间,这样,在每 PerTickBookkeeping 时,只需将当前时间与定时器的绝对时间相比较,就可以知道是否该定时器是否到期。这种方法,把递增操作变为了比较操作。并且上面的实现方式,效率也不高,在执行 StartTimer,StopTimer,PerTickBookkeeping 时,算法复杂度分别为 O(1),O(n),O(n),可以对上面的实现做一个简单的改进,在 StartTimer 时,即在添加 Timer 实例时,对链表进行排序,这样的改进,可以使得在执行 StartTimer,StopTimer,PerTickBookkeeping 时,算法复杂度分别为 O(n),O(1),O(1) 。改进后的定时器系统如下图 1:

    图 1. 基于排序链表的定时器
    基于排序链表的定时器

    基于 2.6 版本内核定时器的实现 (Posix 实时定时器 )

    Linux 自 2.6 开始,已经开始支持 POSIX timer [ 2 ]所定义的定时器,它主要由下面的接口构成 :

    清单 8. POSIX timer 接口
    #include <signal.h> 
     #include <time.h> 
    
     int timer_create(clockid_t clockid, struct sigevent *evp, 
     timer_t *timerid); 
     int timer_settime(timer_t timerid, int flags, 
     const struct itimerspec *new_value, 
     struct itimerspec * old_value); 
     int timer_gettime(timer_t timerid, struct itimerspec *curr_value); 
     int timer_getoverrun(timer_t timerid); 
     int timer_delete(timer_t timerid);

    这套接口是为了让操作系统对实时有更好的支持,在链接时需要指定 -lrt 。

    timer_create(2): 创建了一个定时器。

    timer_settime(2): 启动或者停止一个定时器。

    timer_gettime(2): 返回到下一次到期的剩余时间值和定时器定义的时间间隔。出现该接口的原因是,如果用户定义了一个 1ms 的定时器,可能当时系统负荷很重,导致该定时器实际山 10ms 后才超时,这种情况下,overrun=9ms 。

    timer_getoverrun(2): 返回上次定时器到期时超限值。

    timer_delete(2): 停止并删除一个定时器。

    上面最重要的接口是 timer_create(2),其中,clockid 表明了要使用的时钟类型,在 POSIX 中要求必须实现 CLOCK_REALTIME 类型的时钟。 evp 参数指明了在定时到期后,调用者被通知的方式。该结构体定义如下 :

    清单 9. POSIX timer 接口中的信号和事件定义
    union sigval { 
     int sival_int; 
     void *sival_ptr; 
     }; 
    
     struct sigevent { 
     int sigev_notify; /* Notification method */ 
     int sigev_signo; /* Timer expiration signal */ 
     union sigval sigev_value; /* Value accompanying signal or 
     passed to thread function */ 
     void (*sigev_notify_function) (union sigval); 
     /* Function used for thread 
     notifications (SIGEV_THREAD) */ 
     void *sigev_notify_attributes; 
     /* Attributes for notification thread 
     (SIGEV_THREAD) */ 
     pid_t sigev_notify_thread_id; 
     /* ID of thread to signal (SIGEV_THREAD_ID) */ 
     };

    其中,sigev_notify 指明了通知的方式 :

    SIGEV_NONE

    当定时器到期时,不发送异步通知,但该定时器的运行进度可以使用 timer_gettime(2) 监测。

    SIGEV_SIGNAL

    当定时器到期时,发送 sigev_signo 指定的信号。

    SIGEV_THREAD

    当定时器到期时,以 sigev_notify_function 开始一个新的线程。该函数使用 sigev_value 作为其参数,当 sigev_notify_attributes 非空,则制定该线程的属性。注意,由于 Linux 上线程的特殊性,这个功能实际上是由 glibc 和内核一起实现的。

    SIGEV_THREAD_ID (Linux-specific)

    仅推荐在实现线程库时候使用。

    如果 evp 为空的话,则该函数的行为等效于:sigev_notify = SIGEV_SIGNAL,sigev_signo = SIGVTALRM,sigev_value.sival_int = timer ID 。

    由于 POSIX timer [ 2 ]接口支持在一个进程中同时拥有多个定时器实例,所以在上面的基于 setitimer() 和链表的 PerTickBookkeeping 动作就交由 Linux 内核来维护,这大大减轻了实现定时器的负担。由于 POSIX timer [ 2 ]接口在定时器到期时,有更多的控制能力,因此,可以使用实时信号避免信号的丢失问题,并将 sigev_value.sival_int 值指定为 timer ID,这样,就可以将多个定时器一起管理了。需要注意的是,POSIX timer [ 2 ]接口只在进程环境下才有意义 (fork(2) 和 exec(2) 也需要特殊对待 ),并不适合多线程环境。与此相类似的,Linux 提供了基于文件描述符的相关定时器接口:

    清单 10. Linux 提供的基于文件描述符的定时器接口
    #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 基本哲学之一 ---- “一切皆文件”之下。

    最小堆实现的定时器

    最小堆指的是满足除了根节点以外的每个节点都不小于其父节点的堆。这样,堆中的最小值就存放在根节点中,并且在以某个结点为根的子树中,各节点的值都不小于该子树根节点的值。一个最小堆的例子如下图 2:

    图 2. 最小堆
    最小堆

    一个最小堆,一般支持以下几种操作:

    Insert(TimerHeap, Timer): 在堆中插入一个值,并保持最小堆性质,具体对应于定时器的实现,则是把定时器插入到定时器堆中。根据最小堆的插入算法分析,可以知道该操作的时间复杂度为 O(lgn) 。

    Minimum(TimerHeap): 获取最小堆的中最小值;在定时器系统中,则是返回定时器堆中最先可能终止的定时器。由于是最小堆,只需返回堆的 root 即可。此时的算法复杂度为 O(1) 。

    ExtractMin(TimerHeap): 在定时器到期后,执行相关的动作,它的算法复杂度为 O(1) 。

    最小堆本质上是一种最小优先级队列 (min-priority queue) 。定时可以作为最小优先级队列的一个应用,该优先级队列把定时器的时间间隔值转化为一个绝对时间来处理,ExtractMin 操则是在所有等待的定时器中,找出最先超时的定时器。在任何时候,一个新的定时器实例都可通过 Insert 操作加入到定时器队列中去。

    在 pjsip 项目的基础库 pjlib 中,有基于最小堆实现的定时器,它主要提供了以下的几个接口:

    清单 10. pjlib 提供的基于最小堆的定时器接口
    /** 
     * Create a timer heap. 
     */ 
     PJ_DECL(pj_status_t) pj_timer_heap_create( pj_pool_t *pool, 
    					   pj_size_t count, 
                                               pj_timer_heap_t **ht); 
    
     /** 
     * Destroy the timer heap. 
     */ 
     PJ_DECL(void) pj_timer_heap_destroy( pj_timer_heap_t *ht ); 
    
     /** 
     * Initialize a timer entry. Application should call this function at least 
     * once before scheduling the entry to the timer heap, to properly initialize 
     * the timer entry. 
     */ 
     PJ_DECL(pj_timer_entry*) pj_timer_entry_init( pj_timer_entry *entry, 
                                                  int id, 
                                                  void *user_data, 
                                                  pj_timer_heap_callback *cb ); 
    
     /** 
     * Schedule a timer entry which will expire AFTER the specified delay. 
     */ 
     PJ_DECL(pj_status_t) pj_timer_heap_schedule( pj_timer_heap_t *ht, 
    					     pj_timer_entry *entry, 
    					     const pj_time_val *delay); 
    
     /** 
     * Cancel a previously registered timer. 
     */ 
     PJ_DECL(int) pj_timer_heap_cancel( pj_timer_heap_t *ht, 
    				   pj_timer_entry *entry); 
    
     /** 
     * Poll the timer heap, check for expired timers and call the callback for 
     * each of the expired timers. 
     */ 
     PJ_DECL(unsigned) pj_timer_heap_poll( pj_timer_heap_t *ht, 
                                          pj_time_val *next_delay);

    pjlib 中的定时器在内部使用数组的方式实现堆,这样对于内存空间的使用将更加的紧凑;它的实现还可在定时器的数量超过预先设定的最大数量时会自己增加最大定时器数量。文件 pjlib/src/pjlib-test/timer.c 是它的一个单元测试。与基于链表方式的实现相比较,明显它的时间复杂度要低一些,这样可以支持更多的定时器实例。

    基于时间轮 (Timing-Wheel) 方式实现的定时器

    时间轮 (Timing-Wheel) 算法类似于一以恒定速度旋转的左轮手枪,枪的撞针则撞击枪膛,如果枪膛中有子弹,则会被击发;与之相对应的是:对于 PerTickBookkeeping,其最本质的工作在于以 Tick 为单位增加时钟,如果发现有任何定时器到期,则调用相应的 ExpiryProcessing 。设定一个循环为 N 个 Tick 单元,当前时间是在 S 个循环之后指向元素 i (i>=0 and i<= N - 1),则当前时间 (Current Time)Tc 可以表示为:Tc = S*N + i ;如果此时插入一个时间间隔 (Time Interval) 为 Ti 的定时器,设定它将会放入元素 n(Next) 中,则 n = (Tc + Ti)mod N = (S*N + i + Ti) mod N = (i + Ti) mod N 。如果我们的 N 足够的大,显然 StartTimer,StopTimer,PerTickBookkeeping 时,算法复杂度分别为 O(1),O(1),O(1) 。在 [5] 中,给出了一个简单定时器轮实现的定时。下图 3 是一个简单的时间轮定时器:

    图 3. 简单时间轮
    简单时间轮

    如果需要支持的定时器范围非常的大,上面的实现方式则不能满足这样的需求。因为这样将消耗非常可观的内存,假设需要表示的定时器范围为:0 – 2^3-1ticks,则简单时间轮需要 2^32 个元素空间,这对于内存空间的使用将非常的庞大。也许可以降低定时器的精度,使得每个 Tick 表示的时间更长一些,但这样的代价是定时器的精度将大打折扣。现在的问题是,度量定时器的粒度,只能使用唯一粒度吗?想想日常生活中常遇到的水表,如下图 4:

    图 4. 水表
    水表

    在上面的水表中,为了表示度量范围,分成了不同的单位,比如 1000,100,10 等等,相似的,表示一个 32bits 的范围,也不需要 2^32 个元素的数组。实际上,Linux 的内核把定时器分为 5 组,每组的粒度分别表示为:1 jiffies,256 jiffies,256*64 jiffies,256*64*64 jiffies,256*64*64*64 jiffies,每组中桶的数量分别为:256,64,64,64,64,这样,在 256+64+64+64+64 = 512 个桶中,表示的范围为 2^32 。有了这样的实现,驱动内核定时器的机制也可以通过水表的例子来理解了,就像水表,每个粒度上都有一个指针指向当前时间,时间以固定 tick 递增,而当前时间指针则也依次递增,如果发现当前指针的位置可以确定为一个注册的定时器,就触发其注册的回调函数。 Linux 内核定时器本质上是 Single-Shot Timer,如果想成为 Repeating Timer,可以在注册的回调函数中再次的注册自己。内核定时器如下图 5:

    图 5. Linux 时间轮
    Linux时间轮
    回页首

    结论

    由上面的分析,可以看到各种定时器实现算法的复杂度:

    表 1. 定时器实现算法复杂度
    实现方式 StartTimer StopTimer PerTickBookkeeping
    基于链表 O(1) O(n) O(n)
    基于排序链表 O(n) O(1) O(1)
    基于最小堆 O(lgn) O(1) O(1)
    基于时间轮 O(1) O(1) O(1)

    如果需要能在线程环境中使用的定时器,对于基于链表的定时器,可能需要很小心的处理信号的问题;而 POSIX timer [ 2 ]接口的定时器,只具有进程的语义,如果想在多线程环境下也 n 能使用,可以使用 Linux 提供的 timerfd_create(2) 接口。如果需要支持的定时器数量非常的大,可以考虑使用基于最小堆和时间轮的方式来实现。

    展开全文
  • linux定时器

    2021-05-11 06:50:16
    linux定时器简介这篇文章主要记录我在试图解决如何尽可能精确地在某个特定的时间间隔执行某项具体任务时的思路历程,并在后期对相关的API进行的归纳和总结,以备参考。问题引出很多时候,我们会有类似“每隔多长时间...
  • Linux定时器和时间管理

    千次阅读 2018-08-18 19:12:19
    本章将介绍动态定时器在内核中的实现,同时给出在内核代码中可供使用的定时器接口。 1、内核中的时间概念 时间概念对计算机来说有些模糊,事实上内核必须在硬件的帮助下才能计算和管理时间。硬件为内核提供了一...
  • 实现linux下c语言定时回调,简单易懂,有中文注释。适合新手在不适用库函数下使用和学习。
  • linux定时器总结

    2018-09-11 15:50:32
    1 参考资料 ...Ø 《linux系统编程》第“10.9 定时器”章节 2 概要 要在linux中使用定时器,有如下三种方法: 定时器方式 一个进程允许 使用的数量 通知方式 ...
  • //linux只允许单进程拥有一个定时器,因此在linux下的单进程中要使用多个定时器,则需要自己维护管理 // //这个实现允许用户使用多个自定义的定时器,每个自定义的定时器将周期地被触发直到其被删除。实现的主要思路...
  • [linux c/c++] linux定时器的使用

    千次阅读 2020-05-29 08:44:39
    linux下,定时器有两种实现: 1)使用alarm函数进行计时,alarm函数计时满后会发送ALARM信号,注册信号处理函数即可; 2)使用linux内核的原生timer,需要引入内核模块,头文件为linux/timer.h alarm: //...
  • 内核定时器是一个数据结构,它告诉内核在用户...内核提供了一组用来声明、注册和删除内核定时器的函数,相关接口如下: struct timer_list { /* * All fields that change during normal runtime grouped to...
  • Linux内核定时器

    千次阅读 2019-05-23 20:21:48
    本文转自https://www.ibm.com/developerworks/cn/linux/1308_liuming_linuxtime3/ 引子 时间系统的工作需要软硬件以及操作系统的互相协作,在上一部分,我们已经看到大多数时间函数都依赖内核系统调用,GlibC 仅仅...
  • Linux 定时器

    2021-05-15 15:14:41
    时间间隔定时器interval timer(时间间隔定时器)系统调用自从被POSIX标准化后,首次出现于4.2BSD,能够提供比alarm()还多的控制:#includeint getitimer(int which, struct itimerval *value);int setitimer(int ...
  • 一个函数用来设置信号相关的函数绑定,一个函数用来初始化定时器并且启动它。 然后程序需要运行很久,放到死循环里面去,才能看出来效果,一般的应用程序,都不会自动退出的,往往是永远不退出,或者是等待用户关闭...
  • =Start=缘由:学习需要正文:参考解答:在 2.4 的内核中,并没有提供 POSIX timer 的支持,要在进程环境中支持多个定时器,只能自己来实现,好在 Linux 提供了 setitimer(2) 的接口。它是一个具有间隔功能的定时器 ...
  • linux 定时器

    千次阅读 2018-11-29 22:35:59
    windows下的接口支持单进程中拥有多个定时器,而linux则只允许单进程拥有一个定时器,因此在linux下的单进程中要使用多个定时器,则需要自己维护管理, 1.alarm函数,对定时要求不太精确的话,使用alarm()和signal...
  • Linux Timer定时器

    2021-05-12 16:28:38
    timerfd为Linux为用户程序提供的定时器接口,该接口基于文件描述符,通过文件描述符的可读事件进行超时通知,且能够被用于epoll/select。主要有三个函数。头文件: include int timerfd_create(int clockid, int ...
  • linux内核定时器的使用 1、timer的使用 timer相关函数的定义在kernel\time\timer.c和include\linux\timer.h中。 定时器的结构体为 // include\linux\timer.h struct timer_list { /* * All fields that change ...
  • TDW 代表“Delta Wheel 上的定时器”,因为它是 Delta 列表和定时器轮算法的组合。 它是用于 Linux 应用程序的 C 语言 API。 计时器到期后,将调用用户定义的回调。
  • Linux下一种高效多定时器实现

    万次阅读 2018-08-29 12:11:42
    Linux下一种高效多定时器实现 作者:LouisozZ 日期:2018.08.29 运行环境说明 由于在 Linux 系统下一个进程只能设置一个时钟定时器,所以当应用需要有多个定时器来共同管理程序运行时,就需要自行实现多定时器...
  • LINUX内核定时器是内核用来控制在未来某个时间点(基于jiffies)调度执行某个函数的一种机制,其实现位于 <linux/timer.h> 和 kernel/timer.c 文件中。每当时钟中断发生时,全局变量jiffies(一个32位的unsigned long ...
  • linux Posix定时器介绍

    2020-03-08 17:36:51
    linux Posix定时器介绍 在linux应用编程中,定时器的使用是不可或缺的部分,本文介绍下linux
  • linux下的多定时器实现

    热门讨论 2010-08-25 01:05:57
    文件列表:timer.h,timer.c 功能:实现了linux下的多定时器,采用双向链表来维护定时器列表,用户可利用其中的接口来创建定时器,并注册超时回调函数。时钟计时采用select系统调用来实现。
  • linux 定时器编程

    2014-08-24 11:11:47
    linux 在应用层的编程有以下几种: 是 starttimer 二是
  • 内核提供了一组与定时器相关的接口用来简化管理定时器的操作。所有这些接口都声明在&lt;linux/Timer.h&gt;中,大多数接口在&lt;kernel/timer.c&gt;中的到实现。  创建定时器首先要先定义它,然后...

空空如也

空空如也

1 2 3 4 5 ... 20
收藏数 26,557
精华内容 10,622
关键字:

linux定时器接口

linux 订阅