一个操作系统的实现 -- 多进程

一个操作系统的实现

玩具操作系统也有多进程

单进程

  1. 赋值 tss.esp0
    首先我们要理解整个进程与中断的过程:

    restart() 函数运行之后,我们的进程开始运行,系统现在是在 ring1 特权级上。由于我们已经开启了时钟中断,所以,时钟中断会以一定频率发生。注意,要好好理解 中断 这个概念。一开始我一直无法理解,为什么我们的第一个进程是一个死循环,但是中断依旧能够发生。其实,中断就是将当前的进程终止,去执行其他任务,当该任务完成了之后,就会从当前进程停止处继续。这一点很重要,到后面的 IPC 都要依靠这个概念。

    由于中断发生时出现了特权级转换: ring1 -> ring0 所以存在堆栈的转换。(具体请看《一个操作系统的实现》 P99)堆栈的 ssesp 存储在 tss 里面。在 restart() 我们已经准备好了 tss0.esp ,是指向当前进程的 STACK_FRAME 的最高处。至于为什么要指向这里,那是因为 ring1 -> ring0 的转换,不仅存在堆栈的切换,而且还会将 eip cs eflags esp ss 这些压入新的堆栈中,这跟我们的 struct stack_frame 的顺序完全一致。最后一个疑问,为什么在中断处理程序中要一直给 tss.esp0 赋值?因为我们现在是单进程,所以这个赋值只是一个重复而已。不过,后面我们实现了多进程了之后,这个赋值操作就十分地重要了。

  2. 内核栈
    首先,中断处理程序的堆栈就是 tss.esp0 ,而这里面存有我们进程的所有信息,所以如果我们的中断处理程序要进行一些函数操作,堆栈里面的信息可能会被破坏掉,所以,我们要切换到 内核栈 。这样,我们就可以放心地进行一系列的函数操作了。

多进程

由于目前来说,所谓的 多进程 其实除了 进程体,进程的堆栈,进程的 ldt selector 不一样以外,其他的都是一样的,所以我们可以用一个循环来进行批量的初始化。我们把 进程体 用一个新的数组来存储: task_table[] ,每一次循环,就将 进程表 里的 eip 赋为 task_table[] 相应的元素。

同时,由于出现了多进程,所以自然而然地有了所谓的 进程调度 了。目前的进程调度没有任何高明之处,就是一个顺序循环调度。进程调度了之后,必须重新切换 ldt ,并且准备好 tss.esp0 ,以便下一次时钟中断发生后,能够将进程的状态储存在正确的堆栈里。这里要注意,进程的状态都是储存在自己的堆栈的。

添加进程的流程:

  1. kernel/main.c 添加一个进程体
  2. include/proto.h 声明进程体
  3. kernel/global.ctask_table 添加一个对应的项
  4. 增加 include/proc.h 中的 NR_TASKS 以及对应的栈以及总栈大小

Minix 的中断处理

不得不说,看了 Minux 的中断处理之后发现原来代码可以这么优美的。这里主要解释三点:

  1. 关于 retadr
    在中断处理程序一开始,我们是调用了 save 这个函数,然后注意,由于 call 操作,所以,会将下一条要执行的指令的 eip 压入栈中,又由于中断处理程序存在 ring1->ring0 ,所以会将 ss sp eflag cs eip 进行压栈,所以在 **下一条要执行的指令的 eip 就是之前我们一直忽略的 retadr **。

  2. 关于 中断重入
    就提一点,如果是发生了中断重入的话,则回复的是上一次被重入的中断状态

  3. hwint00 中的最后一句 ret
    这又是一个非常优美的一个做法,主要到我们在 save 里面有 push 东西的,而 ret 就是把栈中的赋给 eip 而已。所以,这给了我们一个启示,**ret 不一定要依赖 call ,可以按需进行压栈,需要的时候, ret 就行了**

至此,进程告一段落,开始系统调用。

EOF

Author: simowce

Permalink: https://blog.simowce.com/muti-process/

知识共享许可协议
本作品采用 知识共享署名-非商业性使用-禁止演绎 4.0 国际许可协议 进行许可。