进程管理
基本概念
程序或作业以进程的方式并发执行的
前驱图:是一个有向无循环图 ,记为DAG。用于描述进程之间执行的前后关系(由于某种条件制约)
可以使用前驱图来表示进程执行时要存在的前驱关系,如果存在直接前驱关系的进程只能按照顺序执行。没有任何关系/没有直接前驱关系的进程是可以任意并发执行。
对于单道环境而言:程序顺序执行。(顺序性 封闭性{程序运行时独占全机资源} 可再现性{只要初始条件相同,程序执 行结果是相同的})
对于多道环境而言:程序并发执行。(间断性 失去封闭性 和 再现性)
并发 + 封闭性 -> 可再现性 (在进程切换时 进行现场的保护)
进程:是为了保证程序能够正确地并发运行(过程:动态性)而采取的一种执行方式,它也是系统进行资源分配、调度和保护的基本单位
PCB(进程控制块):在发生进程切换时,用一个PCB保存被暂停程序的运行现场--关于程序运行的一切信息和数据(新的数据结构)
进程 = PCB + 程序的代码和数据
进程是一个能够独立运行的实体和单位
进程的特征:
-
结构特征(PCB + 数据段 + 程序段 + 系统栈 + 用户栈)
-
动态性(由系统创建而产生,由系统调度执行,由系统撤销摧毁)
-
并发
-
独立性(独立运行、独立分配资源、独立接受调度的基本单位)
-
异步性
进程的三种基本状态
-
就绪状态(高级调度 由外存调入内存 系统创建进程 加入到内存的就绪对列中)
-
执行状态(进程在内存中获取处理机之后进行执行自身的代码)
-
阻塞状态(由于资源的短缺或自身的IO操作而引发阻塞)
-
挂起状态(由外部力量如终端用户/父进程/负荷调节的需要 强制使进程放弃处理机而转入的状态)
PCB | 进程标识符 | 处理机状态 | 进程调度信息 | 进程控制信息 |
---|---|---|---|---|
说明: | 内部标识符(数字) | 是由处理机的各种寄存器中的内容组成 的 | 与进程调度和进程对换有关的信息 | 程序段/数据段地址.. |
外部标识符(由创建者提供) | 进程状态/进程优先级.. |
进程控制
原语
操作系统提供各种进程控制原语。用户可以使用原语实现各种操作。原语也称为原子操作。
两个层级
-
指令级:该指令(原语)在执行期间不能中断,务必保证指令执行的完整性
-
功能级(函数):作为原语的程序段不允许并发执行—保证原语执行的完整性
原语常驻内存,只能在核心态运行。
进程控制原语:
-
创建原语
-
撤销原语
-
阻塞原语
-
唤醒原语
-
挂起原语
-
激活原语
进程创建
进程创建可以以系统调用(操作系统编程接口)的方式调用进程创建原语
一个进程可以创建另外一个进程。子进程也可以创建其子进程。由PCB中的ppid
和cpid
的数据项用来保存父子进程的信息。
子进程被创建时继承父进程的所有资源:创建一个子进程,在进程管理上,实际上是为子进程分配一个PCB,会在内存中为子进程开辟内存,将父进程的代码和数据copy到子进程的内存中。子进程可以具有自己的执行程序。
继承创建过程:
-
申请空白PCB
-
分配必要的空间资源(资源分配)
-
初始化PCB
-
将创建的进程加入就绪队列,等待CPU
父子进程的与运行方式:
-
父进程和子进程并发执行
-
当创建子进程之后,父进程阻塞,等待它创建的子进程执行完毕后执行
进程的终止
引发进程终止的事件:
-
正常结束
-
越界错误
-
保护错(进程访问不允许访问的文件)
-
非法指令(试图调用不存在的指令)
-
特权指令错(试图调用OS执行的指令)
-
运行超时
-
等待超时
-
算是运算错
-
I/O故障
-
外界干预(用户、父进程请求、父进程终止{不一定})
终止的过程:
-
访问PCB读取进程当前运行的状态
-
若执行中,则终止进程的执行
-
若存在子进程,将其所有的子进程终止,防止子进程失控
-
释放进程所有资源
-
将其PCB从所在的队列移除
阻塞/唤醒
引起进程阻塞和唤醒的事件:
-
请求系统服务
-
启动某种操作
-
新数据尚未到达
-
无新工作可做
阻塞过程:
-
进程停止执行
-
保护现场PCB信息
-
改变状态
-
将PCB挂在阻塞队列中
唤醒过程:
-
唤醒条件出现,产生中断
-
从阻塞队列中移除该PCB
-
将PCB放入就绪队列
挂起/激活
进程同步
异步关系:以进程为例,进程之间没有制约关系,可以任意并发执行,称这些进程之间是异步关系。
同步关系:进程之间存在制约关系,进程不能以任意方式并发执行,必须按照制约要求在特定带你顺序执行,称这些进程具有同步关系
信号量机制:类似于信号灯的数据结构,用来保证进程间能按照规定顺序执行。
分类:
-
同步:进程之间的协作关系。
-
互斥:竞争访问临界资源,由于竞争,所以进程只能以顺序的方式访问资源
临界资源(独占资源):在某段时间内只允许一个进程使用的资源
临界区:使用临界资源的代码段。
信号量机制
使用软件的方式实现进程同步和互斥:
两个或多个进程可以通过简单的信号进行合作,一个进程可以被迫在某一位置停止,直到它接收到一个特定的信号
任何复杂的合作需求都可以通过适当的信号结构得到满足
为通过信号量s传送资源释放信号,进程可以执行Signal(s)原语
为通过信号量s申请资源使用请求,进程可以执行Wait(s)原语
信号量通常是一个具有整数值的变量,在它之上定义三个操作(部分是原语):
-
初始化。(根据系统中的资源进行对信号量的初始化)
-
wait操作/P操作:原语
-
信号量--;是进程申请资源的过程
-
若值为负数,则执行wait操作的进程被阻塞。否则进程继续执行
-
-
signal操作/V操作:原语
-
信号量++;是进程释放资源的过程
-
若值小于等于零,则被wait操作阻塞的进程被解除阻塞。
-
struct semaphore { int count; queueType queue; } void wait(semaphore s) { s.count --; if(s.count < 0) { // 将当前进程阻塞 // 将当前进程插入到等待队列 } } void signal(semaphore s) { s.count ++; if(s.count <= 0) { // 在等待队列中选择一个进程P(也可能是在等待队列中进程相互竞争的过程) // 将进程P加入到就绪队列中 } }
经典问题
生产者-消费者问题
一组生产者进程向一组消费者进程提供产品,两类进程共享一个由n个缓冲区组成的有界缓冲池,生产者进程向空缓冲池中投放产品,消费者进程从放有数据的缓冲池中取得产品并消费掉。 只要缓冲池未满,生产者进程就可以把产品送入缓冲池;只要缓冲池未空,消费者进程便可以从缓冲池中取走产品。 但禁止生产者进程向满的缓冲池再输送产品,也禁止消费者进程从空的缓冲池中提取产品。为了防止对缓冲池重复操作,故规定在任何时候,只有一个主体可以访问缓冲池。
循环缓冲结构:
输入指针变量in指向下一个可存放产品的空缓冲区
输出指针变量out指向下一个可从中获取产品的满缓冲区
生产者放入物品:in = (in + 1) % n
消费者提取物品:out = (out + 1) % n
当 in = out 缓冲池为空
当 (in + 1) % n = out 缓冲池满
信号量的设计:
-
同步信号量:
empty = n // 显示当前缓冲池的可用空间 -> 生产者
full = 0 // 溴铵是当前缓冲池中物品的数量 -> 消费者
-
互斥信号量:
mutex = 1 // 缓冲池 生产者消费者互斥使用
Object buffer[n]; // buffer pool int in = 0, out = 0; struct semaphore{ int mutex = 1; int empty = n; int full = 0; } void producer(){ while(1) { wait(empty); wait(mutex); buffer[in] = p; in = (in + 1) % n; signal(mutex); signal(full); } } void consumer() { while(1) { wait(full); wait(mutex); object = buller[out]; out = (out + 1) % n; signal(muetx); signal(empty); } }
注:在wait操作时,不能先对互斥信号量进行wait操作,否则会引起死锁现象的发生。
哲学家进餐
有5位哲学家倾注毕生精力用于思考和吃饭, 他们围坐在一张圆桌旁,在圆桌上有5个碗和5支筷子。每位哲学家的行为通常是思考,当其感到饥饿时,便试图取其左右最靠近他的筷子进餐。只有他拿到两支筷子后才能进餐,进餐完毕后,释放两支筷子并继续思考
对于筷子来说是互斥资源,为了防止思索地产生:
-
至多只允许有4位哲学家同时进餐,这样就能保证至少有一个哲学家能拿到两支筷子。
-
仅当哲学家左、右两支筷子可用时,才允许他拿起筷子进餐。
-
规定奇数号哲学家先拿他左边的筷子,然后再去拿右边的筷子;而偶数号哲学家则相反。 最后总会有一位哲学家能获得两支筷子而进餐
struct semaphore{ int chopstick[5]; // 筷子的互斥资源 int limit = 4; // 最多允许4名学家进餐 } void philosopher(int i) { while(1) { thinking(); wait(limit); // 对人数的限制 wait(chopstick[i]); // 左侧筷子临界访问 wait(chopstick[(i + 1) % 5]); // 右侧筷子 同时满足 eating(); signal(limit); signal(chopstick[i]); signal(chopstick[(i + 1) % 5]); } }
读者写者问题
多个进程间共享一个数据文件,把只要求读文件的进程称为Reader进程,其它的进程称为Writer进程。允 许多个Reader进程同时读取共享文件,但不允许一个 Writer进程和其他Reader进程或Writer进程同时访问共享对象
struct semaphore{ int wmutex = 1;// 互斥信号量,用于区别读者和写者两种状态 int rmutex = 1; } int readcount = 0;// 读者数量 void Reader(){ while(1) { wait(rmutex);// rmutex 用于互斥修改readcount值的互斥信号量 if(readcount == 0) wait(wmetux); // 当readcount == 0时,要看一下有没有wmetux的访问,如果有 Reader阻塞 readcount ++; signal(rmutex); reading(); wait(rmetux); // 互斥对readcount的修改 readcount --; signal(rmutex); if(readcount == 0) signal(wmutex); } } void Wirter() { while(1) { wait(wmutex); writing(); signal(wmutex); } }
00
进程通信
进程通信:进程之间的信息交换。其所交换的信息量少则一个状态或数值,多则成千上万个字节!
信号量机制作为同步工具是卓有成效的,但是作为进程通信工具还不够理想,主要表现在:
-
效率低
-
通信对用户不透明。用户干预太多 (不透明意味着用户对于处理的每一个细节都是可见的, 需要用户编程处理或干预)
类型:
-
共享存储器系统
-
消息传递系统(广泛)
-
管道通信系统
管道通讯
管道:⭐是指用于连接一个读进程和一个写进程 以实现他们之间通信的一个共享文件 向管道(共享文件)提供输入的发送进程(即写进程), 以字符流形式将大量的数据送入管道;而接受管道输出的接收进程(即读进程),则从管道中接收(读)数据。
管道机制协调能力:
-
互斥:当一个进程正在对pipe执行读/写操作时,其它(另一)进程必须等待—单工通信
-
同步:当写(输入)进程把一定数量(如4 KB)的数据写入pipe,便去睡眠等待,直到读(输出)进程取走数据后,再把他唤醒。当读进程读一空pipe时,也应睡眠等待,直至写进程将数据写入管道后,才将之唤醒
-
确定对方是否存在,只有确定了对方已存在时,才能进行通信。
消息传递通讯实现方式
直接通信方式:
发送进程和接收进程都以显式方式提供对方的标识符。
系统提供下述两条通信命令(原语):
-
Send(Receiver, message); 发送一个消息给接收进程;
-
Receive(Sender, message); 接收Sender发来的消息;
间接通信方式:
进程之间通过某种中间实体(信箱)进行通讯
一个进程可以将消息发送给信箱,另外一个进程从信箱获得属于它的消息
进程同步方式:
-
发送进程阻塞、 接收进程阻塞
-
发送进程不阻塞、 接收进程阻塞(应用最广的进程同步方式
-
发送进程和接收进程均不阻塞
消息缓冲队列通信机制
消息缓冲区:在消息缓冲队列通信方式中,主要利用的数 据结构是消息缓冲区。
PCB中有关通信的数据项:消息队列进行操作和实现同 步的信号量
线程
引入
进程的角色:
-
资源分配的基本单位
-
调度的基本单位---调度其实也是分配资源
-
独立运行的基本单位---独立运行的实体
进程拥有资源这一特点,使得进程的“体量”可 能很大,这使得在进行进程调度的时候,有较大的时空开销。切换的开销将限制系统并发程度的提高
若将进程的的两个属性分开,由操作系统分开处理:使得一部分运行实体,作为调度和分派的基本单位,但不拥有资源,以做到“轻装上阵”
而对于拥有资源的基本单位—进程,又不对之进行频繁的切换。进而形成了线程的概念
基本概念
在线程模型里面,进程一方面可以克隆自己,创建一个子进程与其并发执行。另一方面,进程可以把自己代码的一部分(例如代码中某个函 数)创建(不是克隆)一些迷你执行体(线程),让迷你执行体与自己并发执行。
这种迷你执行体,不拥有资源,共享创建它的进程的资源,称之为线程。
线程是系统中轻量化的运行实体,它仅仅拥有ID信息、处理机状态和调度相关的状态信息等少量信息
因此,在线程模型里面,进程和线程都是执行体,两种执行体并发执行(混合并发执行)
一个进程可以创建自己的线程,这些线程共享进程的资源(进程地址空间,数据、代码和其它资源)
属性:
-
同一进程下的线程,共享该进程资源(一个进程的所有线程都具有相同的地址空间
-
轻型运行实体
-
独立调度和分派的基本单位。
-
可并发执行
线程的状态
-
状态参数:线程标识符和一组状态参数进行᧿述
-
线程运行状态:执行状态、就绪状态、阻塞状态
线程的创建:
在创建新线程时,需要利用一个线程创建函数(或系统调用),并提供相应的参数:
-
指向线程主程序的入口指针(线程要执行的函数地址
-
堆栈的大小
-
用于调度的优先级等
在线程创建函数执行完后,将返回一个线程标识符供以后使用。
线程间的同步和通信
互斥锁mutex
可用两条命令(函数)对互斥锁进行操作:
-
lock 关锁 (wait
-
unlock 开锁 (signal
条件变量 Solaris
每一个条件变量必须与一个互斥锁一起使用,亦即,在创建一个互斥锁时便联系着一个条件变量
信号量机制
-
私用信号量(同一进程中各线程间的同步
-
公用信号量(实现不同进程中各线程之间的同步
线程的实现
线程的实现分成三类:
-
内核支持线程
-
用户级线程
-
混合支持线程