本文内容来自我自己的笔记软件,所有跳转链接都为无效链接 (懒得调)
笔记内容来源与考研书籍和自己写过的一些简单项目结合
如若有疑问或者想询问可以+Q1403145273
进程与线程
基本概念
进程
进程产生的目的 :: 更好的实现最基本特征,提高系统吞吐率
进程的最大数目取决于内存大小
进程的数量不影响 CPU利用率,但是会影响进程的等待时间
进程控制块(进程存在的唯一标志)
作用 :: 使进程并发时独立运行
- 进程描述信息 :: PID(进程标识符) UID(用户标识符)
- 进程控制和管理信息
- 资源分配清单
- 处理机相关信息
进程映像(由程序段,相关数据段,PCB构成)
程序段 :: 代码段(存放指令和可执行机器码),数据段(存放全局初始化(静态)变量),BSS段(存放全局未初始化(静态)变量),只读数据段(存放常量,整数常量直接嵌入代码段)
相关数据段 :: 数据栈(存放局部变量),数据堆(存放动态动态分配数据)
可重入编码^(一种允许多进程同时访问的代码. 不允许进行修改)^属于(共享)程序段
进程是动态的,进程实体是静态的
特征 :: 动态性,并发性,独立性(能够独立获取资源和接受调度(与线程区分)),异步性(会导致结果不可再现性,所以需要进程同步机制)
-
进程的状态 :: 创建态(为进程申请PCB),就绪态(等待获取CPU使用权限,已获得除CPU外的一切资源),运行态(进程正在CPU上运行),阻塞态(CPU繁忙,即忙着处理计算或者IO任务没办法响应进程),挂起态(就绪挂起,阻塞挂起),结束态(等待回收PCB)
就绪,运行,阻塞 是基本状态
阻塞态 需要的执行需要 程序中断方式
运行-> 阻塞(主动)
-
进程控制
目的 :: 实现进程状态转换
一般把进程控制用的程序段称为 原语
使用创建原语(申请空白PCB,为其分配所需资源,初始化PCB,将其插入就绪队列)的事件 :: 用户登录,作业调度,提供服务,应用申请
使用撤销原语(找到相应PCB,若在运行则剥夺CPU,终止子进程,将进程资源归还,删除PCB)的事件 :: 正常结束,异常结束,外界干预
使用阻塞原语(找到相应PCB,保护运行现场,将PCB设置阻塞态,停止进程运行,将PCB插入阻塞队列)的事件 :: 等待资源,等待其他进程
将运行环境存入 PCB,并移入就绪队列,选择另个进程执行,并更新 PCB,恢复运行所需环境
找到相应 PCB,将 PCB 从阻塞队列移除,设置为就绪态,插入就绪队列,等待调度
进程通信
-
共享存储(通信进程之间存在一块可直接访问的共享空间)
需要同步互斥工具
- 低级通信方式(基于数据结构的共享) 只能存放固定的数据结构,速度慢,限制多
- 高级通信方式(基于存储区的共享) 在内存划分共享存储区,不限制数据结构和存放位置,速度快,限制少
-
消息传递^(进程间直接交换数据,数据以 格式化的消息 为单位交换)^
通过发送/接受原语进行数据交换
格式(消息头,消息体)
当前最广泛的进程间通信方式,网络的主要通信工具(报文)
- 直接通信方式(直接将消息交给进程(将消息放置接收进程的消息队列))
- 间接通信方式(消息发送给中间实体(信箱),网络中常用)
-
管道通信(一个特殊的共享文件)
在内存中开辟一个固定大小的缓冲区(与内存页大小相同)
采用半双工通信,进程访问管道需要互斥访问
没写满不允许读,没读完不允许写
只能读一次,读出后管道抛弃数据
IPC问题
实际通信方式
-
管道(半双工的通信方式,分为匿名管道和命名管道)
-
信号(异步的通信方式,用于通知接收进程发生了某种事件)
-
共享内存(进程间数据共享的方式,允许多个进程访问同一块内存区域)
-
消息队列(消息传递机制,消息队列存放在内核中,允许一个进程向另一个进程发送消息)
-
信号量(进程间同步的机制,用于协调多个进程对共享资源的访问)
-
套接字(进程间网络通信的机制,可以实现不同主机上的进程间通信)
线程
产生的目的 :: 减少并发开销,提高系统并发性能
是 CPU 的基本执行单元(进程不是(非常容易弄混)),是系统调度和分派的基本单位
当 CPU 需要切换线程时会判断下一个线程是属于哪个进程,如果是同一个进程则不需要切换进程状态
如果不是同一进程则切换,说明了从逻辑上进程下的多个线程可分配给多个 CPU 执行
只是某些语言如 Java,python 的线程库(未优化)只允许在只允许在一个 CPU 上跑
线程没有独立的地址空间,共享进程的地址空间
进程与线程的根本区别是动态和静态特点 #存疑#
线程 是一种 “轻量级进程”,进程 不是 程序
多线程模型
-
用户级线程
线程管理(创建,撤销,切换)由用户管理,本质上还是顺序执行,程序查询方式 会出现踏步
-
不用切换到内核,节省切换开销
-
调度算法的选择灵活
-
线程的实现与操作系统无关
-
会导致所有线程库下的所有线程阻塞
-
多个线程只能分配给单个 CPU
这个就是人们常说的协程,就是在代码上逻辑实现多线程
适用于高并发的计算型任务,速度非常快,有流水线的影子. 但是如果在里面涉及到 IO 等资源操作就会非常糟糕
-
-
内核级线程
在多核上可非顺序执行,程序中断方式
-
线程之间并行执行
-
单个线程的阻塞不影响其他线程的执行
-
线程切换快,开销较小
实际上因为((20220918165737-6ylxk3d “切换状态”))系统开销很大
-
内核本身也采取多线程技术
提高系统执行速度和效率
-
在同一进程下切换线程需要切换状态
-
-
多对多模型
将 m 个用户线程 通过线程库 映射到 n 个内核线程(n >= m)
相当于用调度器管理内核线程,然后使用非对称协程
特点 :: 克服了一对一模型并发度不高,用户线程 多时开销大的缺点
本质上就是在玩十个瓶子九个盖的游戏,只要不是同一时刻都需要盖,那么游戏就可以玩下去
处理机调度
调度的目的 :: 在有限资源下,以某种算法高效的执行
-
作业调度(调入调出都只有一次)为作业分配资源
相当于创建态,结束态,要注意分配完资源后就加入就绪态了
调度频率最低
-
内存调度(将进程调入/调出内存至外存)
目的 :: 提高内存利用率和系统吞吐率
相当于挂起态,调度频率适中
-
进程调度(在就绪队列选择一个进程给CPU)
最基本的调度,调度频率很高
就绪-运行-阻塞
调度的实现
-
调度的时机,切换与过程
调度程序^(由排队器,分派器,上下文切换器 三部分组成)^是内核程序
不能调度的情况(正常情况下)
-
处理中断的过程
要区分处理 中断过程 和 执行中断程序的区别哦
-
进入内核临界区(特指前后期处理中断)
用户临界区是可以调度的,也就是逻辑上的临界区,不同进程不一样
错误理解
-
内核临界区出现调度的情况 :: 故障
因为异常不能屏蔽的特点,在通过互斥锁和信号量检查到临界资源不足时
就会抛出异常,将当前进程的PCB加入到阻塞队列且调度就绪队列
等到处理机唤醒进程后重新执行一次临界区代码
-
内核级线程
-
进程 A 下有三个线程(a,b,c) 由于共享临界区代码(因为是同一进程)
当 a 通过进入区上锁后,b,c 在进入区就会被拦截
线程库 将 线程 b,c 进入 逻辑阻塞态,线程库不分配时间片给它们
当 a 进入临界区后,通过系统调用陷入到核心态,CPU 响应中断请求保存当前线程(内核)的现场后开始执行中断程序(内核临界区)进行申请资源,完成申请操作后恢复当前线程(内核)现场退出内核,在用户态临界区上等待处理结果信息返回
整个等待过程(时间片)进程不会进入阻塞态(实际),在设备完成任务返回处理结果后向处理机发送中断请求,等待进程重新拥有处理机资源后线程 a 再次陷入核心态,经过中断隐指令保存现场 获取到响应 并恢复线程(内核)现场后,返回用户临界区,线程 a 拿到响应后进入退出区
此时线程库 唤醒处于 逻辑(因为同进程下的线程切换不会经过内核)阻塞态的线程(b,c),此时它们可以开始争夺临界资源
-
进程 A 下有三个线程(a,b,c) 和 进程 B(d)
当 d 通过进入区(进程 B)上锁后,进程 A(a,b,c) 不受影响,a 也通过进入区(进程 A)上锁,(b,c) 在进入区被拦截
当 d 进入临界区后,a 在通过临界区(用户)陷入内核态,通过中断程序准备访问临界区(内核)时,就会被拦截在进入区(内核),此时执行阻塞原语,进程 A 所拥有的内核线程进入阻塞,此时进程 A 失去了使用处理机的能力,它的 PCB 被更改为阻塞态加入阻塞队列
线程 d 在完成上述工作后,在 d 退出区(内核)就会向需要该临界资源的进程发起唤醒原语,进程 A 重新加入就绪态,然后以此类推的完成工作
要注意操作系统已经对真实存在的资源进行虚拟化了
所以进程AB,都拥有逻辑上完整且互不干扰的资源
协程也有临界区 和 中断 等概念对于的操作,所以 内核级线程同样可以解释协程的流程
-
-
-
原子操作
-
-
进程调度方式
-
非抢占式调度方式
实现简单,适合大多数批处理程序
除非主动放弃,否则得等到时间片用完
-
抢占式调度方式
提高系统吞吐率和响应效率提升
直接抢夺处理机资源,就算正在使用
-
-
进程切换与过程
-
进程切换(一个进程让出处理机给另一个进程的过程)
它完成了进程现场的保护和恢复过程
过于频繁的切换会导致系统效率降低
狭义 :: 从就绪队列选择一个要运行的进程
广义 :: 选择一个进程和进程切换两个步骤
-
调度的性能指标
-
CPU 利用率 :: (CPU 有效工作时间)/CPU 总时间
-
系统吞吐率 :: 单位时间完成作业的数量
-
周转时间(实际完成所需时间) :: 作业完成时间 - 作业提交时间
- 平均周转时间 = 每个作业的周转时间相加 / n
- 带权周转时间 = 周转时间 / 运行时间
- 平均带权周转时间 = 所有作业带权周转时间 / n
-
运行时间 :: 理论完成所需时间
-
等待时间 :: 等待处理机的时间(实际完成所需时间-理论完成所需时间)
-
响应时间 :: 递交请求到系统首次响应所用的时间(作业开始工作时间 - 作业提交时间)
调度算法
-
先来先服务调度算法
非抢占式调度方式,长作业有利,短作业不利. 不会产生进程饥饿
实现简单,公平
短作业带权周转时间很大
-
短作业(进程)优先调度算法
(默认)非抢占式调度方式(也有抢占式的)
平均等待时间,平均周转时间最短 (排除本身的抢占式版本抢占式版本)
长作业会产生进程饥饿甚至进程饿死
由于运行时间由用户提供,不一定真实
-
优先级调度算法
-
优先级原则
系统 > 用户
前台 > 后台(交互 > 非交互)
IO > 计算
静态优先级(创建进程时确定)
动态优先级 :: 高响应比优先调度算法
适用于实时操作系统,拥有优先级
可能会导致低优先级进程饥饿
-
-
高响应比优先调度算法
响应比 = (等待时间 + 要求服务时间) / (要求服务时间)
非抢占式调度方式
-
时间片轮转调度算法
用于进程调度,抢占式调度方式,需要时钟中断
在交替中插入的新时间片排在队首,可以主动放弃处理机
时间片过长会退化为 FCFS,不会出现进程饥饿
响应快,公平,适用于分时操作系统
高频率切换开销大,没有优先级
-
多级队列调度算法
-
多级反馈队列调度算法
默认为抢占式,用于进程调度
队列的优先级由高到底(时间片由短变长),执行完时间片后会把进程放入下一级队列(直到最末尾队列)
若发生抢占,被抢占的进程返回原队列队尾(没有到下一队列)可能会导致进程饥饿
- 进程饥饿^(作业/进程 长期得不到处理机)^
- 进程饿死^(作业/进程 无法得到处理机)^
同步与互斥
-
基本概念
临界资源 :: 一次只能允许一个进程使用(互斥共享)
-
临界区(访问临界资源的代码)
在 n 个线程访问临界资源A时,有关A的临界资源的临界区 有 n 段(每个线程都处于不同进程)
-
访问临界资源的步骤 :: 进入区(上锁),临界区,退出区(解锁),剩余区
同步((合作关系)为完成某种任务建立的多个进程需要协调工作次序的关系)
互斥((竞争关系)进程之间争夺临界资源)
- 互斥机制原则 :: 空闲让进,忙则等待(不能进入(当前)临界区则等待),有限等待,让权等待(不能进入临界区就立刻释放处理机,比如说在while循环空转)
-
-
实现临界区互斥
-
软件(处于用户态(现代编程中已经不使用这些方式实现互斥))
-
单标志法(只用一个变量作为进入区的锁)
变量的值表示允许进入临界区的线程编号
由于"非黑即白"的情况导致,如果变量设置的线程不使用临界资源
其他线程无法使用,违反空闲让进 -
双标志先检查法(用bool数组记录线程是否访问临界资源)
用一个数组记录所有线程的状态,当前线程检查 bool 数组中自己以外的所有状态
当发现可用时,将自己的状态设置为 true由于处于用户态,系统能够在进入区后进行进程切换导致同时进入临界区
违反忙则等待 -
双标志后检查法(先锁自己的状态,然后才去检查)
由于处于用户态,系统能够在进入区后可能出现同时设置锁的情况
导致出现线程饥饿甚至死锁,违反空闲让进,有限等待 -
Peterson 算法^(使用 bool 数组和一个变量)^
数组记录所有线程的状态,变量指向其他线程
设置自己的 bool 为 true,然后变量指向其他线程
进入区能进入的条件是其他人不争夺 或 变量指向自己
没遵循让权等待(错误理解)
- 不仅仅是在用户态不能遵循,核心态也不能实现
- 内核临界区(中断程序)不能进行状态转换
- 在用户态不能实现的原因是按照错误理解只能在同一进程下的线程才能实现让权
-
-
硬件(内核态)
-
中断屏蔽方法(关中断和开中断)
简单高效
只能适用于单处理机,将中断指令的权力交给用户可能会导致系统终止(执行了关中断后不执行开中断)
-
硬件指令方法 :: TestAndSet指令,Swap指令
适用于多处理机,但是不遵循让权等待
-
-
互斥锁
加锁失败会导致线程 从运行态 -> 阻塞态
通常使用硬件机制实现,适合多处理机,适合访问低速临界资源
在单处理机上进行多次进程切换,导致开销变高
自旋锁
-
信号量
-
整型信号量
用整型表示信号量,数值代表资源数
不满足让权等待
-
记录型信号量
用整型和队列分别记录资源数和等待资源的队列
完全遵守互斥机制原则
在通过信号量判断是否阻塞时要注意题目中 说的是 P 操作 前还是后
PV操作 不是 系统调用,是低级进程通信原语,有两个不可被中断的过程组成
-
-
管程
用一个类来管理一种临界资源及其所有操作,并保证同一时刻只能有一个线程操作
无论是阻塞还是分配资源还是保证互斥都只能在管程这个类里面实现
- 名称 -
极容易忽略 - 管理的(共享)资源的数据结构
- 对数据结构的操作过程(函数)
- 共享数据的初始化(构造函数)
- 名称 -
-
-
经典同步问题
-
生产者-消费者问题
先判断容器内是否为空(或满),然后再拿容器,往里面放入数据(或取出)
然后释放容器,释放判空(满)
-
读者-写者问题
-
读者优先(写锁只有一个)
-
读进程
先拿读锁,判断是否是第一个读(如果是则拿写锁),读者数量+1,
释放读锁,开始读,拿读锁,读者-1,
判断是否是最后一个读(如果是则释放写锁),释放读锁
-
写进程
先拿写锁,写入,释放写锁
-
-
写者优先(写锁有两个)
-
读进程
先拿写锁1,再拿读锁,判断是否是第一个读(如果是则拿写锁2),读者数量+1
释放读锁,释放写锁1,开始读,拿读锁,读者-1
判断是否是最后一个读(如果是则释放写锁2),释放读锁
-
写进程
拿写锁1,拿写锁2,写入,释放写锁2,释放写锁1
-
-
-
哲学家进餐问题
进程需要多种临界资源的问题
将取多次临界资源看作取一次即可,同一时刻只允许取一次
拿取资源锁,拿所需要资源的锁,释放取资源锁,使用资源,释放拥有的资源
存在死锁问题
-
吸烟者问题
供给者轮询的获得消费者的需求并为其生产后释放对于的锁,然后拿生产锁(供给者不释放生产锁)
消费者拿消耗锁(消费者不释放消耗锁),使用资源,释放生产锁
有点WEB中的请求响应模式,对于服务器来说,没有请求的时候就发送响应
客户不发送请求就没有响应
-
死锁(多个进程因竞争资源而造成一种僵局(互相等待),若无外力作用,都无法向前推进)
产生的原因{对不可剥夺资源的竞争,进程推进顺序非法}
概念 | 区别 |
---|---|
死锁 | 至少两个进程同时发生死锁,处于阻塞态 |
饥饿 | 至少一个进程,可能处于就绪态和阻塞态 |
死循环 | 不是管理者的问题,是程序问题 |
死锁产生的必要条件 :: 互斥条件(资源的使用具有排他性一段时间内只能为一个进程服务,其他进程只能等扬声器就没有排他性),不剥夺条件(资源没使用完不能被其他进程剥夺),请求并保持条件(持有至少一种资源且请求新的资源并被阻塞不释放已有资源),循环等待
死锁处理策略
-
死锁预防
破坏死锁的4个必要条件之一
- 破坏互斥条件: 有局限性,有些资源只能同时访问
- 破坏不剥夺条件: 可能造成进程饥饿,且可能造成之前阶段的工作失效
- 破坏请求并保持条件: 运行前一次性把所需的资源分配,可能导致进程饥饿,资源利用率低
- 破坏循环等待条件: 设置资源申请顺序,可能导致资源利用率低,程序编写困难
-
死锁避免
- Need = MAX - Allocation
- request + allocation 是否 大于 MAX
- 大于则报错
- 小于就判断分配后 的 资源 是否大于 各进程的所需资源
- 大于就表示可以分配,小于就不能分配
-
死锁的检测及解除
- 死锁的解除方法:: 资源剥夺(挂起某个进程,剥夺其资源.可能导致饥饿),撤销进程(可能导致之前的工作失效),进程回退(为进程设置快照,阻塞时让进程回退释放资源)