目录
一、进程的描述与控制
1.1前趋图和程序执行
1.1.1前趋图
前趋图是一个有向无循环图,描述进程之间执行的先后顺序,可记为DAG
图中的每个结点可用来表示一个进程或程序段,乃至一条语句;结点之间的有向边则表示两个结点之间存在的偏序或前趋关系(可用"->"表示)
没有前趋的结点称为初始结点,没有后继的结点称为终止结点
每个结点具有一个重量,表示该结点含有的程序量或程序的执行时间
前趋图中不允许存在循环,否则必然产生不可能实现的前趋关系
1.1.2程序顺序执行
一个应用程序由若干个程序控制段组成,它们执行时需要按照某种先后次序顺序执行,仅当前一程序段执行完后才运行后一程序段
程序顺序执行时的特征:
- 顺序性:处理器严格按照程序规定的顺序执行
- 封闭性:程序运行时独占传输资源,资源的状态只有本程序才能改变;程序一旦开始执行,执行结果不受外界因素影响
- 可再现性:只要程序执行时的环境和初始条件相同,当程序重复执行时都可获得相同的结果
1.1.3程序并发执行
只有在不存在前趋关系的程序之间才有可能并发执行
一组在逻辑上互相独立的程序或程序段在执行过程中,其执行时间在客观上互相重叠,即一个程序段的执行尚未结束,另一个程序段的执行已经开始的这种执行方式
程序并发执行时的特征:
- 间断性:并发执行的程序因为共享系统资源和为完成同一项任务相互合作形成相互制约,导致并发程序具有“执行——暂停——执行”的间断性活动规律。例如,当计算程序完成Ci-1的计算后,如果输入程序Ii尚未完成数据输入,则计算程序Ci就无法进行数据处理,必须暂停运行,只有当Ii完成数据输入,Ci才可恢复执行
- 失去封闭性:任一程序在运行时,其环境必然会受到其他程序的影响。例如,处理器被分派给某个进程运行时,其他程序必须等待
- 不可再现性:程序并发执行时由于失去封闭性,计算结果必将与并发程序的执行速度有关,从而使程序的执行失去了可再现性
1.2进程的描述
1.2.1进程的定义和特征
1、定义
在多道程序环境下,程序的执行属于并发执行,此时它们失去封闭性,具有间歇性,运行结果具有不可再现性。由此决定了通常程序不能参与并发执行,否则程序的运行就失去了意义
进程是计算机中的程序关于某数据集合上的一次运行活动,是系统进行资源分配和调度的基本单位,是操作系统结构的基础
由程序段、相关数据段、PCB三部分构成了进程实体
创建进程实质上是创建进程实体中的PCB;撤销进程实质上是撤销进程的PCB
2、特征
- 动态性:进程的实质是进程实体的执行过程。动态性表现在:由创建而产生,由调度而执行,由撤销而消亡
- 并发性:多个进程实体同存于内存中,且能在一段时间内同时运行
- 独立性:进程实体是一个能独立运行、独立获得资源和独立接受调度的基本单位
- 异步性:进程按各自独立的、不可预知的速度向前推进
1.2.2进程的基本状态及转换
1、三种基本状态
- 就绪状态:进程已分配到除CPU以外的所有必要资源,只要再获得CPU即可立即执行。若系统中存在多个处于就绪状态的进程,将它们按一定策略排成队列(就绪队列)
- 执行状态
- 阻塞状态:正在执行的进程由于发生某事件(如I/O请求、申请缓冲区失败等)暂时无法继续执行时的状态。系统将处于阻塞状态的进程排成一个队列(阻塞队列)
2、三种基本状态的转换
3、创建状态和终止状态
创建状态:进程已经拥有了字节的PCB,但该进程所必需的资源或其它信息(如主存资源)尚未分配,进程自身还未进入主存,即创建工作尚未完成,进程还不能够被调度运行
(创建进程的两个步骤:为一个新进程创建PCB,并填写必要管理信息;把该进程转入就绪状态并插入就绪队列)
终止状态:进程的终止首先要等待操作系统进行善后处理,然后将其PCB清零,并将PCB空间返还系统
(当一个进程到达自然结束点或出现了无法克服的错误,或是被操作系统或其它有终止权的进程所终结,它将进入终止状态。进入终止状态的进程不能再执行,但在操作系统中依然保留一个记录,其中保存状态码和一些计时统计数据,供其它进程收集。一旦其它进程完成了对终止状态进程的信息提取之后,操作系统将删除该进程)
1.2.3挂起操作和进程状态的转换
挂起操作:当该操作作用于某个进程时,该进程将被挂起,即进入静止状态。若进程正在执行,它将暂停执行;若原本处于就绪状态,则暂时不接受调度。与挂起操作对应的是激活操作
1、引入挂起操作后三个进程状态的转换
- 活动就绪->静止就绪:处于后者的进程不再被调度执行
- 活动阻塞->静止阻塞:处于后者的进程在期待的事件出现后进入静止就绪状态
- 静止就绪->活动就绪
- 静止阻塞->活动阻塞
2、引入挂起操作后五个进程状态的转换
- NULL->创建:新进程产生时处于该状态
- 创建->活动就绪:系统的性能和内存的容量允许,并完成对进程创建的必要操作后,进程状态转为活动就绪
- 创建->静止就绪:系统的性能和内存的容量不允许(主要是内存),不给进程分配资源,进程状态转为静止就绪,且被安置在外存。此时创建工作尚未完成
- 执行->终止
1.3进程控制
1.3.1进程的创建
1、进程的层次结构
OS中允许一个进程创建另一个进程
子进程能继承父进程拥有的资源。当子进程被撤销时,将其从父进程获得的资源归还;在撤销父进程时,必须同时撤销其所有的子进程
进程不能拒绝其子进程的继承权
2、进程图
3、引起创建进程的事件
用户登录,作业调度,提供服务,应用请求
前三者由系统内核为用户创建新进程,后者由用户自己创建新进程
4、创建过程
- 申请空白PCB,为新进程申请获得唯一的数字标识符,并从PCB集合索取一个空白PCB
- 为新进程分配所需的资源,包括各种物理和逻辑资源,如内存、文件、I/O设备好CPU请求时间
- 初始化进程控制块(PCB)
- 如果进程就绪队列能接纳新进程,将其插入就绪队列
1.3.2进程的终止
1、引起进程终止的事件
- 正常结束
- 异常结束
- 外界干预
2、终止过程
- 根据被终止进程的标识符,从PCB集合中检索该进程的PCB并读出其状态
- 若被终止进程处于执行状态,立即终止并置调度标志为真,用于指示该进程被终止后应重新进行调度
- 若进程有子进程,终止所有子进程以防它们成为不可控进程
- 被终止进程的所有资源归还给父进程或系统
- 将被终止进程(PCB)从所在队列(或链表)中移出,等待其他程序搜集信息
1.3.3进程的阻塞与唤醒
1、引起阻塞与唤醒的事件
- 向系统请求共享资源失败
- 等待某种操作完成
- 新数据尚未到达
- 等待新任务的到达
2、进程阻塞过程
无法继续执行,调用阻塞原语block把自己阻塞,是主动行为
- 立即停止执行
- PCB中的现行状态由“执行”改为“阻塞”并将PCB插入相应阻塞队列
- 转调度程序进行重新调度,将处理机分配给另一就绪进程并进行切换
(保留被阻塞进程的处理机状态(在PCB中),再按新进程的PCB中的处理机状态设置CPU的环境)
3、进程唤醒过程
先把被阻塞的进程从等待该事件的阻塞队列中移出,将其PCB中的现行状态由阻塞改为就绪,然后将该PCB插入就绪队列中
block原语和wakeuo原语必须成对使用
1.3.3进程的挂起与激活
1、进程的挂起
当出现了引起进程挂起的事件时(比如,用户进程请求将自己挂起,或父进程请求挂起某子进程),挂起原语suspend( )的执行过程是:
- 首先检查被挂起进程的状态并进行相应操作(执行态、活动就绪——静止就绪;活动阻塞——静止阻塞)
- 为便于用户或父进程考查该进程的运行情况,把该进程的PCB复制到某指定的内存区域
- 最后,若被挂起的进程正在执行,则转向调度程序重新调度,从而将处理机重新分配
2、进程的激活过程
当发生激活进程的事件时(如父进程或用户进程请求激活指定进程),激活原语active( )激活过程:
- 激活原语将进程从外存调入内存
- 检查该进程的现行状态并进行相应操作(静止就绪-活动就绪; 静止阻塞 - 活动阻塞)
假如采用的是抢占调度策略,则每当有新进程进入就绪队列时,检查是否要进行重新调度,即比较被激活进程与当前进程的优先级,决定处理机归属
1.4进程同步
1.4.1基本概念
1、两种形式的制约关系
- 间接相互制约:由于多个进程并发执行时,共享系统资源,致使这些进程在执行时形成相互制约的关系。为了使共享系统资源的进程有序执行,系统资源必须统一分配,即进程需要先申请后使用
- 直接相互制约:多个进程为了完成同一项任务而合作,在合作过程中形成相互制约的关系
2、主要任务
对多个相关进程在执行次序上进行协调,使并发执行的诸进程之间能按照一定的规则(或时序)共享系统资源,并能很好地相互合作,使进程的执行具有可再现性
3、临界区
每个进程中访问临界资源的代码称为临界区
多个进程对临界资源的访问必须是互斥的,要实现互斥访问,每个进程在进入临界区之前应先对欲访问的临界资源进行检查,看它是否正被访问
- 进入区:临界区前增加的用于进行访问检查的代码
- 退出区:将临界区正被访问的标志恢复为未被访问的代码
- 剩余区:除进入区、临界区、退出区外的其它代码
4、同步机制应遵守的规则
- 空则让进:临界资源如果处于空闲状态,那么该进程可以进入其临界区
- 忙则等待:临界资源如果处于被占用状态,那么该进程需要等待临界资源被释放
- 有限等待:进程在有限时间内可以进入自己的临界区,以避免陷入“死等”状态
- 让权等待:当进程不能进入自己的临界区时,需要让出处理机,以避免陷入“忙等”状态
1.4.2硬件同步机制
1、关中断
在进入锁检测之前关闭中断,知道完成锁检测并上锁之后才打开中断
进程在临界区执行期间,计算机系统不响应中断,从而不会引发调度,自然不会发生进程或者线程切换
缺点
- 关中断时间过长,会影响系统效率,限制处理器交叉执行程序的能力
- 不适合多CPU系统,不能防止进程在其它处理器上执行相同的代码
2、利用Test-and-Set 指令实现互斥
TS指令的一般描述如下:
boolean TS(boolean *lock){
boolean old;
old=*lock;
*lock=true;
return old;
}
//相应的进入区代码为:
while(TS(&lock));
//当lock为false时,将其设置为true,然后返回false;当lock为true时,就返回true
//返回false表示资源可用;返回true表示资源不可用
lock初值为FALSE,表示该临界资源空闲。进程在进入临界区之前,首先用TS指令测试lock,如果值为FALSE表示没有进程在临界区内,可以进入,并将TRUE赋予lock,等效关闭了临界资源,任何进程都不能进入临界区
3、利用Swap指令实现互斥
用于交换两个字的内容
void Swap(boolean* lock,boolean* key){
boolean temp=*lock;
*lock=*key;
*key=temp;
}
//相应的进入区代码为:
key=true;
do{
Swap(&lock,&key);
}while(key!=false);
//进入临界区
为每个临界资源设置一个全局的布尔变量lock,初值为false,在每个进程中再利用一个局部布尔变量key,利用Swap指令实现进程互斥
1.4.3信号量机制
1、整型信号量
把整型信号量定义为用于表示资源数目的的整型量S,除初始化外仅能通过两个标准的原子操作 wait(S)和 signal(S)来访问
wait(S){
while(S<=0);
S--;
}
signal(S){
S++;
}
操作在执行时是不可中断的,即当一个进程在修改某信号量时其它进程不能同时修改
整型信号量机制未遵循“让权等待”准则,而是使进程处于“忙等”状态
2、记录型信号量
不存在“忙等”现象,但采取“让权等待”策略后会出现多个进程等待访问同一临界资源的情况
设置了两个变量:代表资源数目的整型变量 value,链接所有等待进程的进程链表指针 list
typedef struct{
int value;
sturct 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指示资源的数量,每次 wait操作会使 value递减,所以 value的值会反映出等待资源的进程有多少个
在signal中,如果value经过自增后还<=0,说明仍有进程在等待该资源,需要wakeup一个进程
3、AND型信号量
前面所述的进程互斥问题针对的是多个并发进程共享一个临界资源的情况
有的场合是一个进程需要获得两个或更多的共享资源后才能执行任
AND同步机制思想:将进程在整个运行过程中需要的所有资源,要么一次性全部分配给进程,然后使用完后一起释放;要么一个都不分配,即使存在能为之分配的其他资源
Swait(S1,S2,S3,S4,S5....){
while(true){
if(S1>=1&&S2>=1...){
for(i=1;i<=n;i++){
Si--;
}
break;
}else{
//找到第一个小于等于0的Si,然后将进程放置到与其相关的等待队列中
}
}
}
Ssignal(S1,S2...Sn){
while(true){
for(i=0;i<=n;i++){
Si++;
//唤醒一个等待Si资源的进程——该进程将进入Swait中的while循环里继续判断其他资源是否可用。
}
}
}
4、信号量集
前面介绍的几种信号量同步机制都是对某一资源进行一个单位的申请和释放
- 当一次需要N个的时候,就需要进行N次请求,不但低效而且容易发生死锁
- 还有些情况下,为了保证系统的安全性,当所申请的资源低于某个值时,就需要停止对该类资源的分配
解决办法就是当进程申请某类临界资源时,都必须测试资源的数量,判断是否大于可分配的下限值,然后决定是否分配
基于上述两点,对AND信号量机制加以扩充:对进程所申请的所有资源以及每类资源不同的资源需求量,在一次P、V原语操作中完成申请或释放
1.4.4管程机制
1、定义
- 利用共享数据结构抽象地表示系统中的共享资源
- 将对该共享数据结构实施的特定操作定义为一组过程。进程对共享资源的申请、释放和其他操作都必须通过这组进程间接地对共享数据结构实现操作
管程是代表共享资源的数据结构,和对该共享数据结构实施操作的一组过程 组成的资源管理程序构成的一个操作系统的资源管理模块
管程由四部分组成
- 名称
- 局部于管程的共享数据结构说明
- 对该数据结构进行操作的一组过程
- 对局部于管程的共享数据结构设置初始值的语句
管程体现了面向对象程序设计的思想
- 封装与管程内部的数据结构仅能被封装于管程内部的过程访问,任何管程外的过程都不能访问
- 封装与管程内部的过程仅能发完管程内的数据结构
所有进程要访问临界资源时,都只能通过管程间接访问,二管程每次只允许一个进程进入管程并执行管程内的过程,从而实现了进程互斥
2、条件变量
当某进程通过管程请求获得临界资源而未能满足时,管程便调用 wait原语使该进程等待,并将其排在等待队列上。仅当另一进程访问完成并释放该资源后,管程才又调用 signal原语唤醒等待队列中的队首进程
引入条件变量 condition是为了防止进程在管程中被阻塞或挂起而无法释放时,其它进程无法进入被迫长时间等待
条件变量形式为:condition x,y;具有两种操作:x.wait()和x.signal()
- x.wait:正在调用管程的进程因 x条件而需要被挂起或者阻塞,则调用x.wait将自己插入到条件变量 x的等待队列上并释放管程,直到 x条件变化。此时其它进程可以使用该管程
- x.signal:正在调用管程的进程发现 x条件发生了变化,调用x.signal,重新启动一个因 x而阻塞或挂起的进程,如果有多个进程因 x而阻塞,选择其中一个
1.5进程通信
1、共享存储器系统
相互通信的进程共享某些数据结构或存储区,进程之间能通过这些空间进行通信
基于共享数据结构的通信方式:仅适用于传递相对少量的数据,通信效率底下;低级通信
基于共享存储区的通信方式:在内存中划出一块共享存储区域,数据的形式和位置甚至访问控制都由进程负责;高级通信
2、管道通信系统
“管道”是用于连接一个读进程和一个写进程以实现他们之间通信的一个共享文件
管道机制必须提供的协调能力:互斥,同步,确定对方是否存在
3、消息传递系统
进程以格式化的信息为单位,将通信的数据封装在消息中,并利用操作系统提供的一组通信命令在进程中进行消息传递,完成进程间的数据交换
直接通信方式:发送进程利用OS提供的发送原语直接把消息发送给目标进程
间接通信方式:发送和接收进程都通过共享中间实体的方式进行消息的发送和接收,以此完成进程间的通信
二、线程的基本概念
2.1线程的引入
引入进程的目的是为了使多个程序能并发执行,以提高资源利用率好系统吞吐量
引入线程的目的是为了减少程序在并发执行时所付出的时空开销,使OS具有更好的并发性
2.2进程与线程的比较
1、调度的基本单位
在同一进程中,线程的切换不会引起进程的切换。但从一个进程的线程切换到另一个进程的线程时,必然引起进程的切换
2、并发性
再引入线程的OS中,不仅进程之间可以并发执行,一个进程中的多个线程之间也可以并发执行,甚至允许一个进程中的所有线程都能并发执行
不同进程中的线程也能并发执行
3、拥有资源
进程可以拥有资源,并作为系统中拥有资源的一个基本单位
线程本身不拥有系统资源,仅有一点必不可少的、能保证独立运行的资源
线程允许多个其他线程共享所属进程拥有的资源
4、独立性
同一进程的不同线程之间的独立性比不同进程之间的独立性低得多
每个进程不允许其他进程的访问
由一个线程打开的文件可以供其他线程(同一进程)读、写
2.3线程的状态和线程控制
各线程之间存在着共享资源和相互合作的制约关系,致使线程运行时也具有间断性
线程运行时的三种基本状态:
- 执行状态
- 就绪状态
- 阻塞状态
进程有进程控制块PCB,线程有线程控制块TCB
多线程OS中进程的属性:
- 进程是一个可拥有资源的基本单位
- 多个进程可以并发执行
- 进程已不是可执行的实体
2.4线程的实现
2.4.1内核支持线程KST
OS中的所有进程都是在操作系统内核的支持下运行的
优点:
- 多处理器系统中,内核能同时调度同一进程的多个线程并行执行
- 如果进程中一个线程被阻塞,内核可以调度该进程的其他线程占有处理器运行,也可以运行其他进程的线程
- KST具有很小的数据结构和堆栈,线程切换较快,开销较小
- 内核本身可采用多线程技术,提高系统的执行速度和效率
缺点:用户的线程 模式切换开销较大
2.4.2用户级线程ULT
设置ULT的系统,其调度仍以进程为单位进行。采用轮转调度算法时各进程轮流执行一个时间片,对于拥有不同数量ULT的进程是不公平的,不同线程的运行时间和速度相差很大
而设置KST的系统,其调度以线程为单位,采用轮转调度算法时各线程轮流执行一个时间片,不同进程的运行时间与线程数成正比,且多个线程可并发执行
优点:
- 线程切换不需要转换到内核空间,节省了模式切换的开销
- 调度算法可以是进程专用的
- ULT的实现与OS无关,线程管理的代码是用户程序的一部分,所有的应用程序可以共享
缺点:
- 系统调用的阻塞问题。当线程执行一个系统调用时,进程内的所有线程会被阻塞
- 单纯的ULT实现中,多线程应用不能利用多处理器进行多重处理的优点,内核每次仅给进程分配一个CPU,进程中只有一个线程能执行