第三章、Unix/Linux进程管理
一、多任务管理
二、进程的概念
进程是对映像的执行。
操作系统内核将一系列执行视为使用系统资源的单一实体.系统资源包括内存空间、I/O设备以及最重要的CPU时间.在操作系统内核中,每个进程用一个独特的数据结构表示,叫做进程控制块或任务控制块等.
三、多任务处理系统
- type.h文件
type.h文件定义了系统常熟和表示进程的简单PROC结构体.
2.ts.s文件
ts.s在32位GCC汇编代码中可实现进程上下文切换
3.queue.c文件
queue.c文件可实现队列和链表操作函数.enqueue()函数按优先级将PROC输入队列中.在优先级队列中,具有相同优先级的进程按先进先出(FIFO)的顺序排序.dequeue()函数可返回从队列或链表中删除的第一个元素.printList()函数可打印链表元素.
4.t.c文件
t.c文件定义MT系统数据结构、系统初始化代码和进程管理函数.
5.多任务处理系统代码介绍
我们通过以下步骤来介绍MT系统的基本代码
(1) 虚拟CPU:MT系统在Linux下编译链接为
gcc -m32 t.c ts.s
然后运行a.out.整个MT系统在用户模式下作为Linux进程进行。在Linux进程中,我们创建了多个独立执行实体(叫作任务),并通过我们自己的调度算法将它们调度到Linux进程中运行。
(2) init():当MT系统启动时,main()函数调用init()以初始化系统。Init()初始化PROC结构体,并将它们输入freeList中。它还将readyQueue初始化为空。
(3) P0调用kfork()来创建优先级为的子进程P1,并将其输入就绪队列中。然后P0调用tswitch(),将会切换任务以运行P1.
(4) tswitch():tswitch()函数实现进程上下文切换。
(5) .1 tswitch中的SAVE函数:当正在执行的某个任务调用tswitch()时,它会把返回地址保存在堆栈上,并在汇编代码中进入tswitch()。
(6) .2 scheduler():在执行了tswitch()中的SAVE函数之后,任务调用scheduler()来选择下一个正在运行的任务。
(7) .3 tswitch()中的RESUME函数:当执行从scheduler()返回时,“运行”可能已经转而指向另一个任务的PROC。
(8) Kfork():kfork()函数创建一个子任务并将其输入readyQueue中。
(9) body():为便于演示,所有创建的任务都执行同一个body()函数。
(10) 空闲任务P0:P0的特殊之处在于它在所有任务中具有最低的优先级。
(11) 运行多任务处理(MT)系统:在Linux下,输入gcc -m32 t.c s.s 编译链接MT系统并运行所得到的a.out。
四、进程同步
一个操作系统包含许多并发进程,这些进程可以彼此交互。进程同步是指控制和协调进程交互以确保其正确执行所需的各项规则和机制。最简单的进程同步工具是休眠和唤醒操作。
1.睡眠模式
当某进程需要某些当前没有的东西时,例如申请独占一个存储区域、等待用户通过标准输入来输入字符等,它就会在某个事件值上进入休眠状态,该事件值表示休眠的原因。
2.唤醒操作
多个进程可能会进入休眠状态等待同一个事件,这是很自然的,因为这些进程可能都需要同一个资源,例如一台当前正处于繁忙状态的打印机。
五、进程终止
在操作系统中,进程可能终止或死亡,这是进程终止的通俗说法。如第二章所述,进程能以两种方式终止:
正常终止:进程调用exit(value),发出_exit(value)系统调用来执行在操作系统内核中的kexit(value),这都是我们本节要讨论的情况。
异常终止:进程因某个信号而异常终止。
在这两种情况下,当进程终止时,最终都会在操作系统内核中调用kexit()。
1.kexit()的算法
MT系统中的所有进程都以操作系统(OS)模拟内核模式运行。因此,它们没有任何用户模式上下文。
2.进程家族树
通常,进程家族树通过个PROC结构中的一对子进程和兄弟进程指针以二叉树的形式实现,如:PROC *child, *sibling, *parent;
其中,child指向进程的第一个子进程,sibling指向同一个父进程的其他子进程。
3.等待子进程终止
在任何时候,进程都可以调用内核函数
pid = kwait(int *status)等待僵尸子进程。如果成功,则返回的pid是僵尸子进程的pid,而status包含僵尸子进程的退出代码。此外,kwait()还会将僵尸子进程释放回freeList以便重用。
六、MT系统中的进程管理
七、Unix/Linux中的进程
1.进程来源
当操作系统启动时,操作系统内核的启动代码会强行创建一个PID=0的初始进程,即通过分配PROC结构体(通常是proc[0])进行创建,初始化PROC内容,并让运行指向proc[0].
2.INIT和守护进程
当进程P1开始运行时,它将其执行映像更改为INIT程序。因此P1通常被称为INIT进程,因为它的执行映像是init程序。P1开始复刻出许多子进程。
3.登录进程
4.sh进程
5。进程的执行模式
在Unix/Linux中,进程以两种不同的模式执行,即内核模式和用户模式,简称Kmode和Umode。在每种执行模式下,一个进程有一个执行映像。
Umode进程只能通过以下三种方式进入Kmode:
(1)中断:中断是外部设备发送给CPU的信号,请求CPU服务。
(2)陷阱:陷阱是错误条件,例如无效地址、非法指令、除以0等,这些错误条件被CPU识别为异常,使得CPU进入Kmode来处理错误。
(3)系统调用:系统调用(简称syscall)是一种允许Umode进程进入Kmode以执行内核函数的机制。
八、进程管理的系统调用
1.fork()
fork()创建子进程并返回子进程的pid,如果fork()失败则返回-1.在Linux中,每个用户在同一时间只能有数量有限的进程。
2.进程执行顺序
在fork()完成后,子进程与父进程和系统中所有其他进程竞争CPU允许时间。接下来运行哪个进程取决于它们的调度优先级,优先级呈动态变化。
3.进程终止
执行程序映像的进程可能以两种方式终止:
(1)正常终止
(2)异常终止
在执行某程序时,进程可能会遇到错误,如非法指令、越权、除零等,这些错误会被CPU识别为异常。当某进程遇到异常时,它会阱入操作系统内核。内核的异常处理程序将陷阱错误类型转换为一个幻数,称为信号,将信号传递给进程,使进程终止。
4.等待子进程终止
在任何时候,一个进程都可以使用
int pid = wait(int *status);
系统调用,等待僵尸子进程。
5.Linux中的subreaper进程
6.exec():更改进程执行映像
进程可以使用exec()将其Umode映像更改为不同的(可执行)文件。exec()库函数有几个成员
7.环境变量
环境变量是为当前sh定义的变量,由子sh或进程继承。当sh启动时,环境变量即在登录配置文件和.bashrc脚本文件中设置。它们定义了后续程序的执行环境。各环境变量定义为:关键字=字符串
在sh会话中,用户可使用env或printenv命令查看环境变量。
SHELL:指定将解释任何用户命令的sh
TERM:指定运行sh时要模拟的终端类型
USER:当前登录用户
PATH:系统在查找命令时将检查的目录列表
HOME:用户的主目录。在Linux中,所有用户主目录都在/home中。
在sh会话中,可以将环境变量设置为新的(字符串)值,如:
HOME=/home/newhome
可通过EXPORT命令传递给后代sh,如:
export HOME
也可以将它们设置为空字符串来取消设置。
九、I/O重定向
1.文件流和文件描述符
如前所述,sh进程有三个用于终端I/O的文件流:stdin(标准输入)、stdout(标准输出)和stderr(标准错误)。每个流都是指向执行映像堆区中FILE结构体的一个指针。每个文件流对应Linux内核中的一个打开文件。每个打开文件都用一个文件描述符(数字)表示。
2.文件流I/O和系统调用
当进程执行库函数
scanf("%s",&item);
它会试图从stdin文件输入一个(字符串)项,指向FILE结构体。
3.重定向标准输入
如果我们用一个新打开的文件来替换文件描述符0,那么输入来自该文件而不是原始输入设备。
4,重定向标准输出
十、管道
管道是用于进程交换数据的单向进程间通信通道。命名管道是不相关进程之间的FIFO通信通道。
1.Unix/Linux中的管道编程
2.管道命令处理
在Unix/Linux中,命令行
cmd1 |cmd2
包含一个管道符号“|”。sh将通过一个进程运行cmd1,并通过另一个进程运行cmd2,它们通过一个管道连接在一起,因此cmd1的输出变成cmd2的输入。
3.将管道写进程与管道读进程连接起来
(1)当sh获取命令行cmd1|cmd2时,会复刻出一个子进程sh,并等待子进程sh照常终止。
(2)子进程sh:浏览命令行中是否有|符号。在这种情况下,
cmd1 |cmd2
有一个管道符号|。将命令行划分为头部=cmd1,尾部=cmd2
4.命名管道
十一、sh模拟器
1.带有I/O重定向的单命令
2.带有管道的命令
3.ELF可执行文件与sh脚本文件