第三章学习笔记
第三章学习笔记
一、知识点概述
摘要
本章讨论了 Unix/Linux 中的进程管理;阐述了多任务处理原则;介绍了进程概念;以一个编程示例来说明多任务处理、上下文切换和进程处理的各种原则和方法。
多任务处系统支持动态进程创建、进程终止,以及通过休眠与唤醒实现进程同步、进程关系,以及二叉树的形式实现进程家族树,从而允许父进程等待子进程终止;提供了一个具体示例来阐释进程管理函数在操作系统内核中是如何工作的;然后,解释了 Unix/Linux 中各进程的来源,包括系统启动期间的初始进程、INIT 进程、守护进程、登录进程以及可供用户执行命令的 sh 进程;接着,对进程的执行模式进行了讲解,以及如何通过中断、异常和系统调从用户模式转换到内核模式;
再接着,描述了用于进程管理的 Unix/Linux 系统调用,包括 fork、wait、exec 和 exit ;阐明了父进程与子进程之间的关系,包括进程终止和父进程等待操作之间关系的详细描述;解释了如何通过 INIT 进程处理孤儿进程,包括当前 Linux 中的 subreaper 进程,并通过示例演示了 subreaper 进程; 并通过示例演示了subreaper 进程;接着,详细介绍了如何通过exec 更改进程执行映像,包括 execve 系统调用、命令行参数和环境变量;解释了I/O重定向和管道的原则及方法,并通过示例展示了管道编程的方法。
3.1多任务处理
多任务处理指的是同时进行几项独立活的能力。多任务处理是通过在不同任务之间多路复用CPU的执行时间来实现的,即将CPU执行操作从一个任务切换到另一个任务。如果切换速度
足够快,就会给人一种同时执行所有任务的错觉这种逻辑并行性称为“并发”。
3.2 进程的概念
进程是对映像的执行。
3.3多任务处理系统
组成部分:
type.h文件:定义了系统常数和表示进程的简单PROC结构体
ts.s文件:在32位GCC汇编代码中可实现进程上下文切换
queue.c文件:可实现队列和链表操作函数
t.c文件:t.c文件定义MT系统数据结构、系统初始化代码和进程管理函数
3.3.1 queue.c文件
queue.c文件可实现队列和链表操作函数。enqueue()函数按优先级将PROC输入队列中。在优先级队列中,具有相同优先级的进程按照FIFO的顺序排序。dequeue()函数可返回从队列或链表中删除的第一个元素。printList()函数可打印链表元素。
3.3.5 多任务处理系统代码介绍
(1)虚拟CPU:MT系统在Linux下编译链接为
gcc -m32 t.c ts.s
(2)init():当MT系统启动时,main()函数调用init()以初始化系统。
(3)P0调用kfork()来创建优先级为1的子进程P1,并将其输入就绪队列中。
(4)tswitich():tswitch()函数实现进程上下文切换。
(5).1 tswitch()中的SAVE函数:当正在执行的某个任务调用tswitch()时,它会把返回地址保存在堆栈上,并在汇编代码中进入tswitch()。
(6).2 scheduler():在执行了SAVE函数之后,任务调用scheduler()来选择下一个正在运行的任务。
(7).3 tswitch()中的RESUME函数
(8)kfork():kfork()函数创建一个子任务并将其输入readyQueue中。
(9)body():所有创建的任务都执行同一个body()函数。
(10)空闲任务 P0:P0的特殊之处在于它所在任务中具有最低的优先级
(11)运行多任务处理(MT)系统
3.4进程同步
3.4.1睡眠模式
为实现休眠操作,我们可以在 PROC结构体中添加一个event字段,并实现ksleep(int event)函数,使进程进入休眠状态。接下来,我们将假设对 PROC结构体进行修改以包含加粗显示的添加字段。
typedef struct proc{
struct proc next;
int ksp;
int pid;
int ppid;
int status;
int priority;
int event;
int exitCode;
struct proc child;
struct proc sibling;
struct proc parent;
int kstack[1024];
} PROC;
3.4.2唤醒操作
当某个等待时间发生时,另一个执行实体(可能是某个进程或中断处理程序)将会调用 kwakeup(event)。唤醒正处于休眠状态等待该事件值的所有程序。如果没有任何程序休眠等待该程序,kwakeup()就不工作,即不执行任何操作。Kwakeup()的算法是
/***** Algorithm of kwakeup(int event)*********/
// Assume SLEEPing proCs are in a global sleepiist
for each PROC *p in sleepList do {
if (p->event == event){
delete D from sleepLiBt;
p->8tatu8 = READY;
enqueue(EreadyQueue,p);
}
}
3.5进程终止
正常终止:进程调用exit(value),发出 exit(value)系统调用来执行在操作系统内核
中的 kexit(value),这就是我们本节要讨论的情况。
异常终止:进程因某个信号而异常终止。信号和信号处理将在后面第6章讨论。
在这两种情况下,当进程终止时,最终都会在操作系统内核中调用kexit()。
进程家族树

