0%

rCore_Tutorial_CP5

rCore_Tutorial_CP5

第五章,进程
看之前感觉好像没什么东西,看到一半感觉还是非常牛逼的

进程概念及重要系统调用

这里面阐述了任务(Task)和进程(Process)之间的区别
说实话很多地方还是觉得很抽象,按照本科期间的说法,进程就是一个运行的二进制程序,但这么说似乎和任务没什么区别。从本章提到的内容上来说,感觉任务是计算机依次加载的一系列程序,以规定的顺序运行,而进程则是通过fork和exec系统调用,在用户的交互下进行,可以选择执行的二进制程序

并且进程间存在父子关系,子进程由父进程fork产生,linux中所有进程均由pid为1的初始进程产生,以前我以为pid为1的进程就是操作系统,现在看来,似乎操作系统(内核)运行在更下一层,而pid为1的进程是内核创建出来对所有进程进行管理并提供用户交互的初始进程

当一个进程退出时,并不会被立即回收,而是成为一个僵尸进程,等待其父进程将其回收,如果父进程一直不退出且不调用waitpid进行回收,则该进程会一直存在。僵尸进程不占用存储空间,其调用exit()退出时内存空间已经被释放,然而其仍然会占用一个pid和内核控制块数据

而父进程先于子进程退出时,子进程并不会随之退出,而是成为一个所谓的孤儿进程,然后被pid1的进程领养,被接管为pid1的子进程,并在退出后由该进程回收。
这里实现的pid1进程即为一个循环调用waitpid的回收进程

fork,exec,waitpid

实现进程所需的三个关键系统调用。
fork用于产生一个完全相同的进程,堆栈空间布局完全一致,在copy on write的情况下也许只会在内核中创建一个对应的控制块而内存映射一致?
exec,将fork出来的内存清空,载入指定二进制程序并重新初始化堆栈。
waitpid,父进程用于回收子进程的系统调用,发起该系统调用后,父进程会等待子进程退出,并回收其占用的资源(资源回收其实是操作系统做吧?),也算是一种父子进程的同步

进程管理的核心数据结构

对于不动手开摆的人来说这一部分主要是用于理解实现原理
由于还没有实现文件系统,应用程序还是直接链接到内核里面,并且添加全局符号进行加载
PCB相较于TCB并无太大变化,就是额外记录了一下pid,然后为了维护父子进程关系,添加了指向父子进程的PCB指针,在内核中,PCB就代表着一个进程的全部,内核应当以PCB为进程控制的单元,由于子进程是由父进程fork出来的,所以直接在PCB里面实现fork和exec两个系统调用,让父进程管理子进程的PCB

CPU调度方面,switch汇编存寄存器经典换入换出不变,但原来集成在task manager里面的current task取了出来,单独用processor维护,表示在当前处理器上运行的进程,也是为以后考虑多核做准备吧

除此之外,processor实现了一个idle控制流,在内核中单独建立了一条控制流,在启动第一个任务时在run_tasks换出。而之后的任务切换均由_switch换回idle控制流,即回到run_tasks上下文中,该方法是一个循环,重复取出可用task并从idle切换出去

进程管理机制的设计实现

主要看fork和exec的实现吧。这里的fork就是简单的复制,基础教程当然不需要上copy on write这种高级技巧。先创建两份一样的虚拟页表,然后将父进程映射的页面遍历一遍,给子进程也映射进去

fork在PCB中实现进程空间的创建复制和初始化,再进行封装到sys_fork,由于子进程和父进程拥有完全一致的trap上下文(fork系统调用trap进内核),因此从trap后恢复时即可从同样的状态下继续运行,然而,fork需要以不同的返回值来区分父子进程,因此,直接通过修改trap上下文中a0寄存器对应的值进行返回值的区分

exec是被fork出的子进程再次发起的系统调用,在该次系统调用中,需要将父进程的地址空间全部清除,加载新的二进制程序,因为该进程发起了exec trap到了内核,同样需要构造一个trap上下文恢复到用户态。
但是这里提到无需保存任务上下文,被暂停的任务才需要保管任务上下文(任务上下文是干什么的来着?是用户在内核态被switch出去的时候,需要把switch前的上下文再存一层用来恢复的)

trap在syscall后也需要进行额外处理,因为exec会覆盖掉整个地址空间,原先的trap context就没了,需要重新获取,从task manager里面拿(这里是直接从Processor上拿现在运行的PCB,也行)

在进程调用exit退出或是因为错误trap到内核被退出时,内核使用exit_current_and_run_next函数处理,将当前processor上的进程标记为Zombie进程,将exit code写入PCB,并遍历其子进程,将所有该进程为负责回收的子进程交由init进程托管回收,最后释放所有物理页。由此可以看出,僵尸进程仅存在PCB等待父进程将其回收,而不占用任何页面

进程调度

这个倒是没什么好看的,都是操作系统课上讲过的几个先来先服务,最短有限,round robin。高级一点的多级队列,然后防饥饿加个老化机制,都是学过的东西了捏

实时系统调度那段不太想看。。。感觉实时这个需求就略微的有些玄学,不知道在什么场景才能应用上

课后练习第三题属于是操作系统经典了