44
55本实验中,我们将引入进程的概念,并实现如下的进程** 状态转换** 逻辑。
66
7- ``` mermaid fullWidth="false"
7+ ``` mermaid
88graph TD
99A[UNUSED]
1010B[RUNNABLE]
@@ -17,6 +17,7 @@ B -- yield() --> C
1717C -- wait() --> D
1818D -- activate() --> B
1919C -- exit() --> E
20+
2021```
2122
2223我们会先实现负责管理和调度进程的一系列内核库,然后创建一些简易的、运行在内核地址空间的进程。下一个实验,我们将结合页表,引入真正的、拥有独立地址空间的用户态进程。
@@ -98,9 +99,9 @@ typedef struct Proc {
9899* ` ucontext ` :用户态上下文,用于保存用户态的寄存器信息,也称作 ` trap frame `
99100* ` kcontext ` :内核态上下文,用于保存内核态的寄存器信息。
100101
101- {% hint style="success" %}
102- ** 思考 ** :内核态上下文切换时,有哪些寄存器是一定要保存的?
103- {% endhint %}
102+ > [ !tip ]
103+ >
104+ > ** 思考 ** :内核态上下文切换时,有哪些寄存器是一定要保存的?
104105
105106进程退出时,将其子进程的父进程改为 ` root_proc ` 。确保除了 root 的进程都有父进程,也就是所有的进程能够构成一棵树。
106107
@@ -115,9 +116,9 @@ typedef struct Proc {
115116
116117因此操作系统引入了** 调度器** 的概念。
117118
118- {% hint style=" info" %}
119- ** 注意: ** 在我们的实验中,并不存在一个具体的调度器对象,调度器是由 ` sched.c ` 中的所有全局变量和函数一起组成的。
120- {% endhint %}
119+ > [ ! info]
120+ >
121+ > ** 注意: ** 在我们的实验中,并不存在一个具体的调度器对象,调度器是由 ` sched.c ` 中的所有全局变量和函数一起组成的。
121122
122123调度器维护 CPU 和进程的调度信息,在进程请求调度时决定下一个运行什么进程,并执行进程切换。进程切换需要更新相关的调度信息,并进行上下文切换。
123124
@@ -133,41 +134,41 @@ typedef struct Proc {
133134
134135本段将带大家过一遍进程的从创建到退出的整个流程。
135136
136- {% hint style=" info" %}
137- 涉及的具体理论知识请参考elearning上进程相关理论课内容。
138- {% endhint %}
137+ > [ ! info]
138+ >
139+ > 涉及的具体理论知识请参考elearning上进程相关理论课内容。
139140
140141当一段内核代码需要创建一个进程时,首先它应该处于init阶段之后,因为init阶段才完成进程树和调度器的初始化。
141142
142143要创建进程的内核代码首先调用` create_proc ` ,分配空间并初始化进程结构体。此时进程处于` UNUSED ` 状态。
143144
144145在进程启动之前,还可以对进程结构体的一些内容进行修改,如修改其父进程,修改其调度信息,修改初始寄存器值等。
145146
146- {% hint style="success" %}
147- ** 思考 ** :一般情况下,只能选择 ` root_proc ` 和当前进程为新进程的父进程,为什么?
148- {% endhint %}
147+ > [ !tip ]
148+ >
149+ > ** 思考 ** :一般情况下,只能选择 ` root_proc ` 和当前进程为新进程的父进程,为什么?
149150
150151随后调用` start_proc ` 启动进程。启动进程时,将为进程设置入口函数,并将其加入调度队列,状态更新为` RUNNABLE ` 。此时进程已经可以被调度。
151152
152153进程被调度后,进入指定的进程入口函数,执行进程代码。
153154
154- {% hint style="success" %}
155- ** 思考 ** :真正的入口函数是 ` proc_entry ` ,然后才进入指定的入口函数,为什么要这样设计?
156- {% endhint %}
155+ > [ !tip ]
156+ >
157+ > ** 思考 ** :真正的入口函数是 ` proc_entry ` ,然后才进入指定的入口函数,为什么要这样设计?
157158
158159进程可以调用` wait ` 、` wait_sem ` 等函数,这些函数会在条件不满足时令进程陷入` SLEEPING ` 状态等待。它们都是通过配置好相关信息后调用` sched(SLEEPING) ` 实现的。
159160
160- {% hint style="success" %}
161- ** 思考 ** :直接调用 ` sched(SLEEPING) ` 会怎么样?
162- {% endhint %}
161+ > [ !tip ]
162+ >
163+ > ** 思考 ** :直接调用 ` sched(SLEEPING) ` 会怎么样?
163164
164165其他进程可以通过调用` activate_proc ` 唤醒处于` SLEEPING ` 状态的进程,这会将进程的状态更改为` RUNNABLE ` 并加入调度队列。
165166
166167进程执行完毕后,应调用` exit ` 退出。` exit ` 将释放一些资源,将子进程全部转移给` root_proc ` ,然后调用` sched(ZOMBIE) ` 。此时进程处于` ZOMBIE ` 状态,不再执行,只保留一些基础的数据等待父进程在` wait ` 中回收。
167168
168- {% hint style="success" %}
169- ** 思考 ** :进程执行完毕后,直接 ` return ` 而不 ` exit ` 会怎样?
170- {% endhint %}
169+ > [ !tip ]
170+ >
171+ > ** 思考 ** :进程执行完毕后,直接 ` return ` 而不 ` exit ` 会怎样?
171172
172173进程的父进程可以调用` wait ` 释放` ZOMBIE ` 状态子进程的剩余资源,并释放进程结构体。` wait ` 将向父进程反馈子进程的退出代码和 PID 。
173174
@@ -179,9 +180,9 @@ typedef struct Proc {
179180* ** 异常** (Exception):是指程序运行时发生错误,例如除 0 或者读取非法内存地址时发生的中断。
180181* ** 陷入** (Trap):一般指由于软件指令,例如系统调用,使得用户态程序中断,进入内核态执行一些任务。也指在中断发生时,保存用户寄存器、执行特殊处理程序、恢复用户寄存器的过程。
181182
182- {% hint style=" info" %}
183- 事实上,不同平台(如x86-64,AArch64,RISC-V)对于以上三个概念的定义不甚相同,很多情况下甚至交替使用。
184- {% endhint %}
183+ > [ ! info]
184+ >
185+ > 事实上,不同平台(如x86-64,AArch64,RISC-V)对于以上三个概念的定义不甚相同,很多情况下甚至交替使用。
185186
186187通过配置相关寄存器,我们将所有 trap 的入口设定为 ` trap_entry ` 。` trap_entry ` 中需要保存 trap 的上下文,并调用 ` trap_global_handler ` 。
187188
@@ -195,9 +196,9 @@ typedef struct Proc {
195196
196197** 信号量** 是操作系统解决并发中的互斥、同步问题的一种重要方法,基于信号量我们可以实现进程的 SLEEPING ,等待子进程唤醒等功能。
197198
198- {% hint style=" info" %}
199- 具体的实现可以参考 ` src/common/sem.h ` 和 ` src/common/sem.c `
200- {% endhint %}
199+ > [ ! info]
200+ >
201+ > 具体的实现可以参考 ` src/common/sem.h ` 和 ` src/common/sem.c `
201202
202203信号量维护了一个值 val 以及一个等待队列 sleeplist。val 提示此信号量的资源量,对于信号量的操作上分为P、V操作(对应wait、post)。通俗的理解post是生产、wait是消费
203204
0 commit comments