Linux自定义timer实现(基于setitimer)

概论

不管是用户空间的程序开发,还是内核空间的程序开发,很多时候都需要有定时器的支持,定时器属于程序开发中的基本组件。定时器一般按照使用场景分为两种类型:

1.Single-Shot Timer

2.Repeating Timer

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

Linux定时器

2.4内核版本linux
结构体:

struct itimerval { 
	struct timeval it_interval; /* next value */ 
	struct timeval it_value;     /* current value */ 
}; 

struct timeval { 
    long tv_sec;                /* seconds */ 
    long tv_usec;               /* microseconds */ 
 };

user api函数:

#include <sys/time.h> 
int setitimer(int which, const struct itimerval *new_value,struct itimerval *old_value);

传入参数:
which:
ITIMER_REAL 以实时时间 (real time) 递减,在到期之后发送 SIGALRM 信号
ITIMER_VIRTUAL 仅进程在用户空间执行时递减,在到期之后发送 SIGVTALRM 信号
ITIMER_PROF 进程在用户空间执行以及内核为该进程服务时 ( 典型如完成一个系统调用 ) 都会递减,与 ITIMER_VIRTUAL 共用时可度量该应用在内核空间和用户空间的时间消耗情况,在到期之后发送 SIGPROF 信号
new_value:
新设置的定时器时间参数。
old_value:
返回的旧的定时器时间。

setitimer 能够在 timer 到期之后,自动再次启动自己,因此,用它来解决 Single-Shot Timer 和 Repeating Timer 的问题显得很简单。

2.6 内核版本linux
除了上面提到的接口,2.6版本内核以后,都增加了POSIX timer接口API。

#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: 创建了一个定时器。

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

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

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

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

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

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 

 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监测。

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 接口支持在一个进程中同时拥有多个定时器实例。但是需要注意的是,POSIX timer 接口只在进程环境下才有意义 ,并不适合多线程环境。因此,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,poll 等异步接口,使得定时器的实现和使用更加的方便,它们的使用比 POSIX timer 更加的灵活。

自定义定时器

为了同时支持2.4以及2.6版本以上的内核环境,我们可以考虑自己实现定时器来进行统一管理。
在接下来将实现一个简单的定时器模型,将基于上面提到的setitimer接口来实现,此接口在linux系统中都存在。
为什么要实现自定义定时器,首先,2.4内核中没有posix timer接口,而setitimer接口只能允许一个进程中存在一个定时器,那么当我们需要在进程中使用多个定时器时就束手无措,另外也是为了程序上的兼容,所以我们接下来将介绍如何实现自己的定时器。

#include <stdio.h>
#include <stdlib.h>
#include <unistd.h>
#include <sys/time.h>
#include <signal.h>
#include <stdbool.h>
#include <string.h>
#include "list.h"
/**
* @file name: timer.c
* @author:xhc
* @date:2016.5.17
*
*/
#define MAX_TIMER_NUM 50

typedef void (*callback)(int id, void *data, int len);

typedef void (*SIG_FUNC)(int signo);

typedef struct timer{
	struct list_head node;
	int interval; /*timer interval(second)*/
	int elapse;   /*timer count*/
	
	callback cb; /*call back function*/
	void *user;  /*user data*/
	int len;
	int id; /*timerid*/
}timer_node_t;

struct timer_list{
	struct list_head head;
	int num;
	int size;
	void (*sighandler_old)(int);
	void (*sighandler)(int);
	struct itimerval ovalue;
	struct itimerval value;
};


/**
* Local data
*/
static struct timer_list *timer_list = NULL;


static void sig_func(int signo) 
{ 
	struct list_head *node;
	struct list_head *tmp;
	timer_node_t *timer;	
	if(list_empty(&timer_list->head) == true){
		return;
	}
	list_for_each_safe(node, tmp, &timer_list->head) {
		timer = list_entry(node, struct timer, node);
		timer->elapse++; 
                if(timer->elapse >= timer->interval) { 
                        timer->elapse = 0; 
                        timer->cb(timer->id, timer->user, timer->len); 
                }
	}
}
 
/**
*Create timer list
*@param
*/

