操作系统真象还原线程调度的理解

操作系统真象还原是一本介绍如何从零实现一个操作系统的比较不错的书。作者用通俗易懂的语言把操作系统的实现原理非常清晰的讲解出来,非常适合初学者。但是即便已经写的非常好了,读者依然需要认真思考才能将很多问题想清楚。线程调度就是其中一个。

书的第九章是介绍线程的实现以及如何调度的。比较难以理解的是线程调度关于switch_to的理解,源码如下:

/* 实现任务调度 */
void schedule() {

   ASSERT(intr_get_status() == INTR_OFF);

   struct task_struct* cur = running_thread();
   if (cur->status == TASK_RUNNING) { // 若此线程只是cpu时间片到了,将其加入到就绪队列尾
      ASSERT(!elem_find(&thread_ready_list, &cur->general_tag));
      list_append(&thread_ready_list, &cur->general_tag);
      cur->ticks = cur->priority;     // 重新将当前线程的ticks再重置为其priority;
      cur->status = TASK_READY;
   } else {
      /* 若此线程需要某事件发生后才能继续上cpu运行,
      不需要将其加入队列,因为当前线程不在就绪队列中。*/
   }

   ASSERT(!list_empty(&thread_ready_list));
   thread_tag = NULL;     // thread_tag清空
/* 将thread_ready_list队列中的第一个就绪线程弹出,准备将其调度上cpu. */
   thread_tag = list_pop(&thread_ready_list);
   struct task_struct* next = elem2entry(struct task_struct, general_tag, thread_tag);
   next->status = TASK_RUNNING;
   switch_to(cur, next);
}

这是关于调度的程序,我们只关心最后一步将当前进程切换到next进程中去的部分。调度的精髓就在这个函数上。

switch_to:
   ;栈中此处是返回地址         
   push esi
   push edi
   push ebx
   push ebp

   mov eax, [esp + 20]           ; 得到栈中的参数cur, cur = [esp+20]
   mov [eax], esp                ; 保存栈顶指针esp. task_struct的self_kstack字段,
                                 ; self_kstack在task_struct中的偏移为0,
                                 ; 所以直接往thread开头处存4字节便可。
;------------------  以上是备份当前线程的环境,下面是恢复下一个线程的环境  ----------------
   mov eax, [esp + 24]           ; 得到栈中的参数next, next = [esp+24]
   mov esp, [eax]                ; pcb的第一个成员是self_kstack成员,用来记录0级栈顶指针,
                                 ; 用来上cpu时恢复0级栈,0级栈中保存了进程或线程所有信息,包括3级栈指针
   pop ebp
   pop ebx
   pop edi
   pop esi
   ret                           ; 返回到上面switch_to下面的那句注释的返回地址,
                                 ; 未由中断进入,第一次执行时会返回到kernel_thread
~  

由于从c语言函数切换到汇编代码,我们必须小心的是栈,刚刚进入函数时栈顶元素是switch_to的返回地址,接着开始将当前的上下文寄存器压入栈中,接着从switch_to的参数中取出当前进程的pcb地址,pcb的第一个元素是线程栈地址,于是将当前的esp放入线程栈的位置,这样从pcb中就可以找到上下文的寄存器了。接下来就把当前的esp给替换成next的的地址,要从next的pcb中找到next的上下文寄存器并恢复。可以pop出来的寄存器是容易的,但是最关键的eip也就是应该放在esi之上的地方,但是事实上这里存放的是switch_to的返回地址(当然这个switch_to不是现在这个,而是之前的某一个),所以ret只能回到switch_to后继续执行。因为整个的调度程序放在时钟中断服务程序中,因此,执行到最后只能是从中断中返回到中断前的状态,而这个状态正是next的上下文而不是刚刚发生的中断的所保存的上下文。原因是在switch_to函数中已经将esp替换成了next上一次被切换出去的时的栈地址,于是顺着这个栈所保存的寄存器全部是属于next,于是这么回溯回去就可以顺利的切换成next进程。这里的巧妙就在于栈切换,难以理解也在这里。有时间画个流程图会理解的更好。

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值