文章目录
2.1 进程与线程
2.1.1 进程的概念和特征
1. 进程的概念
- 程序:静态,是存放在磁盘里的可执行文件,就是一系列的指令集合
- 进程:动态,是程序的一次执行过程。(同一程序多次执行会对应多个进程
进程的定义:进程是进程实体的运行过程,是系统进行资源分配和调度的一个独立单位
进程的组成
- 一个进程实体(进程映像)由PCB、程序段和数据段组成。
- 进程是动态的,进程实体是静态的,反映了进程在某一时刻的状态
2. 进程的特征
- 动态性:最基本的特征,进程是程序的一次执行过程,动态产生、变化、消亡,具有一定的生命周期
- 并发性:内存中有多个进程实体,可以并发执行
- 独立性:进程是可以独立运行,独立获得资源,独立接受调度的基本单位
- 异步性:各进程按照各自独立的不可预知的速度向前推进,操作系统需要提供“进程同步机制”来解决异步问题
- 结构性:每个进程都会配置一个PCB,结构上看进程由程序段、数据段和PCB组成
2.1.2 进程的状态与转换
- 创建态:进程刚被创建时处于“创建态”,操作系统为进程分配资源、初始化PCB
- 就绪态:进程创建完成后进入“就绪态”,此时进程已经具备运行条件,但由于没有空闲CPU,暂时不能运行
- 运行态:正在CPU上运行的进程
- 阻塞态(等待态):进程运行的过程中可能请求某个事件发生(例如请求系统资源分配或者其他进程响应,在这个事件发生之前,进程无法继续执行,此时操作系统会让这个进程下CPU,进入“阻塞态”
- 终止态:操作系统让进程下CPU,回收内存空间等资源,回收PCB
⚠️区分就绪态和阻塞态
- 就绪态是指进程只缺少处理器,只要获得处理机资源就立即运行
- 等待态:进程需要其他资源(处理机以外),或者需要等待某一事件才能继续
进程状态转换
- 就绪态 -> 运行态:处于就绪态的进程被调度后,获得处理机资源(分派处理机时间片),进程从就绪态变为运行态
- 运行态 -> 就绪态:处于运行态的进程在时间片用完后,不得不让出处理机,进程由运行态转换为就绪态。在可剥夺的操作系统中,优先级更高的进程就绪时,调度程序将正在执行的进程转换为就绪态,让更高优先级的进程执行
- 运行态 -> 阻塞态:进程请求某一资源的使用(例如外设)或等待某一事件(例如IO操作)时,就从运行态转换为阻塞态。进程以系统调用方式请求操作系统提供服务,是一种特殊的、由运行用户态程序调用操作系统内核过程的形式(主动行为
- 阻塞态 -> 就绪态:进程等待的事件到来时,进程由阻塞态转换为就绪态(被动行为

2.1.3 进程的组织
进程是一个独立的运行单位,是操作系统进行资源分配和调度的基本单位,由进程控制块PCB(核心部分)、程序段和数据段组成
1. 进程控制块(PCB
- 进程创建时,操作系统为其新建一个PCB,该结构之后常驻内存,任何时候都可以存取,并在进程结束时删除。PCB是进程实体的一部分,是进程存在的唯一标志
- 进程执行时,系统通过PCB了解该进程的现行状态信息,以便操作系统对其进行控制和管理
- 进程结束时,系统回收其PCB,进程随之消亡
PCB是进程存在的唯一标志,进程被创建时,操作系统会为其创建PCB,进程结束时,回收其PCB

多个PCB组织方式
- 链接方式:将同一状态的PCB连接成一个队列
- 索引方式:将同一状态的进程组织在一个索引表中
2. 程序段
程序的代码(指令序列,可被调度到CPU执行
⚠️程序段可被多个进程共享
3. 数据段
存储原始数据、运行过程中产生的各种数据
- 下列C语言程序中的内容和相关数据结构位于:
全局赋值变量-正文段;未赋值局部变量-栈段;函数调用实参传递值-栈段;用malloc要求动态分配的存储区-堆段;常量值-正文段;进程优先级-PCB
2.1.4 进程控制
- 进程控制的主要功能是对系统中所有进程实施有效管理,包括创建新进程、撤销已有进程、实现进程状态转换等功能
- os中把进程控制用的程序段称为原语,其特点是:执行期间不允许中断,是一个不可分割的基本单位
原语
- 原语的执行具有原子性,执行期间不允许被中断
- 可以用“关中断指令”(不再检查中断信号)和“开中断指令”两个特权指令实现原子性
- 原语运行在核心态
1. 进程创建
- 允许一个进程创建另一个进程(父进程创建子进程)
- 子进程可以继承父进程所拥有的资源
- 子进程被撤销时,应当将从父进程获得的资源归还
- 撤销父进程时,同时也会撤销其所有的子进程
操作系统创建新进程:
- 为新进程分配一个唯一的进程标识号(PID),并申请一个空白PCB(PCB是有限的);若PCB申请失败,则创建进程失败
- 为进程分配其运行所需的资源,例如内存、文件、IO设备和CPU时间等(在PCB中实现)。这些资源从操作系统、或者父进程获得。如果资源不足,进程处于创建态,等待内存资源,而不是创建失败
- 初始化PCB,包括初始化标志信息、初始处理机状态信息和初始化处理机控制信息,以及设置进程优先级
- 若进程就绪队列能接纳新进程,则新进程插入就绪队列,等待被调度
引起进程创建的事件
- 用户登陆:分时系统中,用户登陆成功,系统会为其创建一个新进程
- 作业调度:多道批处理系统中,新的作业放入内存,会为其创建一个新进程
- 提供服务:用户想操作系统提出某些请求
- 应用请求:用户进程请求创建资格子进程
2. 进程的终止(就绪/阻塞/运行态 -> 终止态 -> 无
进程终止:(终止原语)
- 根据被终止进程标识符,检索进程PCB,读取该进程状态
- 若被终止进程处于执行状态,立即终止该进程执行,将处理机资源分配给其他进程
- 若该进程还有子孙进程,则全部终止
- 将该进程所拥有的全部资源归还给操作系统或父进程
- 将PCB删除
引起进程终止的事件
- 正常结束
- 异常结束
- 外界干预
3. 进程的阻塞(运行态 -> 阻塞态
- 阻塞原语(Block
- 找到要阻塞的进程对应的PCB
- 保护进程运行现场,PCB状态信息设置为阻塞态,暂时停止进程运行
- PCB插入相应事件的等待队列
- 引起进程阻塞的事件
- 需要等待系统分配资源
- 等待其他相互合作的进程完成工作
4. 进程的唤醒(阻塞态 -> 就绪态
- 唤醒原语(Wakeup)
- 在事件等待队列中找到PCB
- PCB设置为就绪态,移除等待队列
- PCB插入就绪队列,等待被调度
- 引起进程唤醒的事件
- 等待的事件发生:进程因为什么事件被阻塞,就应该由什么事件唤醒
5. 进程的切换(一个进程 运行态->就绪态;另一个进程 就绪态->运行态
- 切换原语
- 运行环境存入PCB
- PCB移入相应队列
- 选择另一个进程执行,更新PCB
- 根据PCB恢复新进程运行环境
- 引起进程切换的事件
- 当前进程时间片到
- 更高优先级的进程到达
- 当前进程主动阻塞
- 当前进程终止
2.1.5 进程的通信
进程通信是指进程之间的信息交换
每个进程拥有各自的内存地址空间。为了保证安全,一个进程不能直接访问另一个进程的地址空间
PV操作是低级通信方式,高级通信方式主要有以下三种:共享存储、消息传递、管道通信


1. 共享存储
设置一个共享空间,两个进程对共享空间的访问是互斥的(互斥访问通过os提供的工具实现,例如PV操作
- 基于数据结构的共享:例如共享空间内只放一个长度为10的数组,这种共享方式速度慢,限制多,是低级通信方式
- 基于存储区的共享:在内存中划出一块共享存储区,由进程控制(而不是os),这种方式速度快,是高级通信方式
2. 消息传递
数据以格式化的消息为单位,进程通过os提供的原语进行数据交换
- 直接通信方式:发送进程直接把消息发送给接受进程,挂在接受进程消息缓冲队列上,接受进程从消息缓冲队列中取得消息
- 间接通信方式:发送进程将消息发送到某个中间实体(称为信箱),接受进程从信箱取得消息
- 用信箱实现进程间相互通信需要有两个通信原语:发送原语和接收原语
3. 管道通信
管道是指用于连接读写进程的一个共享文件(pine文件),实质是在内存中开辟一个固定大小的缓冲区
- 管道只能半双工通信
- 各个进程要互斥访问管道
- 管道写满时,写进程的系统调用被阻塞;管道空时,读进程的系统调用被阻塞
- 管道没写满,不允许读;管道没读空,不允许写
- 数据一旦被读出,就离开管道,这意味着读进程最多只能有一个
2.1.6 线程和多线程模型
1. 线程的基本概念
引入线程目的:减少程序在并发执行时所付出的时空开销,提高操作系统并发性能
- 线程最直接的理解:轻量级进程
- 线程是进程中的一个实体,是被系统独立调度和分配的基本单位。一个进程可以有多个线程
- 线程只拥有一丁点运行必不可少的资源,不拥有系统资源
- 线程可以与同属一个进程的其他线程共享进程所拥有的所有资源
- 一个线程可以创建和撤销另一个线程
- 同一个进程中的多个线程之间可以并发执行
- 线程在运行中呈现出间断性,线程也有就绪、阻塞和运行三种基本状态
⚠️:引入线程后,进程只作为除了CPU之外的系统资源的分配单位,而线程作为处理机的分配单元
由于一个进程内有多个线程,若线程的切换发生在同一个进程内部,则只需要很小的时空开销
2. 线程和进程的比较
| 传统操作系统 | 引入线程的操作系统 | |
|---|---|---|
| 调度 | 进程是拥有资源,独立调度的基本单位。每次调度需要上下文切换,开销大 | 线程是独立调度的基本单位,线程切换开销远小于进程切换。同一个进程内线程切换不会引起进程切换 |
| 并发性 | 进程可以并发执行 | 进程可以并发执行,线程也可以并发执行。提高系统资源利用率和系统吞吐量 |
| 拥有资源 | 进程是系统中拥有资源的基本单位 | 线程不拥有系统资源,只有一点运行必不可少的资源。线程可以访问其隶属进程的所有资源 |
| 独立性 | 每个进程都有独立的地址空间和资源,除了共享全局变量,不允许其他进程访问 | 某进程中的线程对其他进程不可见。同一进程中的不同线程共享进程的地址空间和资源 |
| 系统开销 | 创建、撤销进程,上下文切换开销大 | 线程切换开销小,线程之间同步和通信容易实现 |
| 支持多处理机系统 | 进程只能在一个处理机上运行 | 可以将进程中的多个线程分配到多个处理机上执行 |
3. 线程的属性
- 线程是一个轻型实体,不拥有系统资源,但有一个唯一的标识符和一个线程控制块(TCB),TCB记录了线程执行的寄存器和栈等状态
- 不同线程可以执行相同程序,即同一个服务程序被不同用户调用时,操作系统将其创建成不同的线程
- 同一进程中的各个线程共享该进程拥有的资源
- 线程是处理机的独立调度单位,多个线程可以并发执行。在单CPU的计算机系统中,各线程可交替占用CPU;多CPU系统中,各线程可以同时占用不同的CPU。各个CPU为同一个进程内的线程同时服务,可以缩短进程处理时间
- 线程的生命周期中会经历阻塞态、就绪态和运行态等各种状态
4. 线程的状态与转换
- 执行状态:线程获得处理机,正在运行
- 就绪状态:线程已具备运行条件,只需要获得CPU便可立即执行
- 阻塞状态:线程执行中因为某些事件受阻,处于暂停状态
线程状态之间的转换和进程状态转换一致
5. 线程的组织与控制
线程控制块
- 与进程类似,系统为每个线程配置一个线程控制块TCB,用于记录和管理线程的信息
- TCB通常包括:线程标识符、一组寄存器、线程运行状态、优先级、线程专有存储区、堆栈指针
- 同一进程中的所有线程完全共享进程的地址空间和全局变量,各个线程都可以访问进程地址空间的每个单元。一个线程可以读、写、甚至清除另一个线程的堆栈
线程创建
用户程序启动时,通常只有一个称为“初始化线程”的线程在执行,其功能是创建新线程
创建新线程时,需要用一个线程创建函数,提供相应参数
线程终止
- 一个线程完成任务后,或者运行中出现异常被强制终止时,终止线程调用相应函数执行终止操作
- 某些线程(主要是系统线程)一旦被创建就不会被终止
- 通常线程被终止后不会立即释放占有的资源,只有当其他线程执行分离函数后,被终止线程才和资源分离
- 被终止但尚未释放资源的线程仍可被其他线程调用,以便被终止线程重新恢复运行
6. 线程的实现方式
线程的实现分为用户级线程(User-Level Thread, ULT)和内核级线程(Kernel-Level Thread)(内核支持的线程)

用户级线程 ULT
- ULT中,有关线程管理的所有工作(创建、撤销、切换等)都由应用程序在用户空间中完成,内核意识不到线程的存在。
- 应用程序可以使用线程库设计成多线程程序。用户级线程管理工作由应用程序通过线程库实现
- 使用ULT的系统,调度仍然以进程为单位,内核每次分配给一个进程一个CPU,进程内只有一个线程可以执行,不能发挥多处理机的优势。线程被阻塞时,同一进程内的所有线程都被阻塞
- 用户级线程切换不需要CPU从用户态转变为内核态。线程切换不需要切换CPU状态,开销减少
内核级线程 KLT
- KLT管理工作由操作系统内核完成
- KLT的切换需要在内核态才能完成
- 操作系统为每个KLT建立相应的TCB,通过TCB对KLT进行管理,因此内核可以看到线程
- 优缺点:
- 一个线程被阻塞,其他的还可以继续执行,并发能力强
- 可以在多核处理机上运行
- 线程管理成本高,开销大
7. 多线程模型
某些系统同时支持ULT和KLT,由链接方式的不同可以分为多对一、一对一和多对多模型



1 . 系统动态DLL库中的系统线程,被不同进程调用,属于相同的线程
2.2 处理机调度
2.2.1 调度的概念
1. 调度的基本概念
处理机调度:对处理机进行分配,从就绪队列中按照一定算法,选择一个进程将处理机分配给它运行,以实现进程并发执行
2. 调度的层次
调度有三个层次:高级调度、中级调度、低级调度

- 高级调度:按照一定的原则从外存的作业后备队列中挑选一个作业调入内存,并创建进程。每个作业只调入一次(创建PCB),调出一次(撤销PCB
- 中级调度(内存调度):挂起:当内存不足时,可将某些进程的数据调出外存,等内存空闲或进程需要运行时再重新调入内存。暂时调到外存等待的进程状态为挂起状态,PCB会被组织成挂起队列。中级调度:按照某种策略决定将哪个处于挂起状态的进程重新调入内存
- 低级调度(进程/处理机调度):按照某种策略,从就绪队列中选取一个进程,将处理机分配给它。进程调度是操作系统最基本的一种调度,进程调度的频率很高
- 挂起态又可以分为就绪挂起和阻塞挂起状态
3. 三级调度的联系
作业调度从外存的后备队列中选择一批作业进入内存,为其建立进程,这些进程被送入就绪队列。进程调度从就绪队列中选择一个进程,将其状态改为运行态,将CPU分配给它。中级调度是为了提高内存利用率,将暂时不能运行的进程挂起
2.2.2 调度的目标
评价不同调度算法的标准

2.2.3 调度的实现
1. 调度程序(调度器
操作系统中,用于调度和分派CPU的组件称为调度程序,主要由以下三部分组成

- 排队器:将所有进程按照一定策略拍成一个或多个队列
- 分派器:依据调度程序选择的进程,将其从就绪队列中取出,将CPU分配给新进程
- 上下文切换器:处理机进行切换时,需要进行上下文切换:当前进程上下文保存到PCB中,装入分派程序的上下文;移出分派程序的上下文,将新选进程的CPU现场装入处理机的各个相应寄存器
2. 进程调度的时机、切换与过程
调度程序是操作系统内核程序,请求调度的事件发生后才可能运行调度程序,调度新的就绪程序后才会引起进程切换
需要进行进程调度与切换的情况
- 当前运行的进程主动放弃处理机
- 进程正常终止
- 进程过程发生异常而终止
- 进程主动请求阻塞(例如等待IO
- 当前运行的进程被动放弃处理机
- 时间片用完
- 有更紧急的事件需要处理(例如IO中断
- 更高优先级的进程进入就绪队列
不能进行进程调度与切换的情况
- 处理中断时
- 进程位于操作系统内核程序临界区中
- 原子操作过程(原语)中
临界资源:一个时间段只允许一个进程使用的资源,各个进程需要互斥地访问临界资源
临界区:访问临界资源的代码
内核程序临界区:用于访问某种内核数据结构,例如进程的就绪队列
3. 进程调度的方式
- 非剥夺调度方式、又称非抢占方式。只允许进程主动放弃处理机
- 剥夺调度方式,又称抢占方式。允许剥夺处理机
4. 闲逛进程
- 进程切换时,如果系统中没有就绪进程,则会调度闲逛进程(idle)运行
- 如果没有其他进程就绪,闲逛进程就会一直进行
- 闲逛进程优先级最低,只要有进程就绪,就会立即让出处理机
5. 两种线程的调度
- 用户级线程调度:内核不知道线程的存在;内核选择一个进程,给予时间控制,由进程中的调度程序决定哪个线程运行
- 内核级线程调度:内核选择一个特定线程运行,通常无需考虑线程属于哪个进程
上下文切换:切换CPU到另一个进程需要保存当前进程的状态并恢复另一个进程的状态,这个任务称为上下文切换。上下文切换一定发生在内核态
模式切换:用户态和内核态之间的切换。模式切换时可能没有发生进程切换
2.2.4 调度算法
操作系统存在多种调度算法,有些适用于作业调度,有些适用于进程调度,有的两者都适用
调度算法包括:
- 先来先服务(FCFS)、短作业优先(SJF)和高响应比优先(HRRN)(交互性差)
- 时间片轮转调度算法(RR)、优先级调度算法、多级反馈队列调度算法
饥饿:某个进程/作业长期得不到服务
1. 先来先服务(FCFS, First Come First Serve
FCFS调度算法是一种最简单的调度算法,可用于作业调度和进程调度

- 按照作业/进程到达的先后顺序进行服务
- 属于不可剥夺算法
- 对长作业有利,对短作业不利
- 不会导致饥饿
2. 短作业优先(SJF, Shortest Job First

- 追求最少的平均等待时间、平均周转时间、平均带权周转时间
- 服务时间最短的作业/进程优先得到服务
- SJF和SPF是非抢占式的算法,但也有抢占式的版本——最短剩余时间优先算法:SRTN(未说明的话,默认非抢占式
- 对短作业有利,对长作业不利;并不一定能做到真正的短作业优先
- 未考虑作业的紧迫程度
- 可能导致长作业饥饿,甚至饿死
3. 优先级调度算法
- 每个作业/进程有各自优先级,调度时选择优先级最高的作业/进程
- 可用于作业调度、进程调度(甚至可以用于IO调度
- 分为非抢占式和抢占式
- 可以灵活的调整优先级,适用于实时操作系统
- 如果源源不断地有高优先级进程到来,可能发生饥饿
- 静态优先级:创建进程时确定,之后一直不变
- 动态优先级:创建进程时有初始值,之后根据情况动态地调整
通常
- 系统进程优先级高于用户进程
- 前台进程优先级高于后台进程
- 操作系统更偏好I/O型进程(又称I/O繁忙型进程)(与I/O型进程相对的是计算型进程,又称CPU繁忙型进程)
- 原因:IO设备和CPU可以并行工作,偏好IO型进程可以尽早让IO设备投入工作,提高资源利用率、系统吞吐量
- 如果进程在就绪队列中等待了很长时间,可以适当提升其优先级;如果进程占用处理机运行了很长时间,可以适当降低优先级
4. 高响应比优先(HRRN, Highest Response Ratio Next
主要用于作业调度,是FCFS和SJF调度算法的综合平衡
- 在每次调度时计算作业/进程的响应比,选择最高的一个
- 响 应 比 R p = 等 待 时 间 + 要 求 服 务 时 间 要 求 服 务 时 间 响应比R_p=\frac{等待时间 + 要求服务时间}{要求服务时间} 响应比Rp=要求服务时间等待时间+要求服务时间
- 作业的等待时间相同时,要求服务时间越短,响应比越高,越有利于段作业,类似SJF
- 要求服务时间相同时,等待时间越长,响应比越高,类似FCFS
- 对于长作业,响应比随等待时间增加提高,当等待时间足够长时也可以获得处理机,克服了饥饿现象
- 非抢占式
5. 时间片轮转调度算法(RR, Round-Robin
适用于分时操作系统
- 按照各个进程到达就绪队列的顺序,轮流让各个进程执行一个时间片,如果未在一个时间片内执行完,则剥夺处理机,进程放到就绪队列队尾重新排队
- 用于进程调度,只有作业放入内存建立相应进程后,才能被分配处理机时间片
- 是抢占式的算法,由时钟装置发出时钟中断来通知CPU时间片到
- 如果时间片太大,则RR退化为FCFS算法;如果时间片太小,会导致进程切换太频繁,导致较大开销
- 不区分任务的紧急程度
- 不会导致饥饿
6. 多级队列调度算法
在系统中设置多个就绪队列,将不同类型或性质的进程固定分配到不同队列,每个队列可以实施不同的调度算法
7. 多级反馈队列调度算法

- 设置多个就绪队列,每个队列赋予不同优先级。第一级队列优先级最高,往下依次递减
- 赋予每个队列的进程运行时间片大小不相同,优先级越高的队列,每个进程的时间片就越小
- 每个队列都采用FCFS算法。新进程进入内存,首先放入第一级队列末尾,进程执行一个时间片后,如果还未完成,则插入第二级队列末尾…
- 按队列优先级调度
8. 比较

2.2.5 进程切换

2.3 同步、互斥
2.3.1 同步和互斥的基本概念
1. 临界资源
对临界资源的互斥访问,在逻辑上可以分为四个部分
- 进入区:检查是否可以进入临界区,若可进入,则设置“正在访问临界资源”的标志,(相当于上锁)阻止其他进程同时进入临界区
- 临界区:访问临界资源的代码段,又称临界段
- 退出区:解除“正在访问临界资源”的标志(相当于解锁)
- 剩余区:做其他处理
临界区是进程中访问临界资源的代码段,进入区和退出区是负责实现互斥的代码段
- 临界区是指并发进程中访问共享变量段的代码程序
- 一个系统中有5个并发进程涉及某个相同变量A,变量A的相关临界区是由5个临界区组成的
2. 同步
进程同步:同步也称作直接制约关系,是指为了完成某种任务而建立的两个或多个进程,因为在需要某些位置上协调他们的工作次序而产生的制约关系
- 必须对并发进程进行同步的原因:并发进程是异步的
3. 互斥
进程互斥:又称间接制约关系,当一个进程进入临界区使用临界资源时,另一个进程必须等待。当占用临界资源的进程退出临界区后,另一进程才可以访问该临界资源
为了实现对临界资源的互斥访问,同时保证整体性能,需要遵循以下原则
- 空闲让进:临界区空闲时,可以允许一个请求进入临界区的进程立即进入临界区
- 忙则等待:当已有进程进入临界区时,其他试图进入临界区的进程必须等待
- 有限等待:对请求访问的进程,应保证能在有限时间内进入临界区(保证不会饥饿
- 让权等待:进程不能进入临界区时,应立即释放处理机,防止进程忙等待
- (21 408) 其中1,2,3必须满足,4可以不满足(例如Peterson算法
4. 可重入函数
可以在执行函数时被os中断,去处理其他任务的函数
- 一个进程映像由程序、数据以及PCB组成,其中共享程序段必须用可重入编码编写
2.3.2 实现临界区互斥的基本方法
1. 软件实现方法
在进入区设置并检查一些标志来表明是否有进程在临界区中,若已经有,则在进入区通过循环检查进行等待,进程离开临界区后则在退出区修改标志
算法1:单标志法
思想:设置一个公共变量turn,turn==0时允许进程P0访问,turn==1允许进程P1访问…

问题:必须交替访问,如果turn==0而P0不进入临界区,那么turn将无法被修改,P1也就永远无法进行。违反了“空闲让进”的原则
算法2:双标志先检查法
思想:设置一个数组flag,标记各个进程是否想要进入临界区。每个进程在进入临界区之前,先检查当前是否有其他进程想进入临界区,如果没有,则自身标志设置为true然后开始访问


存在的问题:如果进程P1确定将访问临界区,但还没置flag,此时进程切换P2,P2也将可以访问临界区。违反了“忙则等待”的原则
算法3:双标志后检查法
思想:对算法2的改进,先上锁,再进入临界区

问题:虽然解决了“忙则等待”的问题,但可能双方都置flag,违反了“空闲让进”和“有限等待”的原则,可能会出现饥饿现象
算法4:Peterson算法
思想:为了防止两个进程为了进入临界区无限等待,设置变量turn,标识临界区是否有进程在使用。

问题:没有遵循”让权等待“的原则
2.硬件实现方法
计算机提供特殊的硬件指令,允许对一个字中的内容进行修正等。通过硬件支持实现临界段问题的方法称为低级方法,或称元方法
1. 中断屏蔽方法
思想:利用“开/关中断指令”实现,不允许中断也就意味着不能发生进程切换,也就不可能发生同时访问临界区的情况
- 优点:简单高效
- 缺点:不适用于多处理机,只适用于操作系统内核进程,不适用于用户进程
2. 硬件指令方法
TestAndSet,简称TS指令,又称TSL指令(TestAndSetLock)
TS指令用硬件实现,执行的过程不允许被中断
// lock表示临界区是否被锁
// 检查并上锁
bool TestAndSet(bool *lock){
bool old = *lock;
*lock = true;
return old;
}
// TSL实现互斥
while(TestAndSet(&lock)){...};
// 临界区
lock = false;
剩余区
Swap指令,又称Exchange指令、XCHG指令。用硬件实现,不允许被中断
Swap(bool* a, bool* b){
bool temp = *a;
*a = *b;
*b = temp;
}
// 实现互斥
// lock表示临界区是否上锁
bool old = true;
while(old) swap(&lock, &old);
// 临界区
lock = false;
// 剩余区
2.3.3 互斥锁(mutex lock
一个进程在进入临界区时获得锁(acquire),在退出临界区时释放锁(release)
每个互斥锁有一个bool变量avaiable,表明锁是否可用,进程试图获取不可用的锁时会被阻塞,直到锁被释放
acquire(){
while(!available){...}
available = false;
}
release(){
available = true;
}
互斥锁的缺点是没有遵循忙等待,浪费CPU周期
2.3.4 信号量机制
信号量只能被两个标准原语wait和signal访问,又称P操作和V操作
1. 整型信号量
用一个整数型的变量作为信号量,用来表示系统中某种资源的数量

整型信号量存在的问题:只要S<=0,就会不断测试。不满足“让权等待”的原则,会发生忙等
2. 记录型信号量
记录型信号量是不存在忙等现象的进程同步机制。除了表示资源数量的整型变量value,再增加一个进程链表L,用于链接所有等待该资源的进程

优点:遵循了“让权等待”的原则,不会出现”忙等“现象
tips:默认S为记录型信号量
- PV操作是一种低级进程通信原语
- PV操作是由两个不可被中断的过程组成的
3. 用信号量机制实现进程同步

- 需要使用某种资源时,先P一下
- 提供某种资源时,V一下
- PV操作实现进程同步,信号量的初始值由用户确定
4. 利用信号量实现进程互斥

- 对临界区的访问紧夹在PV操作之间
- 一个进程在互斥信号量mutex上执行V(mutex)操作导致唤醒另一个进程时,执行V操作后mutex的值为:小于等于0
5. 利用信号量实现前驱关系


每一对前驱关系都是一个进程同步问题(需要保证一前一后的关系,设置多个信号量
2.3.5 管程
- 信号量机制存在问题:编写程序困难,容易出错
- 需要设计一种机制,让程序员写程序无需关注复杂的PV操作
1. 管程的定义和基本特征
管程是一种特殊的软件模块(有点像类),由以下部分组成
- 局部于管程的共享数据结构说明
- 对该数据结构进行操作的一组过程(函数
- 对局部于管程的共享数据设置初始值的语句
- 管程有一个名字
管程的基本特征:
- 局部于管程的数据只可以被局部于管程的过程访问
- 一个进程只有通过调用管程内的过程才能进入管程访问共享数据
- 每次只允许一个进程在管程内执行某个内部过程(互斥
- 管程有很多“入口”,但每次只能开放其中一个入口,并且只有一个进程或线程可以进入。这种互斥特性是由编译器负责实现的
- 可以在管程中设置条件变量及等待/唤醒操作来解决同步问题
- 管程是进程同步工具,解决信号量机制大量同步操作分散的问题
- 管程每次只允许一个进程进入管程
- 管程中signal操作的作用和信号量机制中的V操作不同
- 管程是被进程调用的,管程是语法范围,无法创建和撤销
- 管程执行x.wait()时所做的工作:阻塞该进程,并将其插入x的阻塞队列中
2.3.6 经典同步问题
1. 生产者——消费者问题
系统中有一组生产者进程和一组消费者进程,生产者进程每次生产一个产品放入缓冲区,消费者进程每次从缓冲区取出一个产品使用(产品理解为一种数据
生产者和消费者共享一个初始为空,大小为n的缓冲区
只有缓冲区没有满,生产者才能把产品放入缓冲区,否则必须等待
缓冲区不空时,消费者才能从中取出产品,否则必须等待
缓冲区是临界资源,进程必须互斥访问
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);
使用产品;
}
}
- 实现互斥的P操作一定要在实现同步的P操作之后,否则会发生死锁(两个进程互相等待被对方唤醒
- V操作不会导致进程堵塞,因此可以交换顺序
复杂化:多生产者——多消费者问题
桌子上有一只盘子,每次只能向其中放入一个水果。爸爸只放苹果,妈妈只放橘子;儿子只吃橘子,女儿只吃苹果。
盘子空时,爸妈才能向盘子中放水果;盘子中有自己想吃的水果时,儿女才能从盘子中取出水果
- 互斥关系:对缓冲区(盘子)的访问必须互斥进行
- 同步关系:父亲将苹果放入盘子后,女儿才能取出苹果
- 同步关系:母亲将橘子放入盘子后,儿子才能取出橘子
semaphore apple = 0; // 苹果
semaphore orange = 0; // 橘子
semaphore plate = 1; // 盘子
dad(){
while(1){
准备一个苹果;
P(plate);
苹果放入盘子;
V(apple);
}
}
mom(){
while(1){
准备一个橘子;
P(plate);
橘子放入盘子;
V(orange);
}
}
daughter(){
while(1){
P(apple);
从盘中取出苹果;
V(plate);
吃掉苹果;
}
}
son(){
while(1){
P(orange);
从盘中取出橘子;
V(plate);
吃掉橘子;
}
}
- 实现互斥的P操作一定要在实现同步的P操作之后,否则可能引起死锁
- 九个生产者、六个消费者共享容量为8的缓冲器的生产者-消费者问题中,互斥使用缓冲器的信号量初始值为:1
2. 读者——写者问题
有读者和写者两组并发进程,共享一个文件,要求如下:
- 允许多个读者同时对文件执行读操作
- 只允许一个写者往文件中写信息
- 任意写者在完成写操作之前不允许其他读者或写者工作
- 写者执行写操作前,必须让已有的读者和写者全部退出
- 互斥关系:写进程之间互斥,读进程之间没有互斥关系
semaphore rw = 1; // 实现读写互斥
int count = 0; // 记录当前有几个读进程在访问文件
semaphore mutex = 1;// 保证对count变量的互斥访问
// semaphore w = 1; // 实现写优先
writer(){
while(1){
// P(w);
P(rw);
写文件;
V(rw);
// V(w);
}
}
reader(){
// P(w);
P(mutex);
if(count == 0) // 第一个读进程来上锁
P(rw);
++ count;
V(mutex);
// V(w);
读文件;
P(mutex);
-- count;
if(count == 0) // 最后一个读进程来解锁
V(rw);
V(mutex);
}
- 潜在问题:可能导致写进程饿死
- 解决方法:增加一个w信号量,实现写优先
- 精髓在于计数器
count, 遇到不太好解决的同步-互斥问题,需要考虑能否通过互斥访问的计数器count解决
3. 哲学家进餐问题
一张圆桌上有五名哲学家,每两个哲学家之间的桌子上摆一根筷子,桌子中间是食物
哲学家思考时,不影响其他人。哲学家饥饿时,会试图拿起左右两根筷子(一根一根拿起),如果筷子在其他人手上,则需要等待。饥饿的哲学家只有同时拿着两根筷子才可以开始进餐。进餐完毕后,放下筷子继续思考
- 五位哲学家与左右邻居对其间筷子的访问是互斥关系
- 设置信号量数组,编号0-4,左边的筷子是i,右边的筷子是(i+1)%5
- 如何预防死锁?
- 添加一些限制,比如最多只允许四个哲学家同时进餐,可以保证至少有一个哲学家可以拿到左右两只筷子
- 要求奇数编号哲学家先拿左边筷子,偶数哲学家则相反
- 仅当一个哲学家左右两边都有筷子,才允许吃饭

4. 吸烟者问题
一个系统有三个抽烟者进程和一个供应者进程
每个抽烟者需要用三种材料卷烟并抽掉。第一个拥有烟草,第二个拥有纸,第三个拥有胶水
供应者无限地供应三种材料,每次将两种材料放在桌子上,拥有第三种材料的抽烟者就会使用它们卷烟并抽掉,然后给供应者一个信号告诉完成了,供应者就会放另外两种材料,这个过程一直重复(让三个抽烟者轮流抽烟
- 这道题本质上也是消费者——生产者问题
- 互斥关系:桌子抽象为缓冲区,容量为1(所以可以不设置互斥量
- 同步关系:三种组合才能发生对应的抽烟
- 同步关系:抽烟者发出完成信号->供应者将下一个组合放到桌子上
semaphore offer1 = 0;
semaphore offer2 = 0;
semaphore offer3 = 0;
semaphore finish = 0;
int i = 0; // 用于实现三个抽烟者轮流抽烟
provider(){
while(1){
if(i == 0){
组合1放在桌子上;
V(offer1);
}else if(i == 1){
组合2放在桌子上;
V(offer2);
}else{
组合3放在桌子上
V(offer3);
}
i = (i+1)%3;
P(finish);
}
}
smoker1(){
while(1){
P(offer1);
拿走组合1,卷烟抽掉;
V(finish);
}
}
smoker2(){
while(1){
P(offer2);
拿走组合2,卷烟抽掉;
V(finish);
}
}
smoker3(){
while(1){
P(offer3);
拿走组合3,卷烟抽掉;
V(finish);
}
}
2.4 死锁
2.4.1 死锁的概念
1. 死锁的定义
在并发环境下,各进程竞争资源导致的一种互相等待对方手中的资源,导致各个进程都被阻塞,都无法向前推进的现象 。如果没有外力推动,进程将无法向前推进
例如:某计算机系统只有一台打印机和一个输入设备,P1占有打印机,同时请求输入设备;P2占有输入设备,同时请求打印机。此时两个进程陷入死锁
2. 死锁产生的原因
系统资源的竞争
系统中不可剥夺资源的数量不能满足多个进程运行的需要
进程推进顺序非法
进程在运行过程中,请求和释放资源的顺序不当;信号量使用不当也会产生死锁
死锁产生的必要条件
只要以下任意一个条件不满足,死锁就不会发生
- 互斥条件:进程要求对所分配的资源进行排他性使用
- 不剥夺条件:进程所获得的资源在未使用完之前,不能被其他进程强行夺走,只能自己主动释放
- 请求并保持条件:进程已经保持了至少一个资源,提出新的资源请求,而该资源被其他进程占有,此时进程被阻塞,但对自己获得的资源保持不放
- 循环等待条件:存在一种进程资源的循环等待链,链中每个进程已获得资源同时被链中的下一个进程所请求
3. 死锁的处理策略
为了系统不发生死锁,必须设法破坏四个必要条件之一
- 预防死锁,破坏四个产生条件的一个或几个
- 避免死锁,用某种方法防止系统进入不安全状态(银行家算法
- 死锁的检测和解除,允许死锁发生,但操作系统负责监测处死锁的发生,然后解除死锁

2.4.2 死锁预防
破坏导致死锁的必要条件
1. 破坏互斥条件
- 将资源改造为允许共享使用。例如SPOOLing技术
- 缺点:很多时候系统必须保护资源的互斥性
2. 破坏不剥夺条件
- 当某个进程请求新资源无法满足时,必须释放保持的所有资源,需要时再重新申请
- 可以由操作系统协助,强行剥夺。这种方式一般需要考虑进程优先级
- 缺点:实现起来复杂;释放资源可能造成前一阶段工作失效;反复申请释放资源会增加系统开销;可能导致饥饿
3. 破坏请求和保持条件
- 进程在运行前一次性申请完他所需要的所有资源。在资源未满足不会投入运行
- 缺点:资源利用率低;可能导致饥饿
4. 破坏循环等待条件
- 采用顺序资源分配法。给系统中的资源编号,规定每个进程必须按照编号递增的顺序请求资源
- 缺点:不方便增加新设备;进程使用资源的顺序可能不是递增,导致资源浪费;用户编程麻烦
2.4.3 避免死锁
避免死锁属于事先预防策略
1. 系统安全状态
安全序列:如果系统按照这种序列分配资源,可以满足每个进程对资源的最大需求,则每个进程都可以顺利完成
只要找出一个安全序列,系统就是安全状态。安全序列可能会有多个
系统处于安全状态时,一定不会发生死锁;进入不安全状态,有可能发生死锁;发生了死锁一定在不安全状态
2. 银行家算法
银行家算法核心思想:在进程提出资源申请时,先预判这次分配是否会导致系统进入不安全状态,如果会,则暂时阻塞该进程
2.4.4 死锁的检测和解除
死锁的检测(死锁定理
- 用某种数据结构保存资源的请求和分配信息
- 进程节点、资源节点:圆圈表示进程,矩形框表示资源
- 请求边、分配边
- 提供算法检测是否进入死锁


解除死锁
- 资源剥夺法:挂起某些死锁进程,抢占资源
- 撤销进程法:强制撤销部分死锁进程
- 进程回退法:需要系统记录进程进程的历史信息,设置还原点
1. **若系统中有n个进程,每个进程需要使用某种临界资源m个,则不会发生死锁的该类资源的总数至少为:m+(n-1)(m-1)**
2. 区分死锁预防和死锁避免
3. 产生死锁的根本原因在于系统资源分配不足和进程推进顺序非法
408真题
(17 408)
-
互斥关系的判断:共享变量、一读一写、都写

(19 408) -
哲学家问题改进


被折叠的 条评论
为什么被折叠?



