add lab4_report.md, not finished yet.

This commit is contained in:
Shine wOng
2019-09-05 18:12:34 +08:00
parent 88f9a75073
commit 86bc29584f

161
thu_os/lab4_report.md Normal file
View File

@@ -0,0 +1,161 @@
Lab4 Report
===========
## 实验目的
+ 了解内核线程创建/执行的管理过程
+ 了解内核线程的切换和基本调度过程
## 实验内容
实验2/3完成了物理和虚拟内存管理这给创建内核线程内核线程是一种特殊的进程打下了提供内存管理的基础。当一个程序加载到内存中运行时首先通过`ucore OS`的内存管理子系统分配合适的空间然后就需要考虑如何分时使用CPU来“并发”执行多个程序让每个运行的程序这里用线程或进程表示“感到”它们各自拥有“自己”的CPU。
本次实验将首先接触的是内核线程的管理。内核线程是一种特殊的进程,内核线程与用户进程的区别有两个:
+ 内核线程只运行在内核态
+ 用户进程会在在用户态和内核态交替运行
+ 所有内核线程共用ucore内核内存空间不需为每个内核线程维护单独的内存空间
+ 而用户进程需要维护各自的用户内存空间
## 练习
对实验报告的要求:
+基于markdown格式来完成以文本方式为主
+ 填写各个基本练习中要求完成的报告内容
+ 完成实验后,请分析`ucore lab`中提供的参考答案,并请在实验报告中说明你的实现与参考答案的区别
+ 列出你认为本实验中重要的知识点以及与对应的OS原理中的知识点并简要说明你对二者的含义关系差异等方面的理解也可能出现实验中的知识点没有对应的原理知识点
+ 列出你认为OS原理中很重要但在实验中没有对应上的知识点
## lab4 整体实验流程概述
lab4 相对于前面的实验,就是增加了一个内核线程,即`kern/process`文件夹里面的内容。此外其他的文件也有一些细微的修改,比如增加了一些函数供后续调用等,这种变化目前可以忽略啊。
在增加的`kern/process`主要就是完成了三件事情即建立第零个内核线程建立第一个内核线程以及将CPU的控制权从第零个线程转交给第一个线程这也分别对应了本实验的三个练习。将在下面分别详细阐述。
## 建立第零个内核线程
在原理课中有讲到,在实际的操作系统中,例如在`linux`中,当一个用户进程希望创建一个子进程时,可以通过`fork`系统调用,由操作系统代劳为其建立一个子进程。在`fork`系统调用中,主要进行的操作是,将当前的进程拷贝到新创建的进程中,即占用的各类资源,一些状态信息,复制到新创建的进程中,然后再在后续的操作中,更新其地址空间,更新子进程要执行的代码等。
但是这些操作都不适用于第零号内核线程,因为上面进程的创建,是在已经有了一个进程的基础上进行的,对于第零号进程则不然。我们这里讨论的,就是如何实现这种由零到一的转变。
操作系统通过进程控制块PCB, Process Control Block或者称为线程控制块TCB, Thread Control Block来管理进程。第零号内核线程的创建其实就是手动构造它的TCB将它的各个属性设置为合适的值使它可以正常运行。
此外还需要明确,第零号内核线程究竟是干什么的?实际上,它什么也不干,即所谓的`idleproc`,它的主要作用就是在操作系统没有其它进程可以运行的时候,让操作系统运行这个线程。而它的主函数体如下:
```c
// cpu_idle - at the end of kern_init, the first kernel thread idleproc will do below works
void
cpu_idle(void) {
while (1) {
if (current->need_resched) {
schedule();
}
}
}
```
可以看到,它的工作就是不断地调用调度函数(`schedule`以试图找到一个处于就绪队列的进程将其放到CPU运行。
> 构造第零号内核线程
构造第零号内核线程就是手动构造它的TCB让它成为一个合法的线程。那么应该如何构造呢具体步骤如下
+ 首先为它的TCB分配一个内存空间这可以通过`alloc_proc`函数实现。
+ 接下来就可以为它的TCB的各字段赋值了主要包括
- pid需要给它设置唯一的进程id这里设置为零以表现出它的特殊性
- state:标记它的状态为就绪态(`PROC_RUNNABLE`);
- kstack:设置内核堆栈,关于什么是内核堆栈,会在后面提到。这里直接设置它的内核堆栈为`UCORE`建立的内核堆栈`bootstack`
- need_resched:是否需要被调度。当然要被调度啦!它就是个无所事事的`idle`线程啊。
- name:给它个名字,就叫`idle`吧。
- cr3`alloc_proc`中进行了设置,将其赋值为`boot_cr3`,即内核虚拟地址空间的页表,这是因为内核线程和操作系统共享内核地址空间。
+ 此外,还有一些字段是没有设置的,比如
- parent:父进程,你说它有没有父进程?
- mm:用户虚拟地址空间。它是内核线程啊,没有用户地址空间。
- 一些状态信息,如`tf``flags``context`:它作为第一个运行的线程可以不用设置的,因为它是第一个运行的啊,后面在它被打断的时候,会通过中断机制设置这些信息。而别的线程就不行。会在后面说线程切换的时候详细说明。
上面就说清楚了应该如何构造第一个内核线程,具体的代码如下:
```c
void
proc_init(void) {
int i;
list_init(&proc_list);
for (i = 0; i < HASH_LIST_SIZE; i ++) {
list_init(hash_list + i);
}
if ((idleproc = alloc_proc()) == NULL) {
panic("cannot alloc idleproc.\n");
}
idleproc->pid = 0;
idleproc->state = PROC_RUNNABLE;
idleproc->kstack = (uintptr_t)bootstack;
idleproc->need_resched = 1;
set_proc_name(idleproc, "idle");
nr_process ++;
current = idleproc;
......
```
在前面还进行了进程列表的初始化(`proc_list`),该列表用来保存所有进程,用于进程调度算法的。一旦运行了`current = idleproc`,第零个内核线程就投入运行了,后续代码的执行,后续的系统初始化工作,都可以看做它的运行过程。
## 练习1分配并初始化一个进程控制块需要编码
`alloc_proc`函数(位于`kern/process/proc.c`中)负责分配并返回一个新的`struct proc_struct`结构,用于存储新建立的内核线程的管理信息。`ucore`需要对这个结构进行最基本的初始化,你需要完成这个初始化过程。
> 【提示】在alloc_proc函数的实现中需要初始化的proc_struct结构中的成员变量至少包括state/pid/runs/kstack/need_resched/parent/mm/context/tf/cr3/flags/name。
请在实验报告中简要说明你的设计实现过程。请回答如下问题:
+ 请说明`proc_struct``struct context context和struct trapframe *tf`成员变量含义和在本实验中的作用是什么?(提示通过看代码和编程调试可以判断出来)
### `alloc_proc`函数的实现
在进程的三状态模型中,我们说进程的状态除了就绪,运行,等待(挂起)以外,还有两个特殊的状态,即创建和退出。进程的创建态就涉及到这里的`alloc_proc`函数,因此,在`alloc_proc`函数中,除了为了新的进程控制块申请一块空闲的内存空间外,还要为其中的字段进行一些初步的设置,来表示该进程还未完成创建,处于创建态,此时还不可以被调度,亦不可以被杀死。
进程控制块中各个字段的初始化如下:
+ state: 处于创建态,这里用`PROC_UNINIT`表示,即“尚未初始化完成”。
+ pid因为此时还不是一个合法的进程所以给它的pid赋负值来表示这种不合法。这里简单设置为-1。
+ runs: 运行时间,为零。
+ kstack: 内核堆栈。这个进程还刚在初始化啊,相关资源还没有分配,内核堆栈也还没有建立好,设置为空指针`NULL = 0`。此外的资源信息都没有,即`mm = NULL`
+ need_resched: 不可调度。
+ parent设置为当前进程`current`
+ context和tf和flags是用于恢复进程现场的信息这还没有现场啊都设置为零。
+ cr3由于是内核线程和操作系统共用页表设置为`boot_cr3`
大体上就是这样,具体的代码如下:
```c
alloc_proc(void) {
struct proc_struct *proc = kmalloc(sizeof(struct proc_struct));
if (proc != NULL) {
proc->state = PROC_UNINIT;
proc->pid = -1;
proc->runs = 0;
proc->kstack = 0;
proc->cr3 = boot_cr3;
proc->flags = 0;
proc->parent = NULL;
proc->mm = NULL;
proc->tf = NULL;
set_proc_name(proc, "undefined");
proc->need_resched = 0;
proc->context.eip = 0;
proc->context.esp = 0;
proc->context.ebx = 0;
proc->context.ecx = 0;
proc->context.edx = 0;
proc->context.esi = 0;
proc->context.edi = 0;
proc->context.ebp = 0;
}
return proc;
}
```
这只是我的版本,其中是存在问题的,比如我没有设置`parent`信息,上面也说了应该设置为`current`。不过其他的都差不多,只是我的实现不优雅而已,比如说老师的代码里面对`context`的设置只有一句`memset(&(proc->context), 0, sizeof(struct context));`就完成了,我就比较蠢了。