第二章 进程管理
2.1 进程的基本概念
程序的顺序执行特征:
- 顺序性
- 封闭性
- 可再现性
前驱图,有向无循环图(DGA)
程序的并发执行特征:
- 间断性
- 失去封闭性
- 不可再现性
进程的特征和状态:
-
因为程序执行的结果是不可再现的,程序是不能参与并发执行的。
-
为使程序能并发执行,且为了对并发执行的程序进行描述和控制,引入“进程”概念。
-
定义:进程是进程实体的运行过程,是系统进行资源分配和调度的一个基本单位。
-
结构:由程序段、相关的数据段和PCB三部分构成进程实体。总称“进程映像”。
-
特征:
- 动态性(它由创建而产生,由调度而执行,由撤消而消亡)(程序则只是一组有序指令的集合,因而程序是静态的)
- 并发性
- 独立性
- 异步性
-
进程的三种基本状态:
- 就绪状态,就绪队列 :就绪进程排成队列。
- 执行状态
- 阻塞状态
-
三种状态的转换
-
挂起状态
- 引入原因:
- 终端用户的请求
- 父进程请求
- 负荷调节的需要
- 操作系统的需要
- 具有挂起状态的进程状态图
- 引入原因:
-
创建状态和终止状态
- 创建状态:
- 创建一个新进程两个步骤:
- 创建PCB,分配资源,向PCB填写管理信息
- 把该进程转入就绪状态并插入就绪队列之中
- 创建一个新进程两个步骤:
- 终止状态:
- 两个步骤:
- 等待操作系统进行善后处理
- 将其PCB清零,并将PCB返还系统
- 两个步骤:
- 五种状态转换图
- 创建状态:
-
六种状态转换图
-
进程控制块(PCB):是操作系统中最重要的记录型数据结构
- 作用:使一个在多道程序环境下不能独立运行的程序,成为一个能独立运行的基本单位,一个能与其它进程并发执行的进程。
- OS根据进程的PCB感知到该进程的存在,PCB是进程存在的惟一标志,OS根据PCB来对并发执行的进程进行控制和管理的。
- 因为PCB经常被系统访问,尤其是被运行频率很高的进程及分派程序访问,故PCB应常驻内存。
- 进程控制块中的信息
- 进程标识符:
- 内部标识符:在所有的操作系统中都为每一个进程赋予了一个惟一的数字标识符。
- 外部标识符:它由创建者提供,通常是由字母、数字组成,往往是由用户(进程)在访问该进程时使用。
- 处理机状态:处理机状态信息由处理机的各种寄存器中的内容组成的。
- 进程调度信息:PCB中存放与进程调度和进程对换有关的信息
- 进程状态
- 进程优先级
- 进程调度所需的其他信息
- 事件:指进程由执行状态转变为阻塞状态所等待发生的事件,即阻塞原因。
- 进程控制信息:
- 程序和数据的地址
- 进程同步和通信机制
- 资源清单
- 链接指针
- 进程标识符:
- 进程控制块的组织方式:
- 链接方式
- 索引方式
- 作用:使一个在多道程序环境下不能独立运行的程序,成为一个能独立运行的基本单位,一个能与其它进程并发执行的进程。
2.2 进程控制
进程控制一般是由OS内核中的原语来实现的
- 原语(Primitive)是由若干条指令组成的,用于完成一定功能的一个过程。
- 执行过程中不允许被中断
- 原子操作在管态下执行,常驻内存。
进程的创建
- 进程图
- 引起创建进程的事件:
- 用户登录
- 作业调度:作业由外存进入内存
- 提供服务
- 上述三种情况下,都是由系统内核来创建一个新进程。
- 应用请求
- 进程的创建过程:一旦操作系统发现了要求创建新进程的事件后,便调用进程创建原语Create( )按下述步骤创建一个新进程。
- 申请空白PCB
- 为新进程分配资源(需要知道新进程所需内存大小)
- 初始化进程控制块
- 将新进程插入就绪队列
进程的终止
- 引起进程终止的事件:
- 正常结束
- 异常结束
- 外界干预
- 进程的终止过程:如果系统中发生了上述要求终止进程的某事件,OS便调用进程终止原语Terminate(),按下述过程去终止指定的进程:
- 根据被终止进程的标识符,从PCB集合中检索出该进程的PCB,从中读出该进程的状态。
- 若被终止进程正处于执行状态,应立即终止该进程的执行,并置调度标志为真,用于指示该进程被终止后应重新进行调度其他进程执行。
- 若该进程还有子孙进程,还应将其所有子孙进程予以终止,以防它们成为不可控的进程。
- 将被终止进程所拥有的全部资源,或者归还给其父进程,或者归还给系统。
- 将被终止进程(PCB)从所在队列(或链表)中移出,等待其他程序来搜集信息。
进程的阻塞与唤醒
- 引起进程阻塞和唤醒的事件:
- 请求系统的服务
- 启动某种操作
- 新数据尚未到达
- 无新工作可做
- 进程阻塞过程:正在执行的进程,当发现上述某事件时,无法继续执行,于是进程便主动调用阻塞原语block把自己阻塞。
- 进入block过程后,首先立即停止执行,把进程控制块中的现行状态由“执行”改为“阻塞”
- 并将PCB插入阻塞队列。如果系统中设置了基于不同事件的阻塞队列,则将本进程插入到具有相同事件的阻塞(等待)队列。
- 转调度程序进行重新调度。
- 进程唤醒过程:当被阻塞进程所期待的事件出现时,则由有关进程(如:用完并释放了该I/O设备的进程)调用唤醒原语wakeup( ),将等待该事件的进程唤醒。
- 首先把被阻塞的进程从等待该事件的阻塞队列中移出
- 将其PCB中的现行状态由阻塞改为就绪,
- 然后再将该PCB插入到就绪队列中。
- 注意:block原语和wakeup原语是一对作用刚好相反的原语。因此必须成对使用;否则长久地处于阻塞状态再无机会继续运行。
进程的挂起与激活
- 进程的挂起:当出现引起进程挂起的事件时,系统将利用挂起原语suspend()将指定进程挂起。
- 首先检查被挂起进程的状态,活动就绪状态改为静止就绪;活动阻塞则改为静止阻塞。
- 为了方便用户或父进程考查该进程的运行情况,把该进程的PCB复制到某指定的内存区域。若被挂起的进程正在执行,则转向调度程序重新调度。
- 进程的激活:系统将利用激活原语active( )将指定进程激活。
- 激活原语先将进程从外存调入内存,静止就绪改为活动就绪;静止阻塞改为活动阻塞。
- 假如采用的是抢占调度策略,则每当有新进程进入就绪队列时,应检查是否要进行重新调度。
2.3 进程同步
进程同步的基本概念
- 使并发进程有效共享资源与合作,程序结果可再现
- 两种形式的制约关系
- 间接互相制约关系
- 直接相互制约关系
- 临界资源
- 如打印机、磁带机,进程间应采取互斥方式实现这种资源的共享
- 生产者消费者问题
- 输入指针+1:in=(in+1)%n
- 输出指针+1:out=(out+1)%n
- (in+1)%n=out表示缓冲池满,(out+1)%n=in表示缓冲池空
- 临界区:人们把在每个进程中访问临界资源的那段代码称为临界区(critical section)。
- 在临界区前面增加一段用于进行上述检查的代码,把这段代码称为进入区(entry section)。相应地,在临界区后面也要加上一段称为退出区(exit section)的代码,用于将临界区正被访问的标志恢复为未被访问的标志。
- 进程中除上述进入区、临界区及退出区之外的其它部分的代码,在这里都称为剩余区。
- 同步机制应遵循的规则
- 空闲让进
- 忙则等待
- 有限等待
- 让权等待
信号量机制
-
整型信号量,通过wait(s)和signal(s)来访问,称为P、V操作,不遵循“让权等待”
-
S=+n:n个可用的空闲资源
-
S=0:资源全被占用
-
wait(S){ while(S<=0); S--; } signal(S){ s++; }
-
-
记录型信号量:增加一个进程链表指针,用于链接上述所有等待进程
-
value=+n:n个可用空闲资源
-
value=0:资源全被占用,没有空闲,没有等待进程
-
value=-n:n个进程在等待空闲资源
-
typeof struct{ int value; struct process_control_block *list; }semaphore; wait(semaphore *S){ S->value--; if(S->value < 0) block(S->list); } signal(semaphore *S){ S->value++; if(S->value <= 0) wakeup(S->list); }
-
value初值为1表示只允许一个进程访问资源, 此时信号量为互斥信号量,用于进程互斥
-
-
AND型信号量:若干个临界资源的分配,采取原子操作方式:要么把它所请求的资源全部分配到进程,要么一个也不分配。在wait操作中,增加了一个“AND”条件,故称为AND同步,或称为同时wait操作,即Swait (Simultaneous wait)
-
信号量集:有些情况当资源数量低于某一下限值时,便不予以分配,在每次分配前测试该资源的数量,看其是否大于下限值
- S为信号量,d为需求值,t为下限值
- Swait(S, d, d):此时在信号量集中只有一个信号量S,但允许它每次申请d个资源,当现有资源数少于d时,不予分配。
- Swait(S, 1, 1):此时的信号量集已蜕化为一般的记录型信号量(S>1时)或互斥信号量(S=1时)。
- Swait(S, 1, 0):当S≥1时,允许多个进程进入某特定区;当S变为0后,将阻止任何进程进入特定区。换言之,它相当于一个可控开关。
信号量的应用
- 进程互斥:设置互斥信号量mutex,设其初始值为1
- 前趋关系:
- 例如:设有两个并发执行的进程P1和P2。P1中有语句S1;P2中有语句S2。我们希望在S1执行后再执行S2。
- P1和P2共享一个公用信号量S,并赋予其初值为S=0
- 在进程P1中用: S1; signal(S);
- 在进程P2中用: wait(S); S2;
- 例如:设有两个并发执行的进程P1和P2。P1中有语句S1;P2中有语句S2。我们希望在S1执行后再执行S2。
管程机制
- 定义:代表共享资源的数据结构,以及由对该共享数据结构实施操作的一组过程所组成的资源管理程序,共同构成了一个操作系统的资源管理模块,称之为管程。
- 管程被请求和释放资源的进程所调用。
- 管程由四部分组成:
- 管程的名称
- 局部于管程的共享数据结构说明
- 对该数据结构进行操作的一组过程
- 对局部于管程的共享数据设置初始值的语句
- 管程的特性:
- 模块化
- 抽象的数据类型
- 信息掩蔽
- 管程和进程的差别:
- 进程定义的时私有的数据结构,管程定义的时公共的数据结构
- 进程是与顺序程序有关的操作,管程主要是进行同步操作和初始化操作
- 设置进程的目的是在于实现系统的并发性,管程则是解决共享资源的互斥使用问题
- 进程为主动工作方式,管程为被动工作方式(被进程调用)
- 进程之间能并发执行,管程不能与其调用者并发
- 进程具有动态性,管程仅是操作系统中的一个资源管理模块,供进程调用
- 条件变量:
- 每个条件变量保存了一个链表,用于记录因该条件变量而阻塞的所有进程,同时提供的两个操作,表示为x.wait和x.signal,或者cwait(x)和csignal(x)。
经典进程同步问题
生产者—消费者问题
-
记录型信号量解决:
semaphore mutex = 1, empty = n, full = 0;//初始化 item buffer[n]; int in = 0, out = 0;//指针 void producer(){ do{ ... 产生一个元素nextp ... wait(empty);//先检查是否由空位 wait(mutex);//检查是否有消费者访问临界资源 buffer[in] = nextp;//装入元素 in = (in + 1) % n;//指针后移 signal(mutex); signal(full);//空间满变量加一,表示有一个元素在缓冲池 }while(true); } void consumer(){ do{ wait(full);//检查是否为空 wait(mutex); nextc = buffer[out]; out = (out + 1) % n; signal(mutex); signal(empty); ... 消费nextc中的元素 ... }while(true); } void main(){ cobegin producer(); consumer(); coend }
-
AND信号量解决:
semaphore mutex = 1, empty = n, full = 0;//初始化 item buffer[n]; int in = 0, out = 0;//指针 void producer(){ do{ ... 产生一个元素nextp ... //替换为 Swait(empty, mutex);//-------- buffer[in] = nextp;//装入元素 in = (in + 1) % n;//指针后移 //替换为 Ssignal(mutex, full);//--------- }while(true); } void consumer(){ do{ Swait(full, mutex);//--------- nextc = buffer[out]; out = (out + 1) % n; Ssignal(mutex, empty);//---------- ... 消费nextc中的元素 ... }while(true); } void main(){ cobegin producer(); consumer(); coend }
-
管程解决:
//管程 Monitor producer_consumer{ item buffer[n]; int in, out, count;//count表示缓冲池中已有的产品数目 condition notfull, notempty;//条件变量 void put(item x){ if(count >= n) notfull.wait; buffer[in] = nextp; in = (in + 1) % n; count++; notempty.signal; } void get(item x){ if(count <= 0) notempty.wait; nextc = buffer[out]; out = (out + 1) % n; count--; notfull.signal; } in = 0; out = 0; count = 0; }PC; //生产者-消费者 void producer(){ item x; while(true){ ... 产生一个元素nextp ... PC.put(x); } } void consumer(){ item x; while(true){ PC.get(x); ... 消费元素nextc ... } }
哲学家进餐问题
-
记录型信号量:
semaphore chopstick[5] = {1, 1, 1, 1, 1}; while(true){ wait(chopstick[i]); wait(chopstick[(i+1)%5]); ... eat ... signal(chopstick[i]); signal(chopstick[(i+1)%5]); ... think }
-
AND型信号量:
semaphore chopstick[5] = {1, 1, 1, 1, 1}; while(true){ Swait(chopstick[(i+1)%5], chopstick[i]); ... eat ... Ssignal(chopstick[(i+1)%5], chopstick[i]); ... think }
-
最多允许
semaphore chopstick[5] = {1, 1, 1, 1, 1}; int count = 4; while(true){ wait(count); wait(chopstick[i]); wait(chopstick[(i+1)%5]); ... eat ... signal(chopstick[i]); signal(count); signal(chopstick[(i+1)%5]); ... think }
读者写者问题
-
记录型信号量解决:
semaphore rmutex = 1, wmutex = 1; int readcount = 0; void reader(){ while(true){ wait(rmutex);//控制对readcount的互斥操作 if(readecount == 0) wait(wmutex);//没有读者时(当前为第一个读者),检查是否有写者,上锁 readcount++; signal(rmutex);//对readcount操作完成 ... read ... wait(rmutex); readcount--; if(readcount == 0) signal(wmutex);//最后一个读者释放写者锁 signal(rmutex); } } void writer(){ while(true){ wait(wmutex); ... write ... signal(wmutex); } }
-
信号量集解决:增加了一个限制,即最多只允许 RN 个读者同时读
int RN; semaphore L = RN, mx = 1; void reader(){ while(true){ Swait(L, 1, 1);//L:资源值,1:下限值,1:需求值,进入读者数小于RN时才允许进入 Swait(mx, 1, 0); /* 读者不修改mx 只有mx=1时,读者才可进入 当mx>=1时,允许多个线程进入 当mx=0,阻止任何进程进入 */ ... read ... Ssignal(L, 1); } } void writer(){ while(true){ Swait(mx, 1, 1, L, RN, 0)//仅当既无写者在写,也无读者在读时,writer才能写,这里不修改L ... write ... Ssignal(mx, 1); } }
2.5 进程通信
进程通信的类型
- 共享存储器系统
- 基于共享数据结构的通信方式(低效,少量数据)
- 基于共享存储区的通信方式
- 消息传递系统:进程间的数据交换是以格式化的消息(message)为单位。在计算机网络中,把消息称为报文。
- 直接通信:利用OS提供的发送命令直接发送给目标进程Send(Receiver, message), Receive(Sender, message)
- 间接通信:间接通信方式指进程之间的通信需要通过作为共享数据结构的实体。该实体用来暂存发送进程发送给目标进程的消息,接收进程则从该实体中取出对方发送给自己的消息。通常把这种中间实体称为信箱。
- 管道(pipe)通信系统:
- 管道是指用于连接一个读进程和一个写进程以实现它们之间通信的一个共享文件,又名pipe文件。
- 发送进程(即写进程) 以字符流形式将大量的数据送入管道,接收进程(即读进程) 则从管道中接收(读)数据。
- 管道机制需要提供的协调能力:
- 互斥
- 同步
- 确定对方是否存在
消息缓冲队列通信机制
线程的基本该概念
线程的引入
- 进程的两个基本属性:
- 进程是一个可拥有资源的独立单位
- 进程同时又是一个可独立调度和分派的基本单位
- 将进程的两个属性分开:
- 作为调度和分派的基本单位——线程
- 拥有资源的基本单位,不进行频繁的切换——进程
线程与进程的比较
- 调度:在同一进程中,线程的切换不会引起进程的切换,但从一个进程中的线程切换到另一个进程中的线程时,将会引起进程的切换
- 并发性:在引入线程的操作系统中,不仅进程之间可以并发执行,而且同一个进程中的多个线程之间亦可并发执行
- 拥有资源:线程自己基本不拥有系统资源(仅有一点必不可少的资源),但它可以访问其隶属进程的资源,即一个进程的代码段、数据段及所拥有的系统资源。
- 系统开销:对进程创建或撤消以及切换时,开销(处理资源)明显大于对线程操作的开销。此外,由于一个进程中多个线程具有相同的地址空间,在同步和通信的实现方面线程也比进程容易,甚至都无须OS内核的干预。
- 支持多处理机:多线程可以在多CPU上并行