一、进程
1、进程
1)概念
可以理解为程序的一次实验
程序是静态的,当执行起来时,就被称为进程
2)组成
- 程序段
- 数据段
- PCB(进程控制块)
- 进程描述信息:进程的功能等
- 进程控制和管理信息:进程什么时候开始、结束
- 资源分配清单:这个进程需要使用什么硬件设备等,给予分配
- 处理机相关信息:每个处理机分配几秒的时延等
3)特征
- 动态性:执行起来的,就是动态的
- 并发性:多个进程实体同时存在于内存中
- 独立性:独立获得资源和独立接受调度的基本单位(每个进程都可以得到独立的资源和调度分配)
- 异步性:进程之间相互制约,使得进程具有执行的间断性(比如QQ和微信的视频聊天,共享摄像头等,但是又不能同时进行)
- 结构性:从组成来看,有程序段、数据段、PCB
4)状态
- 运行态
- 就绪态
- 阻塞态(等待态)
还有创建态,终止态,挂起态
2、进程控制
主要关注于核心三态的唤醒
- 进程创建
- . 进程终止
- . 进程的阻塞和唤醒(阻塞为主动行为)
- 进程切换
3、进程的通信
共分为三种方式:
- 共享存储:采用同步互斥工具PV
- 基于数据结构的共享
- 基于存储区的共享
PV原语:
- 消息传递:借助消息缓冲区
- 直接通信方式
- 间接通信方式
- 管道传递:半双工
- 管道是固定大小的缓冲区
- 可以一端写,一端读,但是不可以两端同时写或读,会引起互斥共享的问题
4、进程运行步骤
1)编译程序
由编译程序将用户源代码编译成若干目标模块
2)链接程序
由链接程序将编译后形成的一组目标模块及所需的库函数链接在一起
链接方式:
- 静态链接
- 装入时动态链接
- 运行时动态链接
3)装入程序
由装入程序将装入模块装入内存中运行
4)举例
用户在编译器中使用编译程序编写了程序,分为主函数、子函数1、子函数2三个模块,然后使用链接程序将三个模块链接在一起成为装入模块,由装入程序将装入模块装入内存中运行。
二、线程
线程指一种轻量级进程,进程比线程的资源多
1、特点
-
不拥有系统资源,共享线程资源
-
状态:
- 就绪
- 阻塞
- 运行
-
是处理机的独立调度单位
- 相比较:进程是处理机独立分配资源的单位
2、实现方式
- 用户级线程:用户的应用层面创建的一个线程
- 内核级线程:是对于用户不可见的,透明的
3、引入目的
- 增加多道程序并发度
- 比如:与多个QQ好友同时聊天时,共用同一个进程共享资源,但是对于不同好友分配不同线程
- 提高资源利用率和系统吞吐量
- 系统吞吐量指单位时间内完成的任务数,所以同时多人聊天可以提高系统吞吐量
- 增加程序的并发性
三、调度
1、调度的层次
为了使CPU处理多个不同时间的进程时效率高、处理时间短、响应时间短、等待时间短
- 高级调度(作业调度)–使用频率低:从外存中调入作业,分配给他们内存、必要资源,来创建进程(进程需要在内存中创建)
- 用户提交给计算机的一连串的任务称为作业调度
- 中极调度(内存调度)–使用频率中等:使进程进行状态转换,阻塞态->挂起态 or 挂起态->阻塞态
- 主要解决内存大小的问题,将不需要的进程挂起或者阻塞,或者有些进程需要执行,将挂起态转化为就绪态
- 低级调度(进程调度)–使用频率高:用来分配处理机
- 当有多个不同时间的进程需要执行时,如何分配处理机使用顺序,使得CPU处理效率高、时间短
2、调度方式
根据是否在优先级高的进程进来后将优先级低的进程停止先处理高优先级进程划分
- 非剥夺式调度
- 剥夺式调度:会造成一种饥饿状态。一直有优先级高的进程进来,优先级低的进程一直没有机会处理
3、 评价指标
- CPU利用率
- 系统吞吐率:单位时间内CPU完成作业的数量
- 周转时间:从作业提交到完成的时间
- 响应时间
- 响应比=(等待时间+要求服务时间)/要求服务时间,响应比越高,表示等待时间越长,就会优先处理,缓解饥饿状态的产生
4、调度算法
- 先来先服务算法FCFS(Fisret Call First Server)
- 效率低
- 不利于短作业
- 适合CPU繁忙型(一直让CPU计算),不适合I/O繁忙型作业
- 短作业优先算法SIF
- 长作业的饥饿发生
- 未考虑作业的紧迫程度
- 平均等待时间短
- 优先级调度算法
- 分类:剥夺式优先级调度算法, 非剥夺式优先级调度算法
- 优先级原则:
- 系统进程>用户进程
- 交互型进程>非交互型进程
- IO型进程>计算型进程(IO进程比较慢)
- 高响应比优先调度算法
- 计算公式:响应比=(等待时间+要求服务时间)/要求服务时间
- 克服饥饿状态发生
- 时间片轮转算法(分时系统)
比如:先给每个进程分配5秒处理时间,然后轮流处理,如果5秒还没处理完,再分配5秒,如果3秒就处理完,剩下的时间顺移给下一个要处理的进程- 时间片的长短确定:系统响应时间、就绪队列进程数目、系统的处理能力
- 多级反馈队列调度算法:根据前几种算法总结出的目前最好的算法
- 比如现在有三个队列的进程,每个队列按优先级进行排序,先执行第一队列,如果没执行完,将未完成的进程放入第二队列,然后按照优先级排序之后的顺序执行第二队列的进程……长作业短作业都考虑到了
四、进程同步
1、临界
- 临界资源:多个进程不能同时使用的资源,比如打印机、鼠标等
- 临界区:不能被多个线程同时访问的共用资源的一个程序片段,比如共用设备或共用存储器
2、进程同步
提出原因:
进程在使用临界资源或进入临界区时需要的访问方法
原则:
直接制约关系,通过信号量实现同步
举例:
比如在两个进程(P1,P2)中设置信号量(P、V),一个执行完再执行另一个
P1
{
一些操作代码;
x://执行语句;
v(s)//唤醒另一个P进程
}
P2
{
P(s)://一直阻塞P2进程,等待唤醒
V://被唤醒,执行正事
}
这里,P表示wait等待,V表示signal唤醒
3、进程互斥
1)原则:
间接制约关系
2)实现方法
i、软件
1、单标志法:违背空间让进原则
假设两个进程P0、P1,标志量turn,
原理:
当turn=0,P0进入临界区执行,turn=1,P1进入执行;
turn的初始化
turn=0;
P0进程
while(turn != 0);//进入区
临界区代码段 //临界区
turn = 1; //退出区
剩余区代码段 //剩余区
P1进程
while(turn != 1);//进入区
临界区代码段 //临界区
turn = 0; //退出区
剩余区代码段 //剩余区
缺点:
- 只能由两个进程共用一个标质量,无法完成多个进程互斥
- 两个进程必须交替进入临界区,若P1进入临界区并退出将turn=0,之后P0一直未进入临界区,那么由于turn1!=1,所以P1再次向进入临界区的时候就会被一直阻塞。
2、双标志法先检查:违背忙则等待原则
设两个进程P0和P1,以及两个标志量(flag0、flag1)
原理:
先检查对方是否正在临界区,若对方没有正在访问临界区,则自己进入临界区
flag(0)、flag(1)的初始化
flag0 = false; flag1 = false;
规定flag0 = true时,表示P0正在访问临界区,flag0 = false时,P0没有在访问临界区;
规定flag1 = true时,表示P1正在访问临界区,flag1 = false时,P1没有在访问临界区;
P0进程
while(flag1); //进入区 1
flag0 = true; // 2
临界区代码段 //临界区
flag0 = false; //退出区
剩余区代码段 //剩余区
P1进程
while(flag0); //进入区 3
flag1 = true; // 4
临界区代码段 //临界区
flag1 = false; //退出区
剩余区代码段 //剩余区
缺点:
- 如果按照1324的顺序执行,P0和P1会一起进入临界区,都无法使用临界区,违背来了忙则等待原则
3、双标志法后检查:违背空闲让进、有限等待,出现饥饿现象
设两个进程P0和P1,以及两个标志量(flag0、flag1)
原理
进程先设置自己的状态为正在使用临界区,然后检查对方的状态,如果对方也是True,则等待对方状态转换为False再进入
flag0、flag1的初始化
flag0 = false; flag1 = false;
规定flag0 = true时,表示P0正在访问临界区,flag0 = false时,P0没有在访问临界区;
规定flag1 = true时,表示P1正在访问临界区,flag1 = false时,P1没有在访问临界区;
P0进程
lag0 = true; //进入区 1
while(flag1); // 2
临界区代码段 //临界区
flag0 = false; //退出区
剩余区代码段 //剩余区
P1进程
flag1 = true; //进入区 3
while(flag0); // 4
临界区代码段 //临界区
flag0 = false; //退出区
剩余区代码段 //剩余区
缺点
- 按照1324的顺序执行,P1、P2进程都无法进入临界区,从而造成饥饿现象,从而违反了空闲让进和有限等待原则
4、Peterson’s(皮特森) 算法
- flag 解决临界资源的互斥访问
- turn 解决饥饿现象
设一个flag数组和一个标志量(turn)
原理:
采用一种互相谦让的方式,谦让方表示自己想进入临界区(假设进程i为谦让方,则将flag[i] = true),然后将临界区的进入权让给别人(假设进程j为被谦让方,则将turn = j)。
flag数组、turn的初始化
memset(flag, 0, sizeof(flag));
turn = -1;
规定若flag[i] = true,表示进程i想要访问临界区;
turn = i时,表示进程i有权进入临界区
P0进程
flag[i] = true; turn = j; //进入区 1
while(flag[j] && turn == j); // 2
临界区代码段 //临界区
flag[i] = false; //退出区
剩余区代码段 //剩余区
P1进程
flag[j] = true; turn = i; //进入区 3
while(flag[i] && turn == i); // 4
临界区代码段 //临界区
flag[j] = false; //退出区
剩余区代码段 //剩余区
唯一缺点: 仍然按照1324的顺序执行,首先(第一步) P0想进入(flagi=true),但是把使用权交给P1(turn=j),然后(第三步)P1这边也想进入(flagj=true)但是也将使用权交给P0(turn=i),此时(第二步) flagj=ture,turn=i,不满足当循环条件,跳出循环,P0进入临界区,开始执行,执行结束后,flagi=false,此时(第四步) 不满足P1的当循环,则跳出循环,P1进入临界区,开始执行。谁先谦让谁先进
ii、硬件
1、中断屏蔽法
方法原理:
当一个进程正在使用处理机执行它的临界区代码时,并且CPU只在发生中断时引起进程切换,因此防止其他进程进入其临界区访问最简单的方法是:禁止一切中断发生,称之为屏蔽中断或关中断。
典型模式:
关中断、临界区、开中断
缺点:
- 限制了处理机交替执行程序的能力,降低了系统并发度和系统吞吐量
- 用户权力过大,如果一个进程关中断,不再开中断,系统可能会终止
2、硬件指令方法
计算机提供特殊的硬件指令,允许对一个字中的内容进行监测和修正,或者对两个字进行交换等。通过硬件实现临界段问题的方法为低级方法或称为元方法
1)TestAndSet指令:
为每一个临界资源设置一个共享布尔变量lock**,表示该资源的两种状态:true表示被占用,false表示空闲,其初值为flase。
功能函数:
boolean TestAndSet(boolean *lock) {
boolean old;
*old = *lock;
*lock = true;
return old; //设置old为true
}
执行函数:
利用TestAndSet检查和修改标志lock,若有进程在临界区,则重复检查,直到进程时间片消耗完。其指令实现进程互斥的算法描述如下:
while(TestAndSet(&loack));
临界区代码段 //进入第一件事,设置lock为true,防止其他进程进入
lock = false;
其他代码段
2)Swap指令
为每一个临界资源设置一个共享布尔变量lock,初始值为false;在每一个进程进程中设置一个局部变量key,用于与lock交换信息。
功能函数:
void Swap(boolean *a, boolean *b){
boolean temp;
temp = *a;
*a = *b;
*b = temp;
}
执行函数:
ock初始值为false,key为true,当一个进程进入临界区时,利用Swap指令,key与lock交换信息lock为转换为true,key为false,直到进程使用完临界区,lock转换为false,key为true,跳出循环
key = true;
while(key != false) {
Swap(&loack, &key);
}
临界区代码段
lock = false;
其他代码段
注意:以上TestAndSet和Swap指令功能描述仅仅是代码逻辑,而非软件实现的,事实上,他们是由硬件逻辑直接实现,不会被中断。
3)硬件方法优缺点
优点:
适用于任意数目的进程,而不管是单处理机还是多处理机;简单、容易验证其正确性。可以支持进程内有多个临界区,只需为每一个临界区设立一个布尔变量。
缺点:
- 进程等待进入临界区要耗费处理机时间,不能实现让权等待。
- 从等待进程中随机选择一个进入临界区,有的进程可能一直选不上,从而导致饥饿。
iii、信号量实现互斥
1、整型信号量
操作:
P操作:wait(占有资源 consume)
wait(S)
{
while(S<=0);
S--;
}
V操作:signal(释放资源 release)
signal(S)
{
S++;
}
缺点: 存在忙等
2、记录型信号量
资源数目value,进程链表L(链接所有等待该资源的进程)
ypedef struct
{
int value;
struct process *L;//构造等待序列
}semaphore;
void wait(semaphore S)
{
S.value --;
if(S.value < 0)
{
add this process to S.L;
block(S.l);//放弃处理机,插入等待序列
}
}
void signal(semaphore S)
{
S.value ++;
if(S.value > 0)
{
remove a process P from S.l;
//告诉阻塞进程P,来资源了,可以干活了
wake up(P);
}
}
3)进程互斥–信号量机制实现
- 互斥信号量mutex的初值1:mutex=1
- P(mutex)操作:请求一个单位的该类资源(加锁),在临界区之前执行
V(mutex)操作:释放一个单位的该类资源(解锁),在临界区之后执行 - PV操作实现互斥:PV操作成对出现,把操作夹在中间
- 如果没有P操作:无法实现互斥
- 如果没有V操作:进程一直占用临界区得不到释放,等待进程永不被唤醒
注意:对不同的临界资源需要设置不同的互斥信号量
semaphore mutex=1; //初始化信号量
P1()
{
P(mutex); //mutex=0 使用临界资源前需要加锁
临界区代码段
V(mutex); //mutex=1 使用临界资源后需要解锁
……
}
P2()
{
P(mutex);
临界区代码段
V(mutex);
}
4、经典同步问题
统一把P操作理解为消耗资源,V操作理解为释放资源
1)生产者-消费者问题
2)写者-读者问题
3)哲学家讲解问题
4)吸烟者问题
五、管程
管程(资源管理模块)=共享资源的数据结构+资源管理程序(对共享数据结构实时操作的**
管程将共享资源和每个进程使用共享资源的过程抽象为一个模块,每个使用该共享资源的进程都会经过该过程,并且对于请求访问共享资源的诸多并发进程,根据资源的情况结束或阻塞,确保每次仅有一个进程进入管程,使用共享资源,达到对共享资源的所有访问的统一管理, 有效实现进程互斥。
1、组成
- 1)管程名称:不同共享资源及使用过程有不同的抽象
- 2)局部于管程内部的共享结构数据说明:管程内部共享资源的说明
- 3)对该数据结构进行操作的一组过程:对该共享资源抽象出的一组操作过程
- 4)共享数据设置初始值
2、条件变量(进程阻塞的原因)
管程结合同步工具(wait原语、signal原语)实现进程同步
- wait:通过管程请求进入临界区而未能满足时,管程调用wait原语,将进程排在等待队列上,使其等待
- signal:当临界区资源被释放,管程调用signal原语,唤醒等待队列中的等待进程进入临界区
1)引入条件变量的原因
当一个进程调用管程时,由于某些原因被阻塞挂起,直到阻塞或挂起的原因解除,在此期间,如果该进程不释放管程,新的进程无法进入管程,被迫长时间等待,因此在管程中引入条件变量,根据不同原因设置不同的条件变量。
2)条件变量具体操作
形式:
管程中对每个条件变量都予以说明,形式为:condition x,y;
操作:
wait原语、singal原语
每个变量有一个链表:
记录因该条件而阻塞的所有进程;
提供的两个操作可表示为: x.wait,x.signal
- x.wait: 正在调用管程的进程因原因x需要被阻塞或挂起,调用x.wait将自己插入到x条件的等待队列上,并释放管程,直到x条件无变化。此时其他进程可进入管程
- x.signal: 正在调用管程的进程发现x条件发生了变化,则调用x.signal,重新启动一个因x条件而被阻塞或挂起的进程,如果存在多个在这样的进程,则选择其中一个,如果没有,则继续执行原进程,而不产生任何结果。
六、死锁
1、定义
多个进程因竞争资源而造成的一种僵局
2、产生条件
- 互斥条件:进程对所分配到的资源进行排他性使用
- 不剥夺条件/不可抢占条件:该资源只能在进程使用完后自己释放,在进程未使用完不能被抢占
- 请求并保持条件:进程已经占有至少一个资源,又提出新的资源请求,而该资源正在被别的进程占用。
- 循环等待条件:必然存在一个进程-资源的循环链,即进程集合中{P0,P1,P2…Pn}中的P0正在等待一个P1占用的资源,P1正在等待一个P2占用的资源,P2等待一个P3占用的资源……Pn正在等待已被P0占用的资源。
3、处理策略
1)死锁预防
i、破环“请求和保持”条件
- 第一协议:在进程开始运行之前就将所需的所有资源都分配给进程,,在进程运行期间就不会请求新的资源;如果进程开始之前有资源正在被占用,不能满足所有资源都分配给该进程,即使其他所需资源都空闲,也不分配给该进程,而让其等待。
资源严重浪费、已造成饥饿现象 - 第二协议:允许进程只获得运行初期所需的资源后就开始,运行过程中再逐步释放分配给自己的、自己用完的资源,然后再请求新的资源。
ii、破环“不可抢占”条件
使用第二协议:当进程释放运行初期资源后,就可以被其他进程抢占,但是如果该进程需要再次使用,就得再次申请
增加系统开销,降低系统吞吐量
iii、破环“循环等待”条件
对系统中所有资源类型进行先行排序,并赋予不同的序号,序号的赋予根据不同规则进行赋予;
规定每个进程必须根据序号递增的顺序请求资源,如果在运行过程中,进程又想申请一个序号低的资源,必须先释放自已占有的相同序号或更高序号的资源。
限制了新设备的增加,存在资源浪费,对用户编程造成不便
2)避免死锁
确保系统始终处于“安全状态”
安全状态: 不会发生死锁
不安全状态: 可能会发生死锁
i、 寻找安全序列
在系统进行资源分配之前,先计算此次资源分配的安全性,如果安全,再进行,反之,则不进行。
安全状态: 系统能够按照某种进程推进顺序{P0,P1,P2…}为每个进程分配资源,直至满足每个进程对资源的最大需求。
安全序列: {P0,P1,P2…}即为安全序列
ii、 银行家算法
3)死锁的检测
用于检测系统状态,确定系统中是否发生了死锁
i、 资源分配图
保存有关资源的请求和分配信息(书p115)
ii、死锁定理
S状态是死锁状态的充分条件是:当且仅当S状态的资源分配图是不可简化的—死锁定理
在资源分配图中
- 首先找出一个既不阻塞又不独立的进程结点Pi,如果它能获得所有资源并运行直至结束,然后释放所有结点Pi的资源,即消去图中Pi的请求边和分配边,使之成为孤立的结点
- 再在剩下的既不阻塞又不独立的进程结点Pi中找出一个结点,完成相同的步骤
- 直至资源分配图中所有的边都被消去, 使所有结点都成为孤立的结点,则认为 该图是可完全简化的,即当前系统为安全状态
- 若不能通过任何过程使该图完全简化,则该图是不可简化的,则当前系统为不安全状态
4)死锁解除方法
i、资源剥夺方法
从一个或多个进程中抢占足够数量的资源,分配给死锁进程,以解除死锁状态
ii、终止或撤销进程
终止或撤销系统中一个或多个死锁进程,直至打破循环环路,解除系统的死锁状态
- 终止所有死锁进程
- 逐个终止进程
- 进程回退法:退回到未发生死锁前,重新分配资源