2.1进程与线程
进程(更好地使多道程序并发执行,提高资源利用率和系统吞吐量)
- 为了更好地描述和控制程序地并发执行,实现OS的最基本的两个特性——并发性和共享性
- 进程实体/进程映像
- 程序段
- 相关数据段
- PCB(进程控制块):进程存在的唯一标志
- 创建进程:创建PCB
- 撤销进程:撤销PCB
- 是系统进行资源分配和调度的一个独立单位
- 进程的特征:动态性、并发性、独立性、异步性
进程的状态与转换
-
创建态
- 申请PCB、分配必要资源、填写信息
- 事件:用户登陆、作业调度、提供服务、应用请求
- 执行:系统(如用户请求服务时)、进程
- 申请PCB、分配必要资源、填写信息
-
就绪态
- 获得了除处理机外的一切资源
-
运行态
- 进程在处理机上运行
- 时间片用完后,让出处理机–>就绪态
- 进程请求除处理机外的其他资源
- 单处理机下同时最多一个
- 进程在处理机上运行
-
阻塞态/等待态
- 进程正在等待某一事件或等待其他资源的获得而暂停运行
-
结束态
- 置为结束态,资源释放
- 进程的组织方式
- 链接方式
- 按照进程状态将PCB分为多个队列
- 操作系统持有指向各个队列的指针
- 索引方式
- 根据进程状态的不同,建立几张索引表
- 操作系统持有指向各个索引表的指针
进程的组织
- 进程控制块PCB的内容
- 进程描述信息:进程标识符PID,用户标识符UID
- 进程控制和管理信息:当前状态,优先级,代码入口,外存地址,时间
- 资源分配清单:代码段、数据段、堆栈段的指针,文件描述符
- 处理机相关信息:通用、地址、控制、标志寄存器的值,状态字
- 程序段:内容可以被多个进程共享
- 数据段:原始数据或中间结果
进程控制(用原语控制实现进程状态转换)
- 进程的创建(创建原语)
- 申请空白PCB,为进程分配资源,初始化PCB,将新进程插入就绪队列
- 进程的终止(终止原语)
- 根据被终止进程的标识符,找出该进程的PCB
- 该进程处于执行状态:立即终止该进程,把处理机资源分配给其他进程
- 该进程有子孙进程:终止其所有子孙进程
- 该进程有父进程:将该进程的所有资源归还给父进程或操作系统
- 将该PCB从所在链表/队列中删除
- 根据被终止进程的标识符,找出该进程的PCB
- 进程的堵塞和唤醒
- Block阻塞原语(运行态–>阻塞态)
- 引起进程阻塞的事件
- 需要等待系统分配某种资源
- 需要等待相互合作的其他进程完成工作
- 执行过程
- 找到将要被阻塞进程的标志号对应的PCB
- (运行态)保护现场,将其转为阻塞态,停止运行
- 把该进程插入相应事件的等待队列,把处理机资源调度给其他就绪进程
- 引起进程阻塞的事件
- Wakeup唤醒原语(阻塞态–>就绪态)
- 引起进程唤醒的事件
- 阻塞进程期待的事件出现时
- 执行过程
- 等待队列中找到相应进程的PCB
- 将进程从等待队列中移出,置为就绪态
- 把该PCB插入就绪队列,等待调度程序调度
- 引起进程唤醒的事件
- 阻塞(Block)原语和唤醒(Wakeup)原语必须成对使用
- Block阻塞原语(运行态–>阻塞态)
进程的通信
共享存储
- 通信进程之间存在一块可以被直接访问的共享空间
- 低级方式:基于数据结构的共享——速度慢
- 高级方式:基于存储区的共享
- 操作系统仅提供:存储空间和同步互斥工具,如信号量(P、V)
消息传递
- 进程间的数据交换以格式化的消息为单位
- 通过发送消息和接收消息两个原语进行数据交换
- 直接通信方式
- 发送进程——接收进程(直接)
- 挂在接收进程的消息缓冲队列上,接收进程从消息缓冲队列中取得消息
- 发送进程——接收进程(直接)
- 间接通信方式
- 发送进程——(中间实体/信箱)——接受进程
管道通信
- 相当于一个固定大小的缓冲区,一般一页
- 半双工通信:不能同时读和写
- 写满、读空后该调用会被阻塞
- 写进程把缓冲区写满,才让读进程读,读进程最多只能有一个
- 缓冲区还有数据时,写进程不会往缓冲区写数据
- 其数据一旦被读取,该数据就会被抛弃
- 实现全双工需要两个管道
- 可有多个读写进程
线程(减小程序在并发执行时的时空开销,提高操作系统的并发性能)
- 线程的基本概念
- 调度的基本单位
- 是“轻量级进程”,是最基本的CPU执行单元,是执行流的最小单元
- 也有就绪、阻塞和运行三种基本状态
- 引入线程后:进程只作为除CPU外的系统资源的分配单位
- 线程仅拥有线程ID、寄存器集合和堆栈
- 可以访问其隶属进程所有资源(如虚拟地址空间),但线程间相互不行
- 线程与进程的比较
- 调度
- 并发性
- 拥有资源
- 独立性
- 系统开销:系统为创建/撤销PCB付出的开销**>>**创建/撤销线程时的开销
- 支持多处理机系统
- 线程的属性
- 每个线程都有一个线程ID和TCB
- 线程时处理机的独立调度单位
- 同一进程的各个线程共享该进程所拥有的资源
- 切换同进程内的线程,系统开销很小
- 不拥有系统资源(仅有一点必不可少、能保证独立运行的资源)
- 线程的状态和转换:执行状态、就绪状态、阻塞状态
线程的组织与控制
- 线程控制块(TCB):记录控制和管理线程的信息
- 线程标志符TID
- 寄存器:PC、PSW、通用寄存器
- 堆栈指针
- 线程运行状态
- 优先级
- 线程的创建
- 线程的终止
线程的实现方式
进程库支持的线程:用户级(ULT)
- 所有工作都由应用程序通过线程库在用户空间实现
- 优点
- 切换不需要转换到内存空间,节省开销
- 实现与OS平台无关
- 缺点
- 线程被阻塞,等于进程被阻塞
- 无法充分利用多处理器
- 多对一
内核支持的线程:内核级(KLT)
- 所有工作在内核空间实现
- 优点:适合多处理器,是==处理机分配的单位==
- 缺点:线程切换需要转到内核态,开销大
- 一对一:每个线程有线程控制块
组合方式
- 多个用户级,与多个内核级对应
- 集两者之所长
- 多对多
2.2处理机调度
2.2.1调度的概念
处理机调度是对处理机进行分配,是多道程序操作系统的基础
调度的层次
调频次数:低级>中级>高级
高级调度(作业调度)
- 外存–>内存(面向作业)
- 无——创建态——就绪态
- 作业调入时会建立PCB,调出时会才撤销PCB
- 给作业分配内存、I/O设备等必要资源
- 为进程活动做准备,使进程正常活动起来
中级调度(内存调度)
- 外存–>内存(面向过程)
- 内存不够时,将暂时不能运行的进程调至外存等待(挂起态)
- 把外存上具备运行条件的就绪进程重新调入内存,并修改其状态为就绪态,挂在就绪队列上等待
- 挂起态——就绪态
- 目的:提高内存利用率和系统吞吐量
低级调度(进程调度/处理机调度)
- 内存–>CPU
- 将处理机分配给进程,是最基本的调度
- 就绪态——运行态
2.2.2调度的目标
-
CPU利用率
C P U 利用率 = C P U 有效工作时间 C P U 有效工作时间 + C P U 空闲等待时间 CPU利用率=\frac{CPU有效工作时间}{CPU有效工作时间+CPU空闲等待时间} CPU利用率=CPU有效工作时间+CPU空闲等待时间CPU有效工作时间 -
系统吞吐量
-
周转时间:从作业提交到作业完成的时间
平均周转时间 = Σ ( 作业完成时间 − 作业提交时间 ) 作业数 平均周转时间=\frac{\Sigma(作业完成时间-作业提交时间)}{作业数} 平均周转时间=作业数Σ(作业完成时间−作业提交时间)带权周转时间 = 作业周转时间 作业实际运行时间 带权周转时间=\frac{作业周转时间}{作业实际运行时间} 带权周转时间=作业实际运行时间作业周转时间
-
等待时间
- 进程出于等处理机的时间之和
-
响应时间
- 用户提交请求到系统首次产生响应所用时间
2.2.3调度的实现
调度程序(调度器)
调度的时机、切换与过程
- 不能进行进程的调度与切换的情况
- 在处理中断的过程中
- 进程在操作系统内核临界区中
- 其他需要完全屏蔽中断的原子操作过程
- 应该进行进程调度与切换的情况
- 发生引起调度条件且当前进程无法继续运行下去时,可马上进行进程调度与切换
- 非剥夺
- 中断处理结束或自陷处理结束,若置上请求调度标志,则可马上进行进程调度与切换
- 剥夺
- 发生引起调度条件且当前进程无法继续运行下去时,可马上进行进程调度与切换
- 进程切换要求保存原进程当前断点的现场信息(推入当前进程的内核堆栈,并更新堆栈指针),恢复被调度进程的现场信息(在其内核栈中装入新进程的现场信息、更新空间指针、重设PC寄存器等)
进程调度方式
关中断状态下无法调度程序(要时钟中断来算时间片)
- 非抢占调度方式(非剥夺方式)
- 无法及时处理紧急任务
- 仅当前进程阻塞/退出才触发调度
- 抢占调度方式(剥夺方式)
- 每k个时钟中断可能触发
- 优先处理更紧急的进程
- 通过时钟中断,让各进程按时间片轮转执行
- 闲逛进程(idle)
- 系统中无就绪进程运行,CPU永不空闲
- 优先级最低,耗能也低
- 不需要CPU以外的资源,不会被堵塞
- 常用零地址指令,占一个完整指令周期(有检查中断)
- 两种线程的调度
- 用户级线程调度
- 内核级线程调度
- 内核级线程切换代价高(切换上下文)
- 内核级线程并发度高
2.2.4经典的调度算法
先来先服务FCFS
- 选择最先进入该队列的一个/几个作业
- 既可用于作业调度,也可用于进程调度
- 非抢占式
- 不能作为分时系统和实时系统的主要调度策略
- 对长作业有利,但对短作业不利
- 有利于CPU繁忙型作业,而不利于I/O繁忙型作业
短作业优先SJF
- 选择运行时间最短的作业,将其调入内存
- 对短作业有利,对长作业不利
- 会导致长作业长期不被调度,“饿死”
- 默认:非抢占式,也有最短剩余时间
- 在所有进程同时可运行情况下,平均等待时间、平均周转时间最短
优先级调度算法
- 静态优先级(有饥饿)、动态优先级(考虑等待时间)
- 一般
- 系统进程优先级高于用户
- 交互性(前台)高于非交互(后台)
- I/O型进程高于计算型
- 注:优先级与进程长短、需求资源多少无关
高响应比优先算法
-
主要用于作业调度,在每次作业调度时,先计算后备作业队列中每个作业的响应比,选最高
-
响应比 = 等待时间 + 要求服务时间 要求服务时间 ( ≥ 1 ) 响应比=\frac{等待时间 + 要求服务时间} {要求服务时间}(≥1) 响应比=要求服务时间等待时间+要求服务时间(≥1)
-
综合FCFS和SJF,非抢占式,无饥饿现象
时间片轮转算法
- 公平、调度开销大
- 抢占式
- 适用于分时系统,适合交互式系统
- 时间片过大:退化为先来先服务调度算法
- 时间片过小:处理机在进程间过于频繁地切换,处理机开销增大,真正用于运行进程地时间减少
- 时间片长短的决定因素
- 系统的响应时间
- 就绪队列中的进程数目
- 系统的处理能力
多级队列调度算法
- 设置多个就绪队列
- 其优先级、时间片、调度算法可不同
多级反馈队列调度算法
- 分多级队列,优先级渐低,时间片渐大
- 级内FCFS
- 时间片用完,放到下一级队列末尾
- 低级队列空时,才执行高级队列
- 抢占式:执行时,有高优先级的来了,立即切换
有饥饿
- 短作业优先算法SJF
- 优先级调度算法
- 多级反馈队列调度算法
2.2.5上下文及其切换机制
- 进程
- 地址空间(代价巨大)
- 页表寄存器
- TLB全部失效:同时新进程初期缺页率高
- Cache全部失效、有些需要写回
- 地址空间(代价巨大)
- 线程
- PC、寄存器、堆栈
2.3同步与互斥
2.3.1基本概念
临界资源
- 一次仅允许一个进程使用的资源
- 许多变量、数据、公有队列、
私有数据等,还包括很多物理设备,如:打印机 - 访问过程
- 进入区:“上锁”
- 临界区/临界段:访问临界资源的那段代码
- 退出区:“解锁”
- 剩余区:代码中剩余的部分
同步(直接制约关系)
- 源于相互合作
- 两个或多个进程之间有先后次序
互斥(间接制约关系)
- 源于互斥访问
- 进入临界区使用临界资源的进程有且只有一个
- 准则
- 空闲让进
- 忙则等待
- 有限等待
- 在有限时间内,让请求访问的进程进入临界区
- 保证进程不会“饥饿”
- 让权等待
- 当进程不能进入临界区时,应立即释放处理器,防止进程忙等
- “占着茅坑不拉屎”
2.3.2实现临界区互斥的基本方法
软件实现方法
单标志法——turn
- 算法思想
- 每个进程进入临界区的权限只能被另一进程赋予
- 两个进程交替进入临界区
- 设置一个turn来指示被允许进入临界区的进程编号,表达“谦让”
- 优点:实现简单
- 缺点:违背“空闲让进”,造成资源无法充分利用
双标志先检查法——flag[]
- 算法思想
- 每个进程访问临界区前,先检查临界资源是否被访问,空闲才可进入
- 先“检查”,后“上锁”
- 设置一个布尔类型的数组flag[i]=false/ture,表达“意愿”
- 优点:不用交替进入可以连续使用
- 缺点
- 两个进程可能同时进入临界区,==违背“忙则等待”==原则
- 检查和上锁无法一气呵成
双标志后检查法——flag[]
- 算法思想
- 先“上锁”,后“检查”
- 设置一个布尔类型的数组flag[i]=false/ture,表达“意愿”
- 优点:避免两个进程同时进入临界区
- 缺点:双方可能会相互谦让,导致饥饿,违背“空闲让进”和“有限等待”
Peterson算法——turn+flag[]
- 算法思想
- 结合双标志法、单标志法的思想。如果争夺,则谦让
- 设置一个turn,用来表示优先让哪个进程进入临界区,表达“谦让”
- 进入区
- 主动争取
- 主动谦让
- 检查对方是否也想使用,且最后一次是不是自己说了"客气话"——联想:给压岁钱
- 优点:解决了进程互斥问题,遵循了空闲让进、忙则等待、有限等待
- 缺点:违背“让权等待”,会发生“忙等”
硬件实现方法
中断屏蔽方法
硬件指令方法
-
优点
- 适用于任意数目的进程,而不管是单处理机还是多处理机
- 简单、容易验证其正确性
- 可以支持进程内有多个临界区,只需为每个临界区设立一个布尔变量
-
缺点
- 进程等待进入临界区时要消耗处理机时间,不能实现"让权等待"
- 从等待进程中随机选择一个进入临界区,有些进程可能一直选不上,从而导致**“饥饿”**现象
TestAndSet指令(TS/TSL)
- 是原子操作,不允许被中断
- 为临界资源设置一个共享布尔变量lock(ture/false),表示临界资源是否被占用
- 优点:实现简单,适用于多处理机环境
- 缺点:不满足“让权等待”,暂时无法进入CPU的进程会占用CPU并循环执行TSL指令,从而导致==“忙等”==
Swap指令
- 功能:交换两个字(字节)的内容
- 优点:可以简单有效地实现互斥
2.3.3互斥锁
- 用布尔值True和False两种状态表示有无被使用,来实现进程互斥;
- acquire()获得锁,release()释放锁——均为原子操作;
- 常用硬件机制来实现;
- 特性:
- 忙等,进程时间片用完才下处理机,违反“让权等待”;
- 通常用于多处理机系统,上锁时间短;
2.3.4信号量
- 只能被两个标准的原语wait(S)–P和signal(S)–V访问
整型信号量(S)
- 表示当前系统中可用的资源数
- wait(S):相当于“进入区”,S=S-1
- signal(S):相当于“退出区”,S=S+1
- 未遵循“让权等待”,使进程处于==“忙等”==的状态
记录型信号量(S)
- 遵循了“让权等待”原则,不存在“忙等”现象,用一个进程链表L,链接所有等待该资源的进程
wait操作——申请资源:S.value–
- S.value < 0时,
- 调用block原语
- 自我阻塞,放弃处理机,并插入阻塞队列,运行态——阻塞态
- 可用资源数=0,等待资源的进程数=|S.value|
signal操作——释放资源:S.value++
- 若S.value++ ≤ 0,
- 表示S.L中仍有等待该资源的进程被堵塞
- 调用Wakeup原语,唤醒第一个等待进程
- 若S.value=0,
- 可用资源数=0,等待资源的进程数=0
- 若S.value>0,
- 可用资源数=S.value,等待资源的进程数=0
利用信号量实现同步
- 前V后P,让各并发进程按要求有序地推进
- 初始值根据可用资源数来确定
利用信号量实现互斥
- 用一对PV夹紧访问内容,中不能有其他冗余代码
- 分析并发进程的关键活动,划定临界区
- 对不同的临界资源设置不同的信号量
- 用**互斥信号量(mutex)**表“进入临界区的名额”
- 缓冲区大小=1,可能可以不用mutex
- 初始值一般为1
利用信号量实现前驱
分析进程同步和互斥问题的方法步骤
关系分析——整理思路——设置信号量
2.3.5管程
管程的定义
- 更方便地实现进程同步与互斥
- 保证了进程互斥,由编译器自动实现进程的同步与互斥,降低了死锁发生的可能;
- 把对共享资源的操作封装起来(类似Class)
- 每次仅允许一个进程进入管程,从而实现互斥
- 管程是进程同步工具,解决信号量机制大量同步操作分散的问题
- 管程是被进程调用的,是语法范围,无法创建和撤销
管程的基本特征
- (局部于管程的)数据只能被(局部于管程的)过程所访问
- 一个进程只有通过调用管程内的过程才能进入管程访问共享数据
组成
-
管程的名称
-
局部于管程内部的共享数据结构说明
-
对该数据结构进行操作的一组过程(函数)
-
对局部于管程内部的共享数据设置初始值的语句
条件变量(等待序列)
-
本身没有值,实现进程同步,常配合锁使用
-
资源数用别的变量记录,判断后调用条件变量
-
wait(等待):释放管程的使用权,即让出“入口”;阻塞进程
signal(唤醒):唤醒一个因x条件而阻塞的进程
-
if( a < 1 ) S.wait
- 如果a<1,进程自我阻塞,插入S队列中
- 同时释放管程使用权,允许一个其他进程进入
条件变量和信号量的比较
- 相似点:条件变量的wait/signal操作类似于信号量的P/V操作,可以实现进程的阻塞/唤醒;
- 不同点:
- 条件变量没有值,仅实现了“排队等待”功能;
- 信号量有值,值反映了剩余资源数;
- signal操作不会使条件变量值改变,但信号量机制的V操作会使得S.value+1;
2.3.6经典同步问题
生产者-消费者问题
- 缓冲区没满–生产者生产,缓冲区没空–消费者消费
- 初始值:mutex=1——互斥信号量,full=0,empty=1;
-
semaphore mutex=1; semaphore empty=n; semaphore full=0; producer(){ while(1){ 生产一个产品; P(empty); //empty--,获取空缓冲区单元 P(mutex); //mutex=0,进入临界区 把产品放入缓冲区; V(mutex); //mutex=1,退出临界区 V(full); //full++,满缓冲区数加1 } } consumer(){ while(1){ P(full); //empty--,获取满缓冲区单元 P(mutex); //mutex=0,进入临界区 从缓冲区取出一个产品; V(mutex); //mutex=1,退出临界区 V(empty); //empty++,空缓冲区数加1 使用产品; } }
多生产者-多消费者问题
- 缓冲区为1,因此即使不设置专门的互斥变量,也不会出现多个进程同时访问盘子的现象;
- 若缓冲区>1,则需要设置mutex,否则导致两个进程写入缓冲区的数据相互覆盖的情况;
- dad(),daughter(),mom(),son()必须连续执行
读者-写者问题
哲学家进餐问题
吸烟者问题
- 可生产多种产品的单生产者—多消费者
2.4死锁
注意与“饥饿”的区别
2.4.1死锁的概念
- 多个进程因竞争资源而造成的一种僵局(互相等待),若无外力作用,这些进程都无法向前推进
死锁产生的原因
2.4.2死锁的处理策略
死锁预防(不允许死锁发生的静态策略):破坏必要条件
破坏互斥条件(无法破坏)
- 非共享资源:SPOOLing技术——假脱机技术
- 有些场合应该保护这种互斥性
破坏不剥夺条件
- 实现复杂,增加系统开销,降低系统吞吐量
- 不满足全部资源时,直接释放所有;
- 常用于易于保存恢复的资源,如寄存器和内存,不能用于打印机等;
破坏请求并保持条件
- 预先静态分配全部资源
- 系统资源浪费严重,资源利用率极低,会导致**“饥饿”**现象
破坏循环等待条件
- 限制了新类型设备的增加,给用户的编程带来麻烦
- 顺序资源分配,按编号递增申请资源,造成资源的浪费
死锁避免(不允许死锁发生的动态策略):防止系统进入不安全状态
需要进程运行所需的资源总量信息,死锁检测不需要
系统安全状态
- 系统按经常推进顺序为其分配其所需的资源,直至满足每个进程对资源的最大需求,使每个进程都可顺序完成。
- 系统处于安全状态便可避免死锁,进入不安全状态,可能进入死锁
银行家算法
🐔选择题25、27、28
- 防止进入不安全状态
- 用于判断系统的不安全状态,无法判断是否处于死锁状态,死锁用死锁定理判断
- 流程:假设分配,然后检查安全性
- 注:不会限制申请顺序
死锁的检测及解除(允许死锁发生)
不限制,但能检查错误并解除
资源分配图(描述)
死锁定理:资源分配图不可完全被简化,则存在死锁