父进程执行wait4,并调用schedule切换到子进程:
wait4(child, NULL, 0, NULL);
像其他系统调用一样,wait4()在内核中的入口是sys_wait4(),代码如下:
asmlinkage long sys_wait4(pid_t pid,unsigned int * stat_addr, int options, struct rusage * ru)//pid为子进程的进程号 { int flag, retval; DECLARE_WAITQUEUE(wait, current); struct task_struct *tsk; if (options & ~(WNOHANG|WUNTRACED|__WNOTHREAD|__WCLONE|__WALL)) return -EINVAL; add_wait_queue(current->wait_chldexit,&wait); repeat: flag = 0; current->state = TASK_INTERRUPTIBLE;//父进程设置为可中断等待状态 read_lock(&tasklist_lock); tsk = current; do {//第一层循环 struct task_struct *p; for (p = tsk->p_cptr ; p ; p = p->p_osptr) {//第二层循环,从最年轻的子进程开始沿着由各个task_struct结构中的指针p_osptr所形成的链,找寻与所等待对象的pid相符的子进程、或符合其他一些条件的子进程 if (pid>0) { if (p->pid != pid)//找到pid相符的子进程 continue; } else if (!pid) { if (p->pgrp != current->pgrp) continue; } else if (pid != -1) { if (p->pgrp != -pid) continue; } /* Wait for all children (clone and not) if __WALL is set; * otherwise, wait for clone children *only* if __WCLONE is * set; otherwise, wait for non-clone children *only*. (Note: * A "clone" child here is one that reports to its parent * using a signal other than SIGCHLD.) */ if (((p->exit_signal != SIGCHLD) ^ ((options & __WCLONE) != 0)) && !(options & __WALL)) continue; flag = 1;//说明pid是当前进程的子进程号 switch (p->state) { case TASK_STOPPED: if (!p->exit_code) continue; if (!(options & WUNTRACED) && !(p->ptrace & PT_PTRACED)) continue; read_unlock(&tasklist_lock); retval = ru ? getrusage(p, RUSAGE_BOTH, ru) : 0; if (!retval && stat_addr) retval = put_user((p->exit_code << 8) | 0x7f, stat_addr); if (!retval) { p->exit_code = 0; retval = p->pid; } goto end_wait4;//子进程处于停止状态,goto end_wait4 case TASK_ZOMBIE: current->times.tms_cutime += p->times.tms_utime + p->times.tms_cutime; current->times.tms_cstime += p->times.tms_stime + p->times.tms_cstime; read_unlock(&tasklist_lock); retval = ru ? getrusage(p, RUSAGE_BOTH, ru) : 0; if (!retval && stat_addr) retval = put_user(p->exit_code, stat_addr); if (retval) goto end_wait4; retval = p->pid; if (p->p_opptr != p->p_pptr) { write_lock_irq(&tasklist_lock); REMOVE_LINKS(p); p->p_pptr = p->p_opptr; SET_LINKS(p); do_notify_parent(p, SIGCHLD); write_unlock_irq(&tasklist_lock); } else release_task(p); goto end_wait4;////子进程处于僵死状态,goto end_wait4 default: continue;//否则继续第二层循环 } } if (options & __WNOTHREAD) break; tsk = next_thread(tsk);//从同一个thread_group队列中找到下一个线程的task_struct结构 } while (tsk != current); read_unlock(&tasklist_lock); if (flag) {//如果pid不是当前进程的子进程,直接到end_wait4 retval = 0; if (options & WNOHANG) goto end_wait4; retval = -ERESTARTSYS; if (signal_pending(current)) goto end_wait4; schedule(); goto repeat; } retval = -ECHILD; end_wait4: current->state = TASK_RUNNING; remove_wait_queue(¤t->wait_chldexit,&wait); return retval; }
下列条件之一得到满足时才结束,goto end_wait4:
1、所等待的子进程的状态变成TASK_STOPPED,TASK_ZOMBIE;
2、所等待的子进程存在,可不在上述两个状态,而调用参数options中的WHONANG标志位为1,或者当前进程接受到了其他的信号;
3、进程号pid的那个进程根本不存在,或者不是当前进程的子进程。
否则,当前进程将其自身的状态设成TASK_INTERRUPTIBLE,并调用schedule()。
schedule,代码如下:
asmlinkage void schedule(void) { struct schedule_data * sched_data; struct task_struct *prev, *next, *p; struct list_head *tmp; int this_cpu, c; if (!current->active_mm) BUG();//如果当前进程是个内核线程,那就没有用户空间,所以其mm指针为0,运行时就要暂时借用在它之前运行的那个进程的active_mm,所以active_mm一定不等于0 need_resched_back: prev = current;//当前进程赋值给prev this_cpu = prev->processor; if (in_interrupt())//只能由进程在内核中主动调用,或者在当前进程从系统空间返回用户空间的前夕被动地发生,而不能在一个中断服务程序的内部发生 goto scheduling_in_interrupt; release_kernel_lock(prev, this_cpu); /* Do "administrative" work here while we don‘t hold any locks */ if (softirq_active(this_cpu) & softirq_mask(this_cpu))//处理软中断 goto handle_softirq; handle_softirq_back: /* * ‘sched_data‘ is protected by the fact that we can run * only one process per CPU. */ sched_data = & aligned_data[this_cpu].schedule_data; spin_lock_irq(&runqueue_lock); /* move an exhausted RR process to be last.. */ if (prev->policy == SCHED_RR)//见注释1 goto move_rr_last; move_rr_back: switch (prev->state) { case TASK_INTERRUPTIBLE://TASK_UNINTERRUPTIBLE和TASK_INTERRUPTIBLE的主要区别就在于此,TASK_UNINTERRUPTIBLE即使有信号等待处理,也不将其修改成TASK_RUNNING if (signal_pending(prev)) {//有信号等待处理时要将其改成TASK_RUNNING prev->state = TASK_RUNNING; break; } default: del_from_runqueue(prev);//sys_wait4中调用schedule时的状态为TASK_INTERRUPTIBLE,所以这里把这进程从可执行队列中撤下来 case TASK_RUNNING://如果是TASK_RUNNING,即继续运行,那么这里不需要有什么特殊处理 } prev->need_resched = 0;//刚开始need_reshced清0 /* * this is the scheduler proper: */ repeat_schedule: /* * Default process to select.. */ next = idle_task(this_cpu);//目前是进程0,指向已知最佳的候选进程 c = -1000;//目前是最低的权值,指向这个进程的综合权值 if (prev->state == TASK_RUNNING)//如果当前进程想要继续运行 goto still_running; still_running_back: list_for_each(tmp, &runqueue_head) {//遍历可执行队列runqueue中的每个进程 p = list_entry(tmp, struct task_struct, run_list); if (can_schedule(p, this_cpu)) {//单cpu中can_schedule永远为1 int weight = goodness(p, this_cpu, prev->active_mm);//进程所具有的权值 if (weight > c)//挑选出权值最大的 c = weight, next = p; } } /* Do we need to re-calculate counters? */ if (!c)//如果当前已经选择的进程(权值最高的进程)权值为0,那么就要重新计算各个进程的时间配额,参考注释2 goto recalculate; /* * from this point on nothing can prevent us from * switching to the next task, save this fact in * sched_data. */ sched_data->curr = next; ...... spin_unlock_irq(&runqueue_lock); if (prev == next)//挑选出来的next就是当前进程 goto same_process; ...... kstat.context_swtch++; /* * there are 3 processes which are affected by a context switch: * * prev == .... ==> (last => next) * * It‘s the ‘much more previous‘ ‘prev‘ that is on next‘s stack, * but prev is set to (the just run) ‘last‘ process by switch_to(). * This might sound slightly confusing but makes tons of sense. */ prepare_to_switch(); { struct mm_struct *mm = next->mm; struct mm_struct *oldmm = prev->active_mm; if (!mm) { if (next->active_mm) BUG(); next->active_mm = oldmm; atomic_inc(&oldmm->mm_count); enter_lazy_tlb(oldmm, next, this_cpu); } else { if (next->active_mm != mm) BUG(); switch_mm(oldmm, mm, next, this_cpu); } if (!prev->mm) { prev->active_mm = NULL; mmdrop(oldmm); } } /* * This just switches the register state and the * stack. */ switch_to(prev, next, prev); __schedule_tail(prev); same_process: reacquire_kernel_lock(current); if (current->need_resched)//前面已经把当前进程的need_resched清0,如果现在又成了非0,则一定发生了中断并且情况发生了变化 goto need_resched_back; return; recalculate: { struct task_struct *p; spin_unlock_irq(&runqueue_lock); read_lock(&tasklist_lock); for_each_task(p)//对所有进程的循环,对不在runqueue的进程,也提升其时间配额,参考注释3 p->counter = (p->counter >> 1) + NICE_TO_TICKS(p->nice); read_unlock(&tasklist_lock); spin_lock_irq(&runqueue_lock); } goto repeat_schedule; still_running: c = goodness(prev, this_cpu, prev->active_mm);//那么挑选候选进程时以当前进程此刻的权值开始。这意味着,相对于权值相同的其它进程来说,当前进程优先 next = prev; goto still_running_back; handle_softirq: do_softirq(); goto handle_softirq_back; move_rr_last: if (!prev->counter) {//如果时间配额用完了 prev->counter = NICE_TO_TICKS(prev->nice); move_last_runqueue(prev);//从可执行进程队列runqueue中当前的位置上移到队列的末尾,同时恢复其最初的时间配额,对于相同优先级的进程,调度的时候排在前面的进程优先,所以这使队列中具有相同优先级的其它进程有了优势 } goto move_rr_back; scheduling_in_interrupt: printk("Scheduling in interrupt\n"); BUG(); return; }
注释1:
为了适应各种不同应用的需要,内核在此基础上实现了三种不同的政策:SCHED_FIFO、SCHED_RR以及SCHED_OTHER。每个进程都有自己使用的调度政策,并且进程还可以通过系统调用sched_setscheduler()设定自己使用的调度政策。其中SCHED_FIFO适合于时间性要求比较强、但每次运行所需的时间比较短的进程,实时的应用大都具有这样的特点。SCHED_RR中的“RR”表示“Round Robin”,是轮流的意思,这种政策适合比较大、也就是每次运行需时较长的进程。而除此二者之外的SCHED_OTHER,则为传统的调度政策,比较适合于交互式的分时应用。
当前进程prev的调度政策为SCHED_RR,即轮换调度。SCHED_RR和SCHED_FIFO都是基于优先级的调度政策,可是在怎样调度具有相同优先级的进程这个问题上二者有区别。调度策略为SCHED_FIFO的进程一旦受到调度而开始运行之后,就要一直运行到自愿让出或被优先级更高的进程剥夺为止。对于每次受到调度时要求运行时间不长的进程,这样并没有什么不妥。可是,如果是受到调度后可能会长时间运行的进程,那样就不公平了。这种不公正性是对具有相同优先级的进程而言。所以,对这样的进程应该实行SCHED_RR调度政策,这种政策在相同的优先级上实行轮换调度。
注释2:
此时所有runqueue的进程权值都为0,由于除init进程和调用了sched_yield()的进程以外,每个进程的权值最低为0,所以只要队列中有其他就绪进程存在就不可能为负数。这里要指出,队里中所有其他进程的权限都已降到0,说明这些进程的调度政策都是SCHED_OTHER,因为若有政策为SCHED_FIFO或SCHED_RR的进程存在,则权值至少也有100。
注释3:
for_each_task()是对所有进程的循环,而不是仅对就绪进程队列的循环。对于不在就绪进程队列中的非实时进程,这里得到了提升其时间配额、从而提升其综合权值的机会。不过,对综合权值的这种提升是很有限的,每次重新计算都将原有的时间配额减半,再与NICE_TO_TICKS(p->nice)相加,这样就决定了重新计算以后的综合权值永远也不可能达到NICE_TO_TICKS(p->nice)的两倍。因此,即使经过很长时间的"韬光养晦",也不能达到可与实时进程竞争的地步(综合权值至少是1000),所以只是对非实时进程之间的竞争有意义。至于实时进程,时间配额的增加并不会提升其综合权值,而且对于SCHED_FIFO进程则连时间配额也是没有意义的。