Chinaunix首页 | 论坛 | 博客
  • 博客访问: 153558
  • 博文数量: 115
  • 博客积分: 0
  • 博客等级: 民兵
  • 技术积分: 10
  • 用 户 组: 普通用户
  • 注册时间: 2016-11-28 14:16
文章分类

全部博文(115)

文章存档

2017年(36)

2016年(79)

我的朋友

分类: LINUX

2017-03-12 17:08:58

   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;
}
 
阅读(1067) | 评论(0) | 转发(0) |
给主人留下些什么吧!~~