当前位置: 技术问答>linux和unix
求助!关于linux内核0.11版本进程睡眠调度的疑问?
来源: 互联网 发布时间:2016-11-21
本文导语: 本人刚开始学linux内核,由于基础太差就从赵炯编写的《linux内核完全注释》开始看,其中关于sched.c中的代码sleep_on (struct task_struct **p)与interruptible_sleep_on (struct task_struct **p)两个函数,其中sleep_on中是否应该加*p=tmp...
本人刚开始学linux内核,由于基础太差就从赵炯编写的《linux内核完全注释》开始看,其中关于sched.c中的代码sleep_on (struct task_struct **p)与interruptible_sleep_on (struct task_struct **p)两个函数,其中sleep_on中是否应该加*p=tmp与wake_up中的*p=NULL是否应该去掉?遗留等待队列头指针有何用?
还有这个版本的内核好像没有把可中断等待任务队列与不可中断任务等待队列分开,我不知道是否应该有两个队列,难道将两个队列和合在一起,但如果其中有一个是可中断的,那么好像只要有一个软中断信号发给它,它就能连不可中断的等待任务一起唤醒。我不知道算不算是这一版本的bug。如果有懂的朋友或熟悉最新版本内核的朋友恳请指导一下,
非常感谢,代码如下。
void
schedule (void)
{
int i, next, c;
struct task_struct **p; // 任务结构指针的指针。
/* check alarm, wake up any interruptible tasks that have got a signal */
/* 检测alarm(进程的报警定时值),唤醒任何已得到信号的可中断任务 */
// 从任务数组中最后一个任务开始检测alarm。
for (p = &LAST_TASK; p > &FIRST_TASK; --p)
if (*p)
{
// 如果任务的alarm 时间已经过期(alarmalarm && (*p)->alarm signal |= (1 signal & ~(_BLOCKABLE & (*p)->blocked)) &&
(*p)->state == TASK_INTERRUPTIBLE)
(*p)->state = TASK_RUNNING; //置为就绪(可执行)状态。
}
/* this is the scheduler proper: */
/* 这里是调度程序的主要部分 */
while (1)
{
c = -1;
next = 0;
i = NR_TASKS;
p = &task[NR_TASKS];
// 这段代码也是从任务数组的最后一个任务开始循环处理,并跳过不含任务的数组槽。比较每个就绪
// 状态任务的counter(任务运行时间的递减滴答计数)值,哪一个值大,运行时间还不长,next 就
// 指向哪个的任务号。
while (--i)
{
if (!*--p)
continue;
if ((*p)->state == TASK_RUNNING && (*p)->counter > c)
c = (*p)->counter, next = i;
}
// 如果比较得出有counter 值大于0 的结果,则退出124 行开始的循环,执行任务切换(141 行)。
if (c)
break;
// 否则就根据每个任务的优先权值,更新每一个任务的counter 值,然后回到125 行重新比较。
// counter 值的计算方式为counter = counter /2 + priority。[右边counter=0??]
for (p = &LAST_TASK; p > &FIRST_TASK; --p)
if (*p)
(*p)->counter = ((*p)->counter >> 1) + (*p)->priority;
}
switch_to (next); // 切换到任务号为next 的任务,并运行之。
}
//// pause()系统调用。转换当前任务的状态为可中断的等待状态,并重新调度。
// 该系统调用将导致进程进入睡眠状态,直到收到一个信号。该信号用于终止进程或者使进程调用
// 一个信号捕获函数。只有当捕获了一个信号,并且信号捕获处理函数返回,pause()才会返回。
// 此时pause()返回值应该是-1,并且errno 被置为EINTR。这里还没有完全实现(直到0.95 版)。
int
sys_pause (void)
{
current->state = TASK_INTERRUPTIBLE;
schedule ();
return 0;
}
// 把当前任务置为不可中断的等待状态,并让睡眠队列头的指针指向当前任务。
// 只有明确地唤醒时才会返回。该函数提供了进程与中断处理程序之间的同步机制。
// 函数参数*p 是放置等待任务的队列头指针。(参见列表后的说明)。
void
sleep_on (struct task_struct **p)
{
struct task_struct *tmp;
// 若指针无效,则退出。(指针所指的对象可以是NULL,但指针本身不会为0)。
if (!p)
return;
if (current == &(init_task.task)) // 如果当前任务是任务0,则死机(impossible!)。
panic ("task[0] trying to sleep");
tmp = *p;
// 让tmp 指向已经在等待队列上的任务(如果有的话)。
*p = current;
// 将睡眠队列头的等待指针指向当前任务。
current->state = TASK_UNINTERRUPTIBLE;
// 将当前任务置为不可中断的等待状态。
schedule (); // 重新调度。
// 只有当这个等待任务被唤醒时,调度程序才又返回到这里,则表示进程已被明确地唤醒。
// 既然大家都在等待同样的资源,那么在资源可用时,就有必要唤醒所有等待该资源的进程。该函数
// 嵌套调用,也会嵌套唤醒所有等待该资源的进程。然后系统会根据这些进程的优先条件,重新调度
// 应该由哪个进程首先使用资源。也即让这些进程竞争上岗。
//*p=tmp; //应该加?
if (tmp)
// 若还存在等待的任务,则也将其置为就绪状态(唤醒)。
tmp->state = 0;
}
// 将当前任务置为可中断的等待状态,并放入*p 指定的等待队列中。参见列表后对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 ();
// 如果等待队列中还有等待任务,并且队列头指针所指向的任务不是当前任务时,则将该等待任务置为
// 可运行的就绪状态,并重新执行调度程序。当指针*p 所指向的不是当前任务时,表示在当前任务被放
// 入队列后,又有新的任务被插入等待队列中,因此,既然本任务是可中断的,就应该首先执行所有
// 其它的等待任务。
if (*p && *p != current)
{
(**p).state = 0;
goto repeat;
}
// 下面一句代码有误,应该是*p = tmp,让队列头指针指向其余等待任务,否则在当前任务之前插入
// 等待队列的任务均被抹掉了。参见图4.3。
*p = NULL;
if (tmp)
tmp->state = 0;
}
// 唤醒指定任务*p。
void
wake_up (struct task_struct **p)
{
if (p && *p)
{
(**p).state = 0; // 置为就绪(可运行)状态。
*p = NULL;// 去掉?
}
}
还有这个版本的内核好像没有把可中断等待任务队列与不可中断任务等待队列分开,我不知道是否应该有两个队列,难道将两个队列和合在一起,但如果其中有一个是可中断的,那么好像只要有一个软中断信号发给它,它就能连不可中断的等待任务一起唤醒。我不知道算不算是这一版本的bug。如果有懂的朋友或熟悉最新版本内核的朋友恳请指导一下,
非常感谢,代码如下。
void
schedule (void)
{
int i, next, c;
struct task_struct **p; // 任务结构指针的指针。
/* check alarm, wake up any interruptible tasks that have got a signal */
/* 检测alarm(进程的报警定时值),唤醒任何已得到信号的可中断任务 */
// 从任务数组中最后一个任务开始检测alarm。
for (p = &LAST_TASK; p > &FIRST_TASK; --p)
if (*p)
{
// 如果任务的alarm 时间已经过期(alarmalarm && (*p)->alarm signal |= (1 signal & ~(_BLOCKABLE & (*p)->blocked)) &&
(*p)->state == TASK_INTERRUPTIBLE)
(*p)->state = TASK_RUNNING; //置为就绪(可执行)状态。
}
/* this is the scheduler proper: */
/* 这里是调度程序的主要部分 */
while (1)
{
c = -1;
next = 0;
i = NR_TASKS;
p = &task[NR_TASKS];
// 这段代码也是从任务数组的最后一个任务开始循环处理,并跳过不含任务的数组槽。比较每个就绪
// 状态任务的counter(任务运行时间的递减滴答计数)值,哪一个值大,运行时间还不长,next 就
// 指向哪个的任务号。
while (--i)
{
if (!*--p)
continue;
if ((*p)->state == TASK_RUNNING && (*p)->counter > c)
c = (*p)->counter, next = i;
}
// 如果比较得出有counter 值大于0 的结果,则退出124 行开始的循环,执行任务切换(141 行)。
if (c)
break;
// 否则就根据每个任务的优先权值,更新每一个任务的counter 值,然后回到125 行重新比较。
// counter 值的计算方式为counter = counter /2 + priority。[右边counter=0??]
for (p = &LAST_TASK; p > &FIRST_TASK; --p)
if (*p)
(*p)->counter = ((*p)->counter >> 1) + (*p)->priority;
}
switch_to (next); // 切换到任务号为next 的任务,并运行之。
}
//// pause()系统调用。转换当前任务的状态为可中断的等待状态,并重新调度。
// 该系统调用将导致进程进入睡眠状态,直到收到一个信号。该信号用于终止进程或者使进程调用
// 一个信号捕获函数。只有当捕获了一个信号,并且信号捕获处理函数返回,pause()才会返回。
// 此时pause()返回值应该是-1,并且errno 被置为EINTR。这里还没有完全实现(直到0.95 版)。
int
sys_pause (void)
{
current->state = TASK_INTERRUPTIBLE;
schedule ();
return 0;
}
// 把当前任务置为不可中断的等待状态,并让睡眠队列头的指针指向当前任务。
// 只有明确地唤醒时才会返回。该函数提供了进程与中断处理程序之间的同步机制。
// 函数参数*p 是放置等待任务的队列头指针。(参见列表后的说明)。
void
sleep_on (struct task_struct **p)
{
struct task_struct *tmp;
// 若指针无效,则退出。(指针所指的对象可以是NULL,但指针本身不会为0)。
if (!p)
return;
if (current == &(init_task.task)) // 如果当前任务是任务0,则死机(impossible!)。
panic ("task[0] trying to sleep");
tmp = *p;
// 让tmp 指向已经在等待队列上的任务(如果有的话)。
*p = current;
// 将睡眠队列头的等待指针指向当前任务。
current->state = TASK_UNINTERRUPTIBLE;
// 将当前任务置为不可中断的等待状态。
schedule (); // 重新调度。
// 只有当这个等待任务被唤醒时,调度程序才又返回到这里,则表示进程已被明确地唤醒。
// 既然大家都在等待同样的资源,那么在资源可用时,就有必要唤醒所有等待该资源的进程。该函数
// 嵌套调用,也会嵌套唤醒所有等待该资源的进程。然后系统会根据这些进程的优先条件,重新调度
// 应该由哪个进程首先使用资源。也即让这些进程竞争上岗。
//*p=tmp; //应该加?
if (tmp)
// 若还存在等待的任务,则也将其置为就绪状态(唤醒)。
tmp->state = 0;
}
// 将当前任务置为可中断的等待状态,并放入*p 指定的等待队列中。参见列表后对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 ();
// 如果等待队列中还有等待任务,并且队列头指针所指向的任务不是当前任务时,则将该等待任务置为
// 可运行的就绪状态,并重新执行调度程序。当指针*p 所指向的不是当前任务时,表示在当前任务被放
// 入队列后,又有新的任务被插入等待队列中,因此,既然本任务是可中断的,就应该首先执行所有
// 其它的等待任务。
if (*p && *p != current)
{
(**p).state = 0;
goto repeat;
}
// 下面一句代码有误,应该是*p = tmp,让队列头指针指向其余等待任务,否则在当前任务之前插入
// 等待队列的任务均被抹掉了。参见图4.3。
*p = NULL;
if (tmp)
tmp->state = 0;
}
// 唤醒指定任务*p。
void
wake_up (struct task_struct **p)
{
if (p && *p)
{
(**p).state = 0; // 置为就绪(可运行)状态。
*p = NULL;// 去掉?
}
}
|
sleep_on中的*p=tmp是合理的。可以这样理解,*p代表了一个等待队列,一直指向队列的头部。其实这里有一个bug。比如,当一个进程被唤醒时马上又调用sleep_on,这时等待队列会发生混乱,引起错误。0.12内核修正了这一点。
wake_up中的*p=NULL,完全没有用。0.12内核去掉了。
可中断和不可中断确实可以在一个等待队列中,并且有信号是都会被唤醒。
但是,关键*p是什么?*p是由用户提供的结构(如buffer_head中的b_wait),所以只要用户正确的调用这两个内核函数,就不会出现上面的错误,否则由用户自己负责。
wake_up中的*p=NULL,完全没有用。0.12内核去掉了。
可中断和不可中断确实可以在一个等待队列中,并且有信号是都会被唤醒。
但是,关键*p是什么?*p是由用户提供的结构(如buffer_head中的b_wait),所以只要用户正确的调用这两个内核函数,就不会出现上面的错误,否则由用户自己负责。