玩具操作系统也有多进程
单进程
赋值
tss.esp0
首先我们要理解整个进程与中断的过程:在
restart()
函数运行之后,我们的进程开始运行,系统现在是在ring1
特权级上。由于我们已经开启了时钟中断,所以,时钟中断会以一定频率发生。注意,要好好理解中断
这个概念。一开始我一直无法理解,为什么我们的第一个进程是一个死循环,但是中断依旧能够发生。其实,中断就是将当前的进程终止,去执行其他任务,当该任务完成了之后,就会从当前进程停止处继续。这一点很重要,到后面的IPC
都要依靠这个概念。由于中断发生时出现了特权级转换:
ring1 -> ring0
所以存在堆栈的转换。(具体请看《一个操作系统的实现》 P99)堆栈的ss
和esp
存储在tss
里面。在restart()
我们已经准备好了tss0.esp
,是指向当前进程的STACK_FRAME
的最高处。至于为什么要指向这里,那是因为ring1 -> ring0
的转换,不仅存在堆栈的切换,而且还会将eip
cs
eflags
esp
ss
这些压入新的堆栈中,这跟我们的struct stack_frame
的顺序完全一致。最后一个疑问,为什么在中断处理程序中要一直给tss.esp0
赋值?因为我们现在是单进程,所以这个赋值只是一个重复而已。不过,后面我们实现了多进程了之后,这个赋值操作就十分地重要了。内核栈
首先,中断处理程序的堆栈就是tss.esp0
,而这里面存有我们进程的所有信息,所以如果我们的中断处理程序要进行一些函数操作,堆栈里面的信息可能会被破坏掉,所以,我们要切换到内核栈
。这样,我们就可以放心地进行一系列的函数操作了。
多进程
由于目前来说,所谓的 多进程
其实除了 进程体,进程的堆栈,进程的 ldt selector
不一样以外,其他的都是一样的,所以我们可以用一个循环来进行批量的初始化。我们把 进程体
用一个新的数组来存储: task_table[]
,每一次循环,就将 进程表
里的 eip
赋为 task_table[]
相应的元素。
同时,由于出现了多进程,所以自然而然地有了所谓的 进程调度
了。目前的进程调度没有任何高明之处,就是一个顺序循环调度。进程调度了之后,必须重新切换 ldt
,并且准备好 tss.esp0
,以便下一次时钟中断发生后,能够将进程的状态储存在正确的堆栈里。这里要注意,进程的状态都是储存在自己的堆栈的。
添加进程的流程:
- 在
kernel/main.c
添加一个进程体 - 在
include/proto.h
声明进程体 - 在
kernel/global.c
的task_table
添加一个对应的项 - 增加
include/proc.h
中的NR_TASKS
以及对应的栈以及总栈大小
Minix 的中断处理
不得不说,看了 Minux 的中断处理之后发现原来代码可以这么优美的。这里主要解释三点:
关于
retadr
在中断处理程序一开始,我们是调用了save
这个函数,然后注意,由于call
操作,所以,会将下一条要执行的指令的eip
压入栈中,又由于中断处理程序存在ring1->ring0
,所以会将ss
sp
eflag
cs
eip
进行压栈,所以在 **下一条要执行的指令的eip
就是之前我们一直忽略的retadr
**。关于
中断重入
就提一点,如果是发生了中断重入的话,则回复的是上一次被重入的中断状态hwint00
中的最后一句ret
这又是一个非常优美的一个做法,主要到我们在save
里面有push
东西的,而ret
就是把栈中的赋给eip
而已。所以,这给了我们一个启示,**ret
不一定要依赖call
,可以按需进行压栈,需要的时候,ret
就行了**
至此,进程告一段落,开始系统调用。
EOF
Author: simowce
Permalink: https://blog.simowce.com/muti-process/
本作品采用 知识共享署名-非商业性使用-禁止演绎 4.0 国际许可协议 进行许可。