Chinaunix首页 | 论坛 | 博客
  • 博客访问: 612768
  • 博文数量: 113
  • 博客积分: 2554
  • 博客等级: 少校
  • 技术积分: 1428
  • 用 户 组: 普通用户
  • 注册时间: 2011-12-21 19:53
文章分类

全部博文(113)

文章存档

2014年(1)

2013年(2)

2012年(94)

2011年(16)

分类: LINUX

2011-12-26 16:14:52

  1. 进程调度的实现

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

  3. asmlinkage void schedule(void)

  4. {



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

  6.      next表示调度之后的进程 *

  7.     struct list_head *tmp;

  8.     int this_cpu, c;



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

  10. need_resched_back:

  11.            prev = current;*让prev成为当前进程 *

  12.            this_cpu = prev->processor;



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

  14. 就说明发生了错误*

  15.            printk("Scheduling in interrupt\n");

  16.               BUG();

  17.         }

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

  19. 并开this_cpu的中断*

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

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

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

  23.  move_rr_back:

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

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

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

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

  28.                                prev->state = TASK_RUNNING;

  29.                                 break;

  30.                         }

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

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

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

  34.          }

  35.          prev->need_resched = 0;



  36.      /*下面是调度程序的正文 *

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

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

  39.    c = -1000;

  40.      if (prev->state == TASK_RUNNING)

  41.           goto still_running;

  42. still_running_back:

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

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

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

  46.                if (weight > c)

  47.                    c = weight, next = p;

  48.            }

  49.      }

  50.                

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

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

  53.     if (!c) {

  54.              struct task_struct *p;

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

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

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

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

  59.              read_unlock(&tasklist_lock);

  60.                  spin_lock_irq(&runqueue_lock);

  61.                goto repeat_schedule;

  62.         }





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



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

  65.             prev->policy &= ~SCHED_YIELD;

  66.                goto same_process;

  67.       }



  68.           /* 下面开始进行进程切换*

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

  70.    

  71.         {

  72.                struct mm_struct *mm = next->mm;

  73.                struct mm_struct *oldmm = prev->active_mm;

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

  75.                       if (next->active_mm) BUG();

  76.                       next->active_mm = oldmm;

  77.                  

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

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

  80.                        switch_mm(oldmm, mm, next, this_cpu);

  81.               }



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

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

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

  85.                }

  86.         }

  87.     

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

  89.         __schedule_tail(prev);*置prev->policy的SCHED_YIELD为0 *



  90. same_process:

  91.        reacquire_kernel_lock(current);*针对SMP*

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

  93.                goto need_resched_back;*重新开始调度*

  94.         return;

  95. }



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

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

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

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

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

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

  102. · 用宏switch_to()进行真正的进程切换,后面将详细描述。
 
     在中断描述符表(IDT)中,除中断门、陷阱门和调用门外,还有一种“任务们”。任务门中包含有TSS段的选择符。当CPU因中断而穿过一个任务门时,就会将任务门中的段选择符自动装入TR寄存器,使TR指向新的TSS,并完成任务切换。CPU还可以通过JMPCALL指令实现任务切换,当跳转或调用的目标段(代码段)实际上指向GDT表中的一个TSS描述符项时,就会引起一次任务切换。

Intel的这种设计确实很周到,也为任务切换提供了一个非常简洁的机制。但是,由于i386的系统结构基本上是CISC的,通过JMP指令或CALL(或中断)完成任务的过程实际上是“复杂指令”的执行过程,其执行过程长达300多个CPU周期(一个POP指令占12CPU周期),因此,Linux内核并不完全使用i386CPU提供的任务切换机制。

由于i386CPU要求软件设置TRTSSLinux内核只不过“走过场”地设置TRTSS,以满足CPU的要求。但是,内核并不使用任务门,也不使用JMPCALL指令实施任务切换。内核只是在初始化阶段设置TR,使之指向一个TSS,从此以后再不改变TR的内容了。也就是说,每个CPU(如果有多个CPU)在初始化以后的全部运行过程中永远使用那个初始的TSS。同时,内核也不完全依靠TSS保存每个进程切换时的寄存器副本,而是将这些寄存器副本保存在各个进程自己的内核中(参见上一章task_struct结构的存放)。

这样以来,TSS中的绝大部分内容就失去了原来的意义。那么,当进行任务切换时,怎样自动更换堆栈?我们知道,新任务的内核栈指针(SS0ESP0)应当取自当前任务的TSS,可是,Linux中并不是每个任务就有一个TSS,而是每个CPU只有一个TSSIntel原来的意图是让TR的内容(即TSS)随着任务的切换而走马灯似地换,而在Linux内核中却成了只更换TSS中的SS0ESP0,而不更换TSS本身,也就是根本不更换TR的内容。这是因为,改变TSSSS0ESP0所化的开销比通过装入TR以更换一个TSS要小得多。因此,在Linux内核中,TSS并不是属于某个进程的资源,而是全局性的公共资源。在多处理机的情况下,尽管内核中确实有多个TSS,但是每个CPU仍旧只有一个TSS

 

 

  1. fastcall对函数的调用不同于一般函数的调用,因为__switch_to()从寄存器(如表5.1)取参数,而不像一般函数那样从堆栈取参数,也就是说,通过寄存器eax和edx把prev和next 参数传递给__switch_to()函数。



  2. void __switch_to(struct task_struct *prev_p, struct task_struct *next_p)

  3. {

  4.         struct thread_struct *prev = &prev_p->thread,

  5.                                 *next = &next_p->thread;

  6.         struct tss_struct *tss = init_tss + smp_processor_id();



  7.       unlazy_fpu(prev_p);* 如果数学处理器工作,则保存其寄存器的值*



  8.   /* 将TSS中的内核级(0级)堆栈指针换成next->esp0,这就是next 进程在内核

  9.      栈的指针

  10.    

  11.       tss->esp0 = next->esp0;



  12.    /* 保存fs和gs,但无需保存es和ds,因为当处于内核时,内核段

  13. 总是保持不变*



  14.        asm volatile("movl %%fs,%0":"=m" (*(int *)&prev->fs));

  15.        asm volatile("movl %%gs,%0":"=m" (*(int *)&prev->gs));



  16.      /*恢复next进程的fs和gs *



  17.          loadsegment(fs, next->fs);

  18.         loadsegment(gs, next->gs);



  19. * 如果next挂起时使用了调试寄存器,则装载0~7个寄存器中的6个寄存器,其中第4、5个寄存器没有使用 *



  20.         if (next->debugreg[7]){

  21.                 loaddebug(next, 0);

  22.                 loaddebug(next, 1);

  23.                 loaddebug(next, 2);

  24.                 loaddebug(next, 3);

  25.                  /* no 4 and 5 */

  26.                 loaddebug(next, 6);

  27.                 loaddebug(next, 7);

  28.         }



  29.          if (prev->ioperm || next->ioperm) {

  30.                 if (next->ioperm) {

  31.                

  32. *把next进程的I/O操作权限位图拷贝到TSS中 *

  33.                        memcpy(tss->io_bitmap, next->io_bitmap,

  34. IO_BITMAP_SIZE*sizeof(unsigned long));



  35. /* 把io_bitmap在tss中的偏移量赋给tss->bitmap *

  36.                          tss->bitmap = IO_BITMAP_OFFSET;

  37.                  } else

  38.                   

  39. *如果一个进程要使用I/O指令,但是,若位图的偏移量超出TSS的范围,

  40. * 就会产生一个可控制的SIGSEGV信号。第一次对sys_ioperm()的调用会

  41. * 建立起适当的位图 *

  42.                          

  43.                         tss->bitmap = INVALID_IO_BITMAP_OFFSET;

  44.          }

  45. }





  46.    从上面的描述我们看到,尽管Intel本身为操作系统中的进程(任务)切换提供了硬件支持,但是Linux内核的设计者并没有完全采用这种思想,而是用软件实现了进程切换,而且,软件实现比硬件实现的效率更高,灵活性更大。
阅读(2086) | 评论(0) | 转发(0) |
0

上一篇:基本知识

下一篇:杂料

给主人留下些什么吧!~~