Linux内核源代码情景分析-nanosleep()和pause()

来源:互联网 发布:php timestamp 格式化 编辑:程序博客网 时间:2024/06/08 10:20

    我们介绍nanosleep()和pause()两个系统调用。

    系统调用nanosleep()在内核中的实现为sys_nanosleep(),代码如下:

asmlinkage long sys_nanosleep(struct timespec *rqtp, struct timespec *rmtp)//第一个指针rqtp指向给定所需睡眠时间的数据结构;第二个指针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)//由于时钟中断只能达到10毫秒的精度,如果要求睡眠的时间小于2毫秒,而要求睡眠的进程又是个实时要求的进程,那就不能真的让这个进程进入睡眠,因为那样有可能10毫秒以后才能将其唤醒,对于实时进程来说是不能接受的{/* * Short delay requests up to 2 ms will be handled with * high precision by a busy wait for all real-time processes. * * Its important on SMP not to do this holding locks. */udelay((t.tv_nsec + 999) / 1000);//延迟两秒return 0;}expire = timespec_to_jiffies(&t) + (t.tv_sec || t.tv_nsec);//将数据结构t中的数值换算成时钟中断的次数current->state = TASK_INTERRUPTIBLE;//将当期进程的状态设置为TASK_INTERRUPTIBLEexpire = schedule_timeout(expire);//让当期进程睡眠给定的时间;返回剩余的时钟中断次数,如果没有,返回0if (expire) {if (rmtp) {jiffies_to_timespec(expire, &t);//剩余的时钟中断次数转换成数据结构t的数值if (copy_to_user(rmtp, &t, sizeof(struct timespec)))//剩余时间从内核空间复制到用户空间return -EFAULT;}return -EINTR;}return 0;}

    

    schedule_timeout,让当期进程睡眠给定的时间,代码如下:

