内核代码阅读(26) - sleep和定时器

sleep和pause

asmlinkage long sys_nanosleep(struct timespec *rqtp, struct timespec *rmtp)
    {
        struct timespec t;
        unsigned long expire;
        if(copy_from_user(&t, rqtp, sizeof(struct timespec)))
                return -EFAULT;
        if (t.tv_nsec >= 1000000000L || t.tv_nsec < 0 || t.tv_sec < 0)
                return -EINVAL;
        if (t.tv_sec == 0 && t.tv_nsec <= 2000000L &&
            current->policy != SCHED_OTHER)
        {
                udelay((t.tv_nsec + 999) / 1000);
                return 0;
        }
        expire = timespec_to_jiffies(&t) + (t.tv_sec || t.tv_nsec);
        current->state = TASK_INTERRUPTIBLE;
        expire = schedule_timeout(expire);
        if (expire) {
                if (rmtp) {
                        jiffies_to_timespec(expire, &t);
                        if (copy_to_user(rmtp, &t, sizeof(struct timespec)))
                                return -EFAULT;
                }
                return -EINTR;
        }
        return 0;
    }
1) 参数(struct timespec *rqtp, struct timespec *rmtp)
   rqtp是输入,即要睡眠的时间
   rmtp是输出,即还有多少时间没有睡眠。
2) if (t.tv_sec == 0 && t.tv_nsec <= 2000000L && current->policy != SCHED_OTHER)
   由于时钟中断的频率是100HZ,也就是时钟的精确度是10ms,而要求睡眠的时间小于2毫秒,并且是一个实时的进程,不能让这个进程进入睡眠,因为可能10毫秒之后醒来。
   这里是通过udelay,来消耗2毫秒对应的指令数。然后就完成了睡眠,返回到用户空间了。
3) expire = timespec_to_jiffies(&t) + (t.tv_sec || t.tv_nsec);
   把要睡眠的时间转换成时钟中断的次数。
4) current->state = TASK_INTERRUPTIBLE;
   更改状态。
5) expire = schedule_timeout(expire);
   显示触发一次的调度。
6) if (expire)
   进程已经完成了睡眠,expire是剩余的中断数。转换成时间拷贝到用户空间。

udelay 消耗时间对应的指令个数

#define udelay(n) (__builtin_constant_p(n) ? \
        ((n) > 20000 ? __bad_udelay() : __const_udelay((n) * 0x10c6ul)) : \
        __udelay(n))
static void __loop_delay(unsigned long loops)
    {
        int d0;
        __asm__ __volatile__(
                "\tjmp 1f\n"
                ".align 16\n"
                "1:\tjmp 2f\n"
                ".align 16\n"
                "2:\tdecl %0\n\tjns 2b"
                :"=&a" (d0)
                :"0" (loops));
    }
最于短时间的delay,通过空转CPU实现。
有些外设要求两次外设操作之间要间隔一定的时间。

schedule_timeout

signed long schedule_timeout(signed long timeout)
    {
        struct timer_list timer;
        unsigned long expire;
        switch (timeout)
        {
        case MAX_SCHEDULE_TIMEOUT:
                schedule();
                goto out;
        default:
                if (timeout < 0)
                {
                        printk(KERN_ERR "schedule_timeout: wrong timeout "
                               "value %lx from %p\n", timeout,
                               __builtin_return_address(0));
                        current->state = TASK_RUNNING;
                        goto out;
                }
        }
        expire = timeout + jiffies;
        init_timer(&timer);
        timer.expires = expire;
        timer.data = (unsigned long) current;
        timer.function = process_timeout;
        add_timer(&timer);
        schedule();
        del_timer_sync(&timer);
        timeout = expire - jiffies;
    out:
        return timeout < 0 ? 0 : timeout;
    }
1) add_timer(&timer);
   定时器的插入,在每次时钟中断来了后,要能够快速的找到应该处理的定时器,也就是到时时间是当前的jiffies。
2) schedule();
   主动触发一次调度,当前进程在内核态就在schedule里面切换到了另外一个进程了。
   即,当前进程在执行到schedule被切换走了,等到被唤醒才能接着执行。
3) timeout = expire - jiffies;
   执行到这条指令,说明进程已经被唤醒了。计算出剩余的时间并且返回。
   实际上这个进程已经经历了从运行到调度出去,有回到运行的过程。
4) del_timer_sync(&timer);
   进程被唤醒后把timer从全局的定时器桶中删除。
   定时器在时钟中断已经删除了一次,这个地方为什么还是删除呢?原因进程可能收到了信号,提前醒来了。定时器桶没有机会在中断的驱动下被删除。

添加一个定时器到内核的定时器队列中 - 定时器的数据结构和算法

