Linux 0.11 进程 0: fork 之后——调度

1,147 阅读2分钟

main-fork.png

这篇笔记研究进程 0 通过 fork 创建进程 1 之后发生的事,以及调度的工作原理。

after fork: 进程 0

父进程 fork() 返回子进程号(这里是 1),所以执行到:

for(;;) pause();

pause 又是一个系统调用,最终走到的处理函数是 sys_pause(kernel/sched.c),做两件事:

  • 当前 proc 设为可中断状态:挂起等一个信号
  • schedule() 去调度

调度

(kervel/sched.c)

调度的触发:

  • 当前 proc 时间片用尽:时钟中断时发生调度
  • proc 停止运行:立即发生调度
  • 进程 0 的 pause -> 主动发起调度

调度过程:

  1. 「信号」:遍历处理所有进程的 alarm、signal
    • alarm:有进程的 alarm 到时间了 -> 给他发 SIGALARM信号,清零 alarm
    • signal:有除了 blocked 的信号到来,且处于 TASK_INTERRUPTIBEL -> proc 转为 TASK_RUNNING(就绪:可执行)
  2. 「调度」:遍历 task,挑一个运行
    • 找就绪(TASK_RUNNING)、时间片(counter)最大的
    • 顺利得到最大 counter > 0:switch_to 对应 task
    • else 最大 counter == 0:
      • 所有就绪的 task 的 counter 都到 0 了
      • 重置 counter:counter=counter2+priority, for taskcounter = \frac{counter}{2}+priority, \textrm{ for } \forall task(所有进程,不论状态,无差别重置)

switch_to

#define switch_to(n)

include/linux/sched.h

  1. task[n] == current:无为早退
  2. _tmp.b <- %dx <- (%edx = _TSS(n)).低 (edx:高 eip,低 cs)
  3. current = task[n]
  4. ljmp %0:长跳转至 _tmp:走 task gate -> 进程切换
    • _tmp.a:eip:偏移(ljmp 不用这个值)
    • _tmp.b:cs:新 task 的段选择子(16b,jmpi 0,8 的 8:15~3:index,2:G/L DT,1~0:RPL)
  5. 任务切换回来后:做某些关于协处理器的事情(略)

ljmp:长跳转,有两格式(FF/5 和 EA),见 IA32-SDM-vol2: JMP

  1. ljmp seg, offset:(EA):seg 和 offet 必须立即数
  2. ljmp 内存地址 (FF/15)
    • 这里用的就是这个,传了 _tmp 的内存地址
    • 相当于 ljmp [_tmp.b], [_tmp.a]

这个指令的功能是:

  • 跳到同级 code seg
  • or a task switch:走 Task Gate
    • 找到所给内存处的 TSS
    • 恢复给 CPU
    • 以 tss 为准,忽略 ljmp 的 offset
    • 即跳到 TSS 所对应的 proc 继续执行

switch_to.png


after ljmp

执行完 ljmp,就切换到了 task 1,执行 INT 0x80 之后的一行代码(user 态):[[6.after-fork-1-init]]

task 0 被暂存,直到下一次 0 被 switch_to 回来:

  • 回来后还是 kernel 态
  • 一路 ret 退 switch_to -> schedule -> sys_pause -> systemcall
  • 然后 iret (回用户态了):退回到 pause