1.基本概念
程序:就是一个指令序列。
随着计算机的发展,计算机从单道程序发展到多道程序。
系统为每个进程配置一个数据块,称为程序控制块(PCB),用来描述进程的各种信息。
PCB,程序段,数据段三部分构成了进程实体。
创建进程,实质上就是创建进程实体中的pcb,撤销进程,实际上就是撤销进程实体中的PCB,PCB是进程存在的唯一标志。
- 程序段:存放代码本身。
- 数据段:程序运行中使用,产生的运算数据。
- PCB:操作系统通过PCB来管理进程,因此PCB中包含操作系统对其进行管理所需要的各种信息。
PCB中包含的信息:
- 进程描述信息
- 进程标识符PID
- 用户标识符UID
- 进程控制和管理信息
- 资源分配清单
- 处理机相关信息:各种寄存器值
进程的组织:多个进程之间的组织方式
- 链接状态:执行指针,就绪队列指针,阻塞队列指针。
- 索引方式:指针指向索引表
2.进程的状态与转换
进程是能够独立运行,独立获取资源,独立接受调度的基本单位。
- 运行态到阻塞态是一种进程自身做出的主动行为。
- 阻塞态到运行态不是进程能控制的,是一种被动行为。
- 阻塞态不能直接转换为运行态,也不能直接从就绪态转换为阻塞态。
3.进程控制
进程控制的主要功能是对系统中所有进程实施有效管理,它具有创建进程,撤销已有进程,实现进程状态转换等功能。实现各种进程状态之间的转换。
原语:执行期间不允许被中断。采用关中断和开中断指令实现,只允许在核心态下执行的特权指令。
创建原语,撤销原语,阻塞原语,唤醒原语,切换原语。
进程因什么而阻塞,就因什么而唤醒
4.进程通信
各个进程拥有的内存地址空间是相互独立的,一个进程不能直接访问另一个进程的地址空间。
共享存储:两个进程对共享空间的访问是互斥的。
- 基于数据结构的共享:低级通信方式
- 基于存储区的共享:高级通信方式
消息传递:格式化消息为单位传递。用发送信息/接收信息两个原语进行数据交换。
管道通信:固定大小的缓冲区,半双工通信。各个进程要互斥的访问管道。管道写满时,不能再写,管道读空时,不能再读。
5.线程概念
引入线程之后,线程成了程序执行流的最小单位。进程是资源分配的基本单位,线程是调度的基本单位。每个线程都有一个线程ID,和线程控制块TCB。
同一个进程中的不同线程共享进程的资源。线程几乎不拥有系统资源,由于共享内存地址空间,同一进程的线程通信无需系统的干预。同一进程中的线程切换,不会引起进程切换,不同进程间的线程切换,会引起进程切换,切换同进程中的线程,系统开销很小。
线程的实现方式:用户级线程与内核级线程。
用户级线程:由应用程序通过线程库实现。线程管理工作由应用程序负责,线程切换可以在用户态下即可完成。对内核来说是透明的。
内核级线程:线程管理由内核负责。
内核级线程才是处理机分配的单位。
多线程模型:在同时支持用户级线程和内核级线程的系统中,由几个用户级线程映射到几个内核级线程的问题引出了“多线程模型”的问题。
多对一模型,一对一模型,多对多模型。多对多模型克服了一对一模型中一个用户进程占用太多内核级线程开销太大的缺点,同时有较高的并发性。
6.处理机调度的概念、层次
调度:由于资源有限,按照某种规则处理决定任务的顺序。
- 高级调度:作业调度。外存与内存之间的调度,面向作业。每个作业只调入一次,调出一次,调入的时候创建相应的PCB。
- 中级调度:内存调度,外存内存之间的调度,面向进程。引入虚拟内存之后,可以将暂时不能运行的进程调至外存等待,提高了内存的利用率和系统的吞吐量。暂时掉到外存等待的进程状态称为挂起状态。PCB不会被调到外存,而是会常驻内存。被挂起的进程的PCB会放在挂起队列中。
- 低级调度:进程调度,内存到CPU间的调度,按照某种算法,从就绪队列中选取进程。
7.进程调度
什么时候需要进程调度?
- 当前进程主动放弃处理机:进程正常终止,发生异常,请求阻塞
- 被动放弃:时间片用完,更紧急的事情处理,优先级更高的进程进入队列
不能进行进程调度的情况:
- 处理中断过程中
- 进程在操作系统内核程序临界区
- 原子操作过程中
进程调度方式:
- 非剥夺调度方式,只允许进程主动放弃处理机。
- 剥夺式调度方式,允许被动。
8.调度算法的评价指标
- CPU利用率
- 系统吞吐量:单位时间内完成作业的数量
- 周转时间
- 带权周转时间
- 等待时间
9.调度算法
先来先服务(FCFS,first come first serve)
周转时间=完成时间-到达时间
- 规则:先到达作业/进程先进行服务。
- 是否可以抢占?:非抢占式的算法
- 优点:公平,算法实现简单
- 缺点:排在长作业后面的短作业等待很长时间,带权周转世界很大。FCFS算法对长作业有利,对短作业不利。
- 是否会导致饥饿?不会导致饥饿。
短作业优先(SJF,short job first)
- 思想:追求更少的平均等待时间,最少的平均周转时间
- 最短的进程/作业优先得到服务,当前已经到达的且运行时间最短的作业/进程。
- 用于作业调度,用于进程调度时被称为“短进程优先”(SPF)
- 是否可以抢占?默认是非抢占的算法,但也有抢占式的版本----最短剩余时间优先算法(SRTN,shortest remaining time next),每当有新进程进入就绪队列就需要调度,如果新到达进程的剩余时间比当前运行进程时间更短,则新进程抢占处理机。
- 优点:较短的平均等待时间,平均周转时间
- 缺点:不公平,对短作业有利,对长作业不利。
- 有饥饿现象,一直得不到服务,进程会饿死
高响应比优先(HRRN)
- 综合考虑作业等待时间和要求服务时间
- 在每次调度时,计算响应比,选择响应比最高的作业为其服务。响应比=(等待时间+要求服务时间)/要求服务时间。
- 非抢占式的算法
- 优点:综合考虑。等待时间相同,要求服务时间短的优先。要求服务时间相同,等待时间长的优先,避免了饥饿问题。
交互式系统的调度算法:
时间片轮转(RR,round-robin):
- 公平,轮流的为各个进程服务
- 规则:按照进程到达各个就绪队列的顺序,轮流让各个进程执行一个时间片。若进程未在一个时间片内执行完,则剥夺处理机,将进程重新放入就绪队列尾重新排队。
- 用于进程调度,只有作业放入内存建立了相应进程之后,才能被分配处理机时间片。
- 抢占式的算法,由时钟装置发出时钟中断来通知CPU时间片已到
- 如果时间片过长,RR算法退化为FCFS算法
- 如果时间片太短,进程切换过于频繁。
- 优点:公平,响应很快,适用于分时操作系统
- 缺点:算法不区分任务的紧急程度,进程切换有一定的开销
- 不会导致饥饿
优先级调度算法:
- 根据任务的紧急程度处理顺序
- 为每个作业或者进程设置各自的优先级,调度时选择优先级最高的作业/进程。
- 既可以用于作业调度,也可以用于进程调度,也可以用于I/O调度
- 有抢占式和非抢占式
- 怎么设置优先级:通常:系统进程优先级高于用户进程,前台进程优先级高于后台进程,操作系统更加偏好IO繁忙型进程
- 优点:用优先级区分紧急程度,重要程度
- 缺点:会发生饥饿
多级反馈队列调度算法:
综合以上算法的折衷权衡
用于进程调度。是一种可抢占式的算法。
优点:很多,综合了以上几种算法
缺点:有可能会导致饥饿
10.进程同步与进程互斥:
异步性:各并发执行的进程以各自独立的,不可预知的速度向前推进。
一个时间段内只允许一个进程使用的资源被称为临界资源。互斥:当一个进程访问临界资源,另一个进程必须等待。
对临界资源的互斥访问,可以在逻辑上分为如下四个部分:
进入区,临界区,退出区,剩余区
对临界区的互斥访问需要遵循的原则:
- 空闲让进,临界区空闲,则允许进入
- 忙则等待
- 有限等待,保证不会饥饿
- 让权等待,当进程不能进入临界区,则应立即释放处理机,防止进程忙等待。
11.进程互斥的软硬件实现方法:
11.1进程互斥的软件实现方法:
单标志法:每个进程进入临界区的权限只能被另一个进程赋予。
int turn =0;
P0进程:
while(turn != 0);//进入区,不等于零一直执行此步,等于零执行下一步临界区
critical section ;//临界区
turn=1; //退出区
remanider section;//剩余区
P1进程:
while(turn != 1);//进入区
critical section ;//临界区
turn=0; //退出区
remanider section;//剩余区
缺点:必须轮流访问,违背了空闲让进原则
双标志先检查法:
bool flag[2];
flag[0]=false;
flag[1]=true;
while(flag[1]);//检查另一个是否想进入临界区
flag[0]=true;//标记此进程愿意进入临界区
critical section;
flag[0]=false; //访问完临界区,修改标志为不愿意访问临界区
remainder section;
while(flag[0]);//检查另一个是否想进入临界区
flag[1]=true;//标记此进程愿意进入临界区
critical section;
flag[1]=false; //访问完临界区,修改标志为不愿意访问临界区
remainder section;
缺点:两个进程有可能同时访问临界区,违反了忙则等待。
原因:“检查”和“上锁”不是一气呵成的,检查后有可能发生进程切换。
双标志后监察法:
先上锁,后检查
bool flag[2];
flag[0]=false;
flag[1]=true;
flag[0]=true;//标记此进程愿意进入临界区
while(flag[1]);//检查另一个是否想进入临界区
critical section;
flag[0]=false; //访问完临界区,修改标志为不愿意访问临界区
remainder section;
flag[1]=true;//标记此进程愿意进入临界区
while(flag[0]);//检查另一个是否想进入临界区
critical section;
flag[1]=false; //访问完临界区,修改标志为不愿意访问临界区
remainder section;
缺点:解决了忙则等待,但是有违背了空闲让进等原则,产生饥饿现象
Peterson算法:
bool flag[2];
int turn=0;
P0:
flag[0]=true;
turn=1;
while(flag[1]&&turn==1);
critical session;
flag[0]=flase;
remainder section;
P1:
flag[1]=true; //表示自己想进入临界区
turn=0;//可以优先对方进入临界区
while(flag[0]&&turn==0);//对方想进,那自己就循环等待
critical session;
flag[1]=flase;//自己不想访问临界区
remainder section;
主动争取,主动谦让。peterson没有遵循让权等待的原则。
11.2进程互斥的硬件软件实现方法
中断屏蔽方法:
关中断----临界区----开中断
优点:简单高效
缺点:不适用于多处理机。关中断指令只对当前处理机有用。只适用于操作系统内核进程,不适用于用户进程
TestAndSet指令:
TSL指令,用硬件实现。
SWAP指令:
硬件实现
12.信号量机制:
用户可以通过操作系统提供的一对原语来对信号量进行操作。从而很方便的实现了进程互斥,进程同步。Wait(s)原语和Signal(s)原语。wait和signal也可以简称为PV操作。
整型信号量:只能进行三种操作,初始化,P操作,V操作。但会发生“忙等”的状况。
记录型信号量:用记录型数据结构表示信号。
typedef struct{
int value;//剩余资源数
struct process *L;//等待队列
}semaphore;
void wait(semaphore S){
S.value--;
if(S.value<=0)
block(S.L);//如果剩余资源不够,则用block原语让进程变成阻塞态,并将此进程挂到等待队列里
//从而解决了“忙等”的问题
}
void Signal(semaphore S){
S.value++;
if(S.value<0)//小于0说明,等待队列有进程正在等待,需要wake'up
wakeup(S.L);
}
13.用信号量机制实现进程互斥,同步
设置互斥信号量mutex,初始值为1
在临界区之前执行P(mutex)
在临界区之后执行V(mutex)
对不同的临界区资源,要设置不同的互斥信号量。
P,V操作是必须成对出现的。
进程的同步:让各并发进程按要求有序推进。
设置同步信号量S,初始值为0;
在“前操作”之后,执行V(s)操作
在“后操作”之后,执行P(s)操作
14.生产者-消费者问题
系统中有一组生产者和一组消费者进程,生产者进程每次生产一个产品放入缓冲区,消费者进程每次从缓冲区中取出一个产品并使用。
缓冲区是临界资源,各个进程必须互斥的进行访问。
只有缓冲区没满时,生产者才能把产品放入缓冲区,否则必须等待。(同步)
只有缓冲区不空时,消费者才能取出产品,否则必须等待。(同步)
生产者与消费者共享一个初始为空,大小为n的缓冲区。
用PV操作解决生产者与消费者问题。
设置信号量:
semaphore mutex = 1; //互斥信号量
semaphore empty = n; //同步信号量,表示缓冲区的数量
semaphore full = 0; //同步信号量,表示产品数量,非空缓冲区的数量
producer(){
while(1){
生产一个产品;
P(empty);//消耗一个空闲缓冲区
P(mutex);//上锁
把产品放入缓冲区
V(mutex);//解锁
V(full);
}
}
consumer(){
while(1){
P(full);
P(Mutex);
从缓冲区取出一个产品
V(mutex);
V(empty);
使用产品
}
}
实现互斥是在同一进程中使用一对PV操作
实现同步是在不同进程之间使用一对PV操作
实现互斥的P操作一定要放在实现同步的P操作之后。
V操作不会导致进程阻塞,所以两个V操作顺序可以互换。
15.多(类)生产者-多消费者
16.吸烟者问题
同步事件:前V后P
可以生产多个产品的单生产者,可以从中学习如何轮流实现。
17读者与写者问题
要求:
- 允许多个读者可以同时对文件执行读操作
- 只允许一个写者写入
- 任何一个写者在完成操作之前不允许其他读者或写者工作
- 写者执行写操作前,应该让已有的读者和写者完全退出
两类进程:读进程与写进程
互斥关系:写-写,读-写
key:设置整型变量count来记录当前有几个进程在访问文件
18哲学家进餐问题
互斥 的访问左右两个筷子。而每一位哲学家需要两个筷子才能进餐。
如何避免临界资源分配不当造成死锁现象,是哲学家问题的精髓。
如何避免死锁?(五个大佬同时拿一根筷子)
- 施加限制,比如只允许最多四个哲学家同时进餐,则保证了至少有一个哲学家可以拿到两个筷子
- 奇数号科学家先拿左边的筷子,偶数号科学家先拿右边的筷子。
19管程
信号量机制:编写程序困难,易出错
管程用来实现进程的同步
管程是一个特殊的软件模块,有这些部分组成:
- 共享数据结构
- 对该数据结构进行操作的一系列函数
- 对共享数据结构初始化
- 名字
管程的基本特征:
- 管城内的数据结构只能被管程内的函数操作
- 一个进程只能通过调用管程内的函数才能访问管程内的数据
- 每次仅允许一个进程在管程内执行某个内部过程。
20死锁
死锁:在并发环境下,各个进程互相等待对方的资源,导致进程阻塞,都无法向前推进的现象。
饥饿:长期得不到想要的资源,无法向前推进
死循环:
死锁产生的必要条件:
- 互斥条件
- 不剥夺条件,进程在获得资源未使用完之前,不能由其他进程强行夺走。
- 请求保持条件。已有一个资源,在请求另一个资源,但对已有资源进行保持。
- 循环等待条件。发生死锁一定有循环等待,但是发生循环时未必死锁。
预防死锁:
破坏死锁的四个条件中某一个。
互斥条件:将独占资源的设备改为可以共享。
破坏不剥夺条件:比如:请求新资源不满足时,立即释放所有现有资源;比如请求新资源时,操作系统强行剥夺。
破坏请求和保持条件:采用静态分配方法,进程运行前一次性申请所需要的全部资源
破坏循环等待条件:顺序资源分配,每个进程必须按编号递增的顺序请求资源。只有占有小编号资源才能申请大编号资源,持有大编号资源的进程不能返回来申请小编号资源。
避免死锁:
安全序列------>系统按照安全序列分配资源,这些进程都会满足。如果能找到安全序列,则称系统为安全状态,如果分配了资源之后,系统找不出任何一个安全序列,那么系统就进入了人不安全状态,这意味着之后可能所有的进程都无法顺利的执行下去。但是,如果有进程提前归还了一些资源,系统也有可能重新回到安全状态。
如果系统处于安全状态,一定不会发生死锁。
如果系统进入不安全状态,则可能会发生死锁。
避免死锁:分配资源之前,预判系统是否会进入不安全状态,以此决定是否分配资源。
银行家算法(重点)
检测和解除死锁:
允许死锁发生,但操作系统会检查出并解除死锁。
死锁的检测:资源分配图
如果不能消除所有边,则已经发生死锁。
解除死锁:
- 挂起某些死锁进程,谨防饥饿现象
- 撤销进程法,强行撤销某些死锁进程
- 进程回退法,让死锁进程回退到足以避免死锁的地步。