static inline void internal_add_timer(struct timer_list *timer)
    {
        unsigned long expires = timer->expires;
        unsigned long idx = expires - timer_jiffies;
        struct list_head * vec;
        if (idx < TVR_SIZE) {
                int i = expires & TVR_MASK;
                vec = tv1.vec + i;
        } else if (idx < 1 << (TVR_BITS + TVN_BITS)) {
                int i = (expires >> TVR_BITS) & TVN_MASK;
                vec = tv2.vec + i;
        } else if (idx < 1 << (TVR_BITS + 2 * TVN_BITS)) {
                int i = (expires >> (TVR_BITS + TVN_BITS)) & TVN_MASK;
                vec =  tv3.vec + i;
        } else if (idx < 1 << (TVR_BITS + 3 * TVN_BITS)) {
                int i = (expires >> (TVR_BITS + 2 * TVN_BITS)) & TVN_MASK;
                vec = tv4.vec + i;
        } else if ((signed long) idx < 0) {
                vec = tv1.vec + tv1.index;
        } else if (idx <= 0xffffffffUL) {
                int i = (expires >> (TVR_BITS + 3 * TVN_BITS)) & TVN_MASK;
                vec = tv5.vec + i;
        } else {
                INIT_LIST_HEAD(&timer->list);
                return;
        }
        list_add(&timer->list, vec->prev);
    }
首先在内核中把要到点的时间转换成jiffies来处理。
如何来维护定时器,保证在时间点到了后快速的找到要处理的定时器呢?
内核将32位的expire分解成了 6 + 6 + 6 + 6 + 8 一共5hash表来维护。
v1是最早到时的定时器,v2次之。每次时钟中断来了之后,都从v1中取定时器,并把v1的index往下面移动一位。如果index移动了256次,则从v2中取一批定时器,如果v2的index也走完了64次,则从更高级别的v3中取一批。
这个过程很像进位制,低位的定时器消耗完了,从高位的取一批定时器,把这批定时器分散到低位的操位里。
struct timer_vec {
        int index;
        struct list_head vec[6];
    };
    struct timer_vec_root {
        int index;
        struct list_head vec[8];
    };
    static struct timer_vec tv5;
    static struct timer_vec tv4;
    static struct timer_vec tv3;
    static struct timer_vec tv2;
    static struct timer_vec_root tv1;
    static struct timer_vec * const tvecs[] = {
        (struct timer_vec *)&tv1, &tv2, &tv3, &tv4, &tv5
    };

内核定时器队列的消费 - 时钟中断对定时器的处理

void timer_bh(void)
    {
        update_times();
        run_timer_list();
   }

时钟中断的bottom half处理了定时的列表。

run_timer_list

这个函数是定时器的全部逻辑,共做了两件事情:
 1) 每当低位的定时器桶走完了256格,或者64格,就从高位‘借位’。
 2) 对指针tv1.index所指向的定时器桶,遍历处理。
static inline void run_timer_list(void)
    {
        spin_lock_irq(&timerlist_lock);
        while ((long)(jiffies - timer_jiffies) >= 0) {
                struct list_head *head, *curr;
                if (!tv1.index) {
                        int n = 1;
                        do {
                                cascade_timers(tvecs[n]);
                        } while (tvecs[n]->index == 1 && ++n < NOOF_TVECS);
                }
    repeat:
                head = tv1.vec + tv1.index;
                curr = head->next;
                if (curr != head) {
                        struct timer_list *timer;
                        void (*fn)(unsigned long);
                        unsigned long data;
                        timer = list_entry(curr, struct timer_list, list);
                         fn = timer->function;
                         data= timer->data;
                        detach_timer(timer);
                        timer->list.next = timer->list.prev = NULL;
                        timer_enter(timer);
                        spin_unlock_irq(&timerlist_lock);
                        fn(data);
                        spin_lock_irq(&timerlist_lock);
                        timer_exit();
                        goto repeat;
                }
                ++timer_jiffies; 
                tv1.index = (tv1.index + 1) & TVR_MASK;
        }
        spin_unlock_irq(&timerlist_lock);
    }
1) if (!tv1.index) {
            int n = 1;
            do {
                    cascade_timers(tvecs[n]);
            } while (tvecs[n]->index == 1 && ++n < NOOF_TVECS);
    }
   每当tv1.index走到0的时候,就从更高位的桶里获取定时器。
2) head = tv1.vec + tv1.index;
   处理指针tv1.index所指向的定时器桶。
3) if (curr != head)
   如果tv1.index所指向的定时器列表非空,则处理所有者个桶上的定时器。

定时器对睡眠进程的唤醒process_timeout

在定时器到时后,会调用schedule_timeout设置的回调函数process_timeout
static void process_timeout(unsigned long __data)
    {
        struct task_struct * p = (struct task_struct *) __data;
        wake_up_process(p);
    }
至此,实现了进程的到时唤醒。把目标进程加入了runqueue中。
上一篇:C10M - The Kernel is the Problem, Not the Solution


下一篇:@SuppressWarnings注解作用与用法详解