sleep_on()函数主要是为了在一个进程所请求的资源忙的时候进行进程调度,并把该进程放到等待队列上等待一段时间。代码如下:
void sleep_on(struct task_struct **p) { struct task_struct *tmp; if (!p) return; if (current == &(init_task.task)) panic("task[0] trying to sleep"); tmp = *p; *p = current; current->state = TASK_UNINTERRUPTIBLE; schedule(); if (tmp) tmp->state=0; }
分析:首先应该理解下这个tmp的作用,在这段函数中主要用到三个指针:*p,current和tmp,其中*p指向等待队列的头,也就是最近被放入等待队列的任务,current指向当前任务,tmp是在任务内核堆栈上建立的一个临时指针。在sleep的入口处,会让
tmp = *p;
*p= current;
这样的话tmp指向原来的头,*p指向新的头,这样就相当于串联起一个链表一样的结构,通过*p可以找到第一个等待任务,再通过第一个任务里面的tmp结构又可以找到下一个等待任务。下图可以帮助理解:
之后执行current->state= TASK_UNINTERRUPTIBLE,将当前任务的状态改为TASK_UNINTERRUPTIBLE后进行任务调度,在执行任务调度的时候,当前的执行环境都会被保存起来,也就是后面的
if (tmp)
tmp->state=0;
还不会执行,直到当前任务被唤醒之后,才会执行,因为每个等待队列中都保存了这个指向环境,当等待队列头被唤醒后,就会执行上面的代码,而tmp指向下一个等待任务,这样下一个也被唤醒了,一次类推,整个等待队列中的任务都被唤醒,这里的唤醒指进程状态编程可执行。
另一个睡眠代码是interruptible_sleep_on(),调用这个函数的进程的休眠状态可以被信号唤醒。代码如下:
void interruptible_sleep_on(struct task_struct **p) { struct task_struct *tmp; if (!p) return; if (current == &(init_task.task)) panic("task[0] trying to sleep"); tmp=*p; *p=current; repeat: current->state = TASK_INTERRUPTIBLE; schedule(); if (*p && *p != current) { (**p).state=0; goto repeat; } *p=NULL; if (tmp) tmp->state=0; }
在这个代码里面,多了一个判断,if (*p && *p != current),使用这个来判断当前进程是不是等待队列的头,如果不是则将当前进程的状态置为TASK_INTERRUPTIBLE,然后重新进行调度。
说下我的理解,在sleep_on中进程的状态被置为TASK_UNINTERRUPTIBLE,这样的话该等待队列的进程都不能被信号唤醒,只有调用了wake_up才可以,这样的话必定唤醒的是等待队列的头,而在interruptible_sleep_on中,进程的状态会被设置为TASK_INTERRUPTIBLE,这样的话等待队列中的某个进程就能够被信号量唤醒,它可能不是头,假如它被执行,那链接在它后面的睡眠进程都将被唤醒,而它之前的也在等待队列中的进程却没有被唤醒,而前一个等待进程的tmp仍旧指向这个刚被唤醒的进程,这样应该算错误了,所以只能从头结点开始。
wake_up的代码如下:
void wake_up(struct task_struct **p) { if (p && *p) { (**p).state=0; *p=NULL; } }
它是从等待队列的头结点开始,将第一个等待进程的状态置为可运行。