struct timer_list *create_timer_list(int count) 
{ 
        int ret = 0; 
	struct timer_list *ptr = NULL;
	
        if(count <=0 || count > MAX_TIMER_NUM) { 
               printf("the timer max number too big, MAX num is %d.\n", MAX_TIMER_NUM); 
               return NULL; 
        } 
	
	ptr = (struct timer_list *)malloc(sizeof(struct timer_list));
        memset(ptr, 0, sizeof(struct timer_list)); 
        INIT_LIST_HEAD(&ptr->head);
        ptr->size = count;

        /* Register our internal signal handler and store old signal handler */ 
        if ((ptr->sighandler_old = signal(SIGALRM, sig_func)) == SIG_ERR) { 
                goto err_out; 
        } 
        ptr->sighandler = sig_func; 

   
        ptr->value.it_value.tv_sec = 1;  /*for firt timeout*/
        ptr->value.it_value.tv_usec = 0; 
        ptr->value.it_interval.tv_sec = 1; /*for tick reload*/
        ptr->value.it_interval.tv_usec = 0; 
        ret = setitimer(ITIMER_REAL, &ptr->value, &ptr->ovalue); 
	if (ret < 0)
		goto err_out;
        return ptr; 
err_out:
	printf("create_timer_list error\n");
	free(ptr);
	return NULL;
 } 


 /** 
 * Destroy the timer list. 
 * 
 * @return          0 means ok, the other means fail. 
 */ 
 int destroy_timer(struct timer_list *list) 
 { 
        struct timer *node = NULL; 

        signal(SIGALRM, list->sighandler_old);
        setitimer(ITIMER_REAL, &list->ovalue, &list->value);

	if(list_empty(&list->head) == false){
		struct list_head *node;
		struct list_head *tmp;
		timer_node_t *timer;	
		list_for_each_safe(node, tmp, &list->head) {
			timer = list_entry(node, struct timer, node);
			list_del(node); 
			free(timer->user);
			free(timer);
			timer = NULL;
		}
	}
        free(list);
        return 0; 
 }


/** 
 * 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          if == -1, add timer fail. 
 */ 
 int  add_timer(int interval, callback cb, void *user_data, int len) 
 { 
	struct timer *node = NULL; 

	if (cb == NULL || interval <= 0) { 
		return -1; 
	} 

	if(timer_list->num < timer_list->size) { 
		timer_list->num++; 
	} else { 
		return -1; 
	} 

	if((node = malloc(sizeof(struct timer))) == NULL) { 
		return -1; 
	} 
	if(user_data != NULL || len != 0) { 
		node->user = malloc(len); 
		memcpy(node->user, user_data, len); 
		node->len = len; 
	} 

	node->cb = cb; 
	node->interval = interval; 
	node->elapse = 0; 
	node->id =  timer_list->num; 

	list_add_tail(&node->node, &timer_list->head); 

	return node->id; 
}

 int  del_timer(int timer_id) 
 { 
	struct list_head *node;
	struct list_head *tmp;
	timer_node_t *timer;	
	
	if(timer_list == NULL) {
		return -1; 
	} 
	
	if(list_empty(&timer_list->head) == true){
		return -1;
	}
	
	list_for_each_safe(node, tmp, &timer_list->head) {
		timer = list_entry(node, struct timer, node);
		if (timer->id == timer_id){
			list_del(node); 
			free(timer->user);
			free(timer);
			return 0;
		}
		timer = NULL;
	}
	return -1;
}



/****************demo*******************/

void timer_test_callback(int id, void *data, int len)
{
	int *user = (int *)data;
	printf("timer id is :%d\n",id);
	printf("data is : %d\n", *user);
	printf("print this for every 5 sec!!!\n");
	user[0]++;
}

int main()
{
	int timer_id = -1;
	int count = 0;
	timer_list = create_timer_list(10);
	timer_id = add_timer(5, timer_test_callback, &count, 4);
	while(count++ < 20)
		sleep(1);
	del_timer(timer_id);
	destroy_timer(timer_list);
}

以上就是我们自己实现的timer管理器,还是有待优化的地方,比如采用排序链表,来优化查找复杂度,感兴趣的读者可以自己尝试修改。

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值