这篇笔记研究进程 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 -> 主动发起调度
调度过程:
- 「信号」:遍历处理所有进程的 alarm、signal
- alarm:有进程的 alarm 到时间了 -> 给他发 SIGALARM信号,清零 alarm
- signal:有除了 blocked 的信号到来,且处于
TASK_INTERRUPTIBEL-> proc 转为TASK_RUNNING(就绪:可执行)
- 「调度」:遍历 task,挑一个运行
- 找就绪(
TASK_RUNNING)、时间片(counter)最大的 - 顺利得到最大 counter > 0:
switch_to对应 task - else 最大 counter == 0:
- 所有就绪的 task 的 counter 都到 0 了
- 重置 counter:(所有进程,不论状态,无差别重置)
- 找就绪(
switch_to
#define switch_to(n)
include/linux/sched.h
- 若
task[n] == current:无为早退 _tmp.b <- %dx <- (%edx = _TSS(n)).低(edx:高 eip,低 cs)current = task[n]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)
- 任务切换回来后:做某些关于协处理器的事情(略)
ljmp:长跳转,有两格式(FF/5 和 EA),见 IA32-SDM-vol2: JMP
ljmp seg, offset:(EA):seg 和 offet 必须立即数ljmp 内存地址(FF/15)- 这里用的就是这个,传了
_tmp的内存地址 - 相当于
ljmp [_tmp.b], [_tmp.a]
- 这里用的就是这个,传了
这个指令的功能是:
- 跳到同级 code seg
- or a task switch:走 Task Gate
- 找到所给内存处的 TSS
- 恢复给 CPU
- 以 tss 为准,忽略 ljmp 的 offset
- 即跳到 TSS 所对应的 proc 继续执行
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