linux 定时器的实现及其应用 (2.6.11)
1, 概念
定时器分为三种,分别是:
*真实间隔定时器(ITIMER_REAL)
*虚拟间隔定时器(ITIMER_VIRTUAL)
*PROF间隔定时器(ITIMER_PROF)
*真实间隔定时器(ITIMER_REAL):这种间隔定时器在启动后,不管进程是否运行,每个时钟滴答都将其间隔计数器减1。当减到0值时,内核向进程发送SIGALRM信号。结构类型task_struct中的成员it_real_incr则表示真实间隔定时器的间隔计数器的初始值,而成员 it_real_value则表示真实间隔定时器的间隔计数器的当前值。由于这种间隔定时器本质上与上一节的内核定时器时一样的,因此Linux实际上是通过real_timer这个内嵌在task_struct结构中的内核动态定时器来实现真实间隔定时器ITIMER_REAL的。
*虚拟间隔定时器ITIMER_VIRT:也称为进程的用户态间隔定时器。结构类型task_struct中成员it_virt_incr和 it_virt_value分别表示虚拟间隔定时器的间隔计数器的初始值和当前值,二者均以时钟滴答次数位计数单位。当虚拟间隔定时器启动后,只有当进程在用户态下运行时,一次时钟滴答才能使间隔计数器当前值it_virt_value减1。当减到0值时,内核向进程发送SIGVTALRM信号(虚拟闹钟信号),并将it_virt_value重置为初值it_virt_incr。具体请do_it_virt()函数的实现。
*PROF间隔定时器ITIMER_PROF:进程的task_struct结构中的it_prof_value和it_prof_incr成员分别表示PROF间隔定时器的间隔计数器的当前值和初始值(均以时钟滴答为单位)。当一个进程的PROF间隔定时器启动后,则只要该进程处于运行中,而不管是在用户态或核心态下执行,每个时钟滴答都使间隔计数器it_prof_value值减1。当减到0值时,内核向进程发送SIGPROF信号,并将 it_prof_value重置为初值it_prof_incr。具体请见do_it_prof()函数。
2, 数据结构
struct timeval {
time_t tv_sec; /* seconds */
suseconds_t tv_usec; /* microseconds */
};
struct itimerval {
struct timeval it_interval; /* timer interval */ //间隔计时器的初始值
struct timeval it_value; /* current value */ //间隔定时器的当前值
};
struct timer_list {
struct list_head entry; //定时器链表
unsigned long expires; //终止时间
spinlock_t lock;
unsigned long magic;
void (*function)(unsigned long); //定时器终止时执行的函数
unsigned long data;
struct tvec_t_base_s *base; //定时器基础设施
};
//与进程相关的定时器结构
//成员it_real_incr则表示真实间隔定时器的间隔计数器的初始值,
//成员 it_real_value则表示真实间隔定时器的间隔计数器的当前值。
struct task_struct {
...
unsigned long rt_priority;
unsigned long it_real_value, it_real_incr;
cputime_t it_virt_value, it_virt_incr;
cputime_t it_prof_value, it_prof_incr;
struct timer_list real_timer;
...
}
3, 实现
*初始化
#define INIT_TASK(tsk) \
...
.real_timer = { \
.function = it_real_fn \
},
...
}
//该函数设定制定的间隔定时器,并且返回间隔定时器的原来的值。
asmlinkage long sys_setitimer(int which,
struct itimerval __user *value,
struct itimerval __user *ovalue)
{
struct itimerval set_buffer, get_buffer;
int error;
// 若value指针非空,把该值从用户空间复制到内核空间的set_buffer变量中
// 若value指针为空,把变量set_buffer清空
if (value) {
if(copy_from_user(&set_buffer, value, sizeof(set_buffer)))
return -EFAULT;
} else
memset((char *) &set_buffer, 0, sizeof(set_buffer));
//若ovalue指针不为NULL,把内核变量get_buffer的指针值作为第三个参数传入
error = do_setitimer(which, &set_buffer, ovalue ? &get_buffer : NULL);
if (error || !ovalue)
return error;
//若get_buffer指针不为NULL,必须把它的值复制到用户空间变量ovalue
if (copy_to_user(ovalue, &get_buffer, sizeof(get_buffer)))
return -EFAULT;
return 0;
}
int do_setitimer(int which, struct itimerval *value, struct itimerval *ovalue)
{
unsigned long expire;
cputime_t cputime;
int k;
if (ovalue && (k = do_getitimer(which, ovalue)) < 0)
return k;
switch (which) {
case ITIMER_REAL:
//从真实间隔定时器链表中删除原来的间隔定时器
del_timer_sync(¤t->real_timer);
//计算定时器终止时间
expire = timeval_to_jiffies(&value->it_value);
//把计算出来的终止时间赋给it_real_value,表示当前值
current->it_real_value = expire;
current->it_real_incr =
timeval_to_jiffies(&value->it_interval);
//value->it_value为0,只是删除老定时器,更新目前进程相关的结构的值
if (!expire)
break;
if (expire > (unsigned long) LONG_MAX)
expire = LONG_MAX;
current->real_timer.expires = jiffies + expire; //更新终止时间
add_timer(¤t->real_timer); //把该定时器加入到定时器动态链表中
break;
...
}
//获取定时器剩余的描述
int do_getitimer(int which, struct itimerval *value)
{
register unsigned long val;
switch (which) {
case ITIMER_REAL:
val = 0;
/*
* FIXME! This needs to be atomic, in case the kernel timer happens!
*/
if (timer_pending(¤t->real_timer)) { //检测定时器是否已经启动
val = current->real_timer.expires - jiffies; //计算定时器当前剩余时间
/* look out for negative/zero itimer.. */
if ((long) val <= 0) //不满1,补足1
val = 1;
}
jiffies_to_timeval(val, &value->it_value); //转换,并保存计算出来的时间值
jiffies_to_timeval(current->it_real_incr, &value->it_interval); //直接保存初始值
break;
...
default:
return(-EINVAL);
}
return 0;
}
void it_real_fn(unsigned long __data)
{
struct task_struct * p = (struct task_struct *) __data; //获得进程描述符指针
unsigned long interval;
send_group_sig_info(SIGALRM, SEND_SIG_PRIV, p); //发送SIGALRM信号
interval = p->it_real_incr;
if (interval) { //若it_real_incr不为0,重启定时器,把定时器再次加入到定时器链表中
if (interval > (unsigned long) LONG_MAX)
interval = LONG_MAX;
//重启后的终止时间设定为现在的值+interval的值
p->real_timer.expires = jiffies + interval;
add_timer(&p->real_timer); //把定时器再次加入到动态链表
}
}
// alarm系统调用的实现
asmlinkage unsigned long sys_alarm(unsigned int seconds)
{
struct itimerval it_new, it_old;
unsigned int oldalarm;
//alarm不会重复启动定时器,这必须把it_interval设置成0
it_new.it_interval.tv_sec = it_new.it_interval.tv_usec = 0;
it_new.it_value.tv_sec = seconds; //设置定时器终止秒数
it_new.it_value.tv_usec = 0;
//添加ITIMER_REAL定时器,并获取老定时器的值
do_setitimer(ITIMER_REAL, &it_new, &it_old);
oldalarm = it_old.it_value.tv_sec;
/* ehhh.. We can't return 0 if we have an alarm pending.. */
/* And we'd better return too much than too little anyway */
//不足1秒补足1秒
if ((!oldalarm && it_old.it_value.tv_usec) || it_old.it_value.tv_usec >= 500000)
oldalarm++;
return oldalarm;
}
可以看出若alarm的参数为0,系统将删除老的定时器,并把现在的定时器的值设定为0,而不会发送信号。
也就是说alarm(0),就是取消以前设定的老的定时器。
3.1 说明
我这里分析的事2.6.11版本的,在后来的版本定时器的实现发生了很大的变化,
在高精定时器的实现中,使用了红黑树的数据结构来组织定时器结构,使得查询和添加删除操作的效率
得到了很大的提高。
4, 应用
settimer和alarm共享一个定时器,所以这两个系统调用不能同时使用。
void handle_timer(int sig)
{
if (SIGALRM == sig)
fprintf(stderr, "get SIGALARM SIG\n");
}
int
main(void)
{
char str[1024];
int readn;
struct sigaction sa;
//这里要注意不能使用signal来注册信号,
//原因见Linux信号的实现机制分析。
sa.sa_flags = SA_SIGINFO;
sigemptyset(&sa.sa_mask);
sa.sa_handler = handle_timer;
sigaction(SIGALRM, &sa, NULL);
for ( ; ; ) {
memset(str, 0, sizeof(str));
fprintf(stderr, "please input int number:\n");
alarm(3);
readn = read(STDIN_FILENO, str, sizeof(str)-1);
if (-1 == readn)
fprintf(stderr, "error =[%s]\n", strerror(errno));
fprintf(stderr, "you input: %s\n", str);
alarm(0);
}
return 0;
}