signed long schedule_timeout(signed long timeout){struct timer_list timer;unsigned long expire;switch (timeout){case MAX_SCHEDULE_TIMEOUT:/* * These two special cases are useful to be comfortable * in the caller. Nothing more. We could take * MAX_SCHEDULE_TIMEOUT from one of the negative value * but I' d like to return a valid offset (>=0) to allow * the caller to do everything it want with the retval. */schedule();//无限期等待goto out;default:/* * Another bit of PARANOID. Note that the retval will be * 0 since no piece of kernel is supposed to do a check * for a negative retval of schedule_timeout() (since it * should never happens anyway). You just have the printk() * that will tell you if something is gone wrong and where. */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;//timeout是把需要睡眠的时间先换算成时钟中断的次数,把这个次数与当前的jiffies相加就得到了"到点"的时间init_timer(&timer);timer.expires = expire;//初始化数据结构timertimer.data = (unsigned long) current;timer.function = process_timeout;add_timer(&timer);//将timer挂入定时器队列schedule();del_timer_sync(&timer);timeout = expire - jiffies;//剩余的时钟中断次数 out:return timeout < 0 ? 0 : timeout;}
struct timer_list {struct list_head list;unsigned long expires;unsigned long data;void (*function)(unsigned long);};


    add_timer,将timer挂入定时器队列,代码如下:

void add_timer(struct timer_list *timer){unsigned long flags;spin_lock_irqsave(&timerlist_lock, flags);if (timer_pending(timer))goto bug;internal_add_timer(timer);spin_unlock_irqrestore(&timerlist_lock, flags);return;bug:spin_unlock_irqrestore(&timerlist_lock, flags);printk("bug: kernel timer added twice at %p.\n",__builtin_return_address(0));}
static inline void internal_add_timer(struct timer_list *timer){/* * must be cli-ed when calling this */unsigned long expires = timer->expires;unsigned long idx = expires - timer_jiffies;//期望时间点于当前时间点之差,就是中间要经过的中断次数,为32位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;//第一个expires为256,i为1vec = tv2.vec + i;//所以tv2.vec[0]为空} 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) {/* can happen if you add a timer with expires == jiffies, * or you set a timer to go off in the past */vec = tv1.vec + tv1.index;} else if (idx <= 0xffffffffUL) {int i = (expires >> (TVR_BITS + 3 * TVN_BITS)) & TVN_MASK;vec = tv5.vec + i;} else {/* Can only get here on architectures with 64-bit jiffies */INIT_LIST_HEAD(&timer->list);return;}/* * Timers are FIFO! */list_add(&timer->list, vec->prev);}
#define TVN_BITS 6#define TVR_BITS 8#define TVN_SIZE (1 << TVN_BITS)#define TVR_SIZE (1 << TVR_BITS)#define TVN_MASK (TVN_SIZE - 1)#define TVR_MASK (TVR_SIZE - 1)struct timer_vec {int index;struct list_head vec[TVN_SIZE];};struct timer_vec_root {int index;struct list_head vec[TVR_SIZE];};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};
    数据结构tv1、tv2、...、tv5每个都包含了一个timer_list指针数组,这就是所谓杂凑表,表中的每个指针都指向一个定时器队列。其中tv1与其它几个数据结构的不同仅在于数组的大小,tv1的数组大小是2 8,而其它几个的大小都是2 ^ 6。这样队列中的数量是8 + 4 * (2 ^ 6) = 512个。

    idx为32位,如下图:


    如果idx小于2 ^ 8,以8位为下标链入数据结构tv1的vec[2 ^ 8]。

    如果idx大于2 ^ 8小于2 ^ 14,也就是idx为14位;以14位中前6位为下标链入数据结构tv1的vec[2 ^ 6],也就是说一个队列中最多会有256个定时器;因为相同的前6位,不同的后8位,一共有256种组合,都会链入一个队列(以前6位为下标)。

    以此类推......


    在Linux内核源代码情景分析-中断下半部(软中断),最后时钟中断处理程序的下半部,执行timer_bh,代码如下:

void timer_bh(void){update_times();run_timer_list();}
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) {//当tv1.index为0,根据tv2.index的指引将tv2中的一个队列搬运到tv1中int n = 1;do {cascade_timers(tvecs[n]);} while (tvecs[n]->index == 1 && ++n < NOOF_TVECS);//当tv2.index为1,根据tv3.index的指引将tv2中的一个队列搬运到tv2中}repeat:head = tv1.vec + tv1.index;//index共256个curr = head->next;if (curr != head) {//tv1的队列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);//process_timeout(current)spin_lock_irq(&timerlist_lock);timer_exit();goto repeat;}++timer_jiffies; tv1.index = (tv1.index + 1) & TVR_MASK;}spin_unlock_irq(&timerlist_lock);}
static inline void cascade_timers(struct timer_vec *tv){/* cascade all the timers from tv up one level */struct list_head *head, *curr, *next;head = tv->vec + tv->index;curr = head->next;/* * We are removing _all_ timers from the list, so we don't  have to * detach them individually, just clear the list afterwards. */while (curr != head) {struct timer_list *tmp;tmp = list_entry(curr, struct timer_list, list);next = curr->next;list_del(curr); // not neededinternal_add_timer(tmp);//链入到下一级的队列curr = next;}INIT_LIST_HEAD(head);tv->index = (tv->index + 1) & TVN_MASK;}
    tv1一共有256个队列,每个队列只链入了一个定时器。把它们都执行完后,也就是tv1.index再次为0,就把tv2->vec + tv2->index这个队列这个队列的定时器,放入tv1的256个队列。我们说过tv2队列中的定时器最多是256个,所以正好链入tv1的256个队列。当tv2.index再次为1(tv2->vec + 0这个队列为空),就把tv3->vec + tv3->index这个队列的定时器,放入tv2的128个队列。依次类推。


    每个被唤醒的定时器都会执行fn(data);//process_timeout(current),代码如下:

static void process_timeout(unsigned long __data){struct task_struct * p = (struct task_struct *) __data;wake_up_process(p);}
    被唤醒后,这个进程再次被调度执行(我们不关心在什么情况下,调度到这个进程),进程继续运行,返回schedule_timeout,又执行了一次del_timer_sync,这是因为该进程有可能被其他进程通过信号唤醒,且这个进程再次被调度执行。此事由于没有在run_timer_list执行detach_timer,所以这里补上一次。最后返回睡眠剩余的时间。


    pause的系统调用是,sys_pause,代码如下:

asmlinkage int sys_pause(void){current->state = TASK_INTERRUPTIBLE;schedule();return -ERESTARTNOHAND;}
    和sys_nanosleep的区别是只有在接受到信号时才会被唤醒,不会定时被唤醒。

    TASK_INTERRUPTIBLE和TASK_UNINTERRUPTIBLE最大的区别是是否在接受到信号,可以被唤醒。

0 0
原创粉丝点击