3.5.3等待子进程终止
在任何时候,进程都可以调用内核函数
pid = kwait(int *status)
等待僵尸子进程。如果成功,则返回的 pid是僵尸子进程的 pid,而 status包含僵尸子进程的退出代码。此外,kwait()还会将僵尸子进程释放回 freeList 以便重用.
3.6MT系统中的进程管理
完善基础MT系统,实现MT系统的进程管理函数:
(1)用二叉树的形式实现进程家族树。
(2)实现 ksleepO()和kwakeup()进程同步函数。
(3)实现kexit()和kwait()进程管理函数。
(4)添加"w"命令来测试和演示等待操作。
修改后的MT系统的输出示例
3.7 Unix/Linux中的进程
中断:中断是外部设备发送给CPU的信号,请求CPU服务。当在Umodie下执行时,CPU中断是启用的,因此它将响应任何中断。在中断发生时,CPU将进入Kmode来处理中断,这将导致进程进人Kmode。
陷附:陷是错误条件,例如无效地址、非法指令、陈以0等、这些错误条件被CPU识别为异常,使得CPU进人Kmode来处理错误。在Unix/Linux中,内核陷阴处理程序将陷研原因转换为信号编号,并将信号传递给进程。对于大多数信号,进程的默认操作是终止。
系统调用:系统调用(简称syscall)是一种允许Umode进程进入Kmode以执行内核函数的机制。当某进程执行完内核函数后,它将期望结果和一个返回值返回到Umode,该值通常为0(表示成功)或-1(表示错误)。如果发生错误,外部全局变量errno(在erno.h中)会包含一个ERROR代码,用于标识错误。
3.8进程管理的系统调用
3.8.3进程终止
1.正常终止:回顾前面的内容,我们知道,每个C程序的 main()函数都是由C启动代码 crt0.o调用的。如果程序执行成功,main()最终会返回到 crt0.o,调用库函数 exit((0)来终止进程。首先,exit(value)函数会执行一些清理工作,如刷新 stdout、关闭I/O流等。然后,它发出一个_exit(value)系统调用,使进入操作系统内核的进程终止。
2.异常终止:在执行某程序时,进程可能会遇到错误,如非法指令、越权、除零等,这些错误会被 CPU识别为异常。当某进程遇到异常时,它会进入操作系统内核。内核的异常处理程序将陷阱错误类型转换为一个函数,称为信号,将信号传递给进程,使进程终止。
3.9 I/O重定向
标准输入:如果用一个新打开的文件来替换文件描述符0,那么输入将来自该文件而不是原始输入设备。
标准输出:更改文件描述符1,指向打开的文件名,然后stdout的输出将会转到该文件而不是屏幕;同样也可以将stderr重定向到一个文件。
3.10管道
管道是用于进程交换数据的单向进程间通信通道。管道有一个读取端和一个写入端。可从管道的读取端读取写入管道写入端的数据。自从管道在最初的Unix 中首次出现以来,已经被用于几乎所有的操作系统中,有许多变体。一些系统允许双向管道,在双向管道上,数据可以双向传输。普通管道用于相关进程。命名管道是不相关进程之间的 FIFO通信通道。但是,如果管道不再有读进程,写进程必须将这种情况视为管道中断错误,并中止写入。
3.10.1 Unix/Linux系统中的管道编程


浙公网安备 33010602011771号