5.3.5进程调度的实现 

 调度程序在内核中就是一个函数,为了讨论方便,我们同样对其进行了简化,略其对SMP的实现部分。   

asmlinkage void schedule(void)

{

 

  struct task_struct *prev, *next, *p; * prev表示调度之前的进程,

     next表示调度之后的进程 * 

    struct list_head *tmp;

    int this_cpu, c;

 

      if (!current->active_mm) BUG();/*如果当前进程的的active_mm为空,出错*

need_resched_back:            

           prev = current;         *prev成为当前进程 *

           this_cpu = prev->processor;

 

if (in_interrupt()) {*如果schedule是在中断服务程序内部执行,

就说明发生了错误*

           printk("Scheduling in interrupt\n");

              BUG();

        }

   release_kernel_lock(prev, this_cpu); /*释放全局内核锁,

并开this_cpu的中断*

       spin_lock_irq(&runqueue_lock); *锁住运行队列,并且同时关中断*/

        if (prev->policy == SCHED_RR) *将一个时间片用完的SCHED_RR实时

               goto move_rr_last;      进程放到队列的末尾 *

 move_rr_back:

        switch (prev->state) {     *根据prev的状态做相应的处理*

               case TASK_INTERRUPTIBLE:  /*此状态表明该进程可以被信号中断*/

                        if (signal_pending(prev)) { /*如果该进程有未处理的

信号,则让其变为可运行状态*/

                               prev->state = TASK_RUNNING;

                                break;

                        }

                 default:     *如果为可中断的等待状态或僵死状态*

                        del_from_runqueue(prev); *从运行队列中删除*

                case TASK_RUNNING:;*如果为可运行状态,继续处理*

         }

         prev->need_resched = 0;

 

     *下面是调度程序的正文 *

repeat_schedule:    *真正开始选择值得运行的进程*

        next = idle_task(this_cpu); /*缺省选择空闲进程*/

   c = -1000;

     if (prev->state == TASK_RUNNING)

          goto still_running;

still_running_back:

    list_for_each(tmp, &runqueue_head) { *遍历运行队列*

       p = list_entry(tmp, struct task_struct, run_list);

 if (can_schedule(p, this_cpu)) { *CPU中,该函数总返回1*                          int weight = goodness(p, this_cpu, prev->active_mm);

               if (weight > c)

                   c = weight, next = p;

           }

     }         

               

* 如果c0,说明运行队列中所有进程的权值都为0,也就是分配给各个进程的

     时间片都已用完,需重新计算各个进程的时间片 * 

    if  (!c) {

             struct task_struct *p;

             spin_unlock_irq(&runqueue_lock);*锁住运行队列*

              read_lock(&tasklist_lock);  * 锁住进程的双向链表*

             for_each_task(p)            * 对系统中的每个进程*

             p->counter = (p->counter >> 1) + NICE_TO_TICKS(p->nice);

             read_unlock(&tasklist_lock);

                 spin_lock_irq(&runqueue_lock);

               goto repeat_schedule;

        }

 

 

     spin_unlock_irq(&runqueue_lock);*对运行队列解锁,并开中断*

 

       if (prev == next) {     /*如果选中的进程就是原来的进程*/

            prev->policy &= ~SCHED_YIELD;

               goto same_process;

      }

 

          * 下面开始进行进程切换*

       kstat.context_swtch++; *统计上下文切换的次数*

  

        {

               struct mm_struct *mm = next->mm;

               struct mm_struct *oldmm = prev->active_mm;

              if (!mm) {  *如果是内核线程,则借用prev的地址空间*

                      if (next->active_mm) BUG();

                      next->active_mm = oldmm;

                

              } else { *如果是一般进程,则切换到next的用户空间*

                       if (next->active_mm != mm) BUG();

                       switch_mm(oldmm, mm, next, this_cpu);

              }

 

            if (!prev->mm) { *如果切换出去的是内核线程*

                  prev->active_mm = NULL;*归还它所借用的地址空间*

                    mmdrop(oldmm);     *mm_struct中的共享计数减1*

               }

        }

   

        switch_to(prev, next, prev); *进程的真正切换,即堆栈的切换*

        __schedule_tail(prev);  *prev->policySCHED_YIELD0 *

 

same_process:

       reacquire_kernel_lock(current);*针对SMP*

        if (current->need_resched)    *如果调度标志被置位*

               goto need_resched_back; *重新开始调度*

        return;

}

 

   以上就是调度程序的主要内容,为了对该程序形成一个清晰的思路,我们对其再给出进一步的解释:

·    如果当前进程既没有自己的地址空间,也没有向别的进程借用地址空间,那肯定出错。另外, 如果schedule()在中断服务程序内部执行,那也出错.

·    对当前进程做相关处理,为选择下一个进程做好准备。当前进程就是正在运行着的进程,可是,当进入schedule(),其状态却不一定是TASK_RUNNIG,例如,在exit()系统调用中,当前进程的状态可能已被改为TASK_ZOMBE;又例如,在wait4()系统调用中,当前进程的状态可能被置为TASK_INTERRUPTIBLE。因此,如果当前进程处于这些状态中的一种,就要把它从运行队列中删除。

·    从运行队列中选择最值得运行的进程,也就是权值最大的进程。

·    如果已经选择的进程其权值为0,说明运行队列中所有进程的时间片都用完了(队列中肯定没有实时进程,因为其最小权值为1000),因此,重新计算所有进程的时间片,其中宏操作NICE_TO_TICKS就是把优先级nice转换为时钟滴答。

·    进程地址空间的切换。如果新进程有自己的用户空间,也就是说,如果next->mmnext->active_mm相同,那么,switch_mm( )函数就把该进程从内核空间切换到用户空间,也就是加载next的页目录。如果新进程无用户空间(next->mm为空),也就是说,如果它是一个内核线程,那它就要在内核空间运行,因此,需要借用前一个进程(prev)的地址空间,因为所有进程的内核空间都是共享的,因此,这种借用是有效的。

·      用宏switch_to()进行真正的进程切换,后面将详细描述。