- 2021-12-11 非原创,但是也算原创,除了图,大部分是自己的思路与整理,因此,禁止转载,图来源于网络
复习
- 进程管理
- 进程和线程
- 内存管理
- 文件管理
临时
- 时间片是以进程为单位分配
—
操作系统
- 批处理操作系统
- 交互式操作系统
- 分时操作系统
硬件结构
-
-
冯诺依曼模型定义了5个基本结构
- 中央处理器(CPU)
- 既 32位,64位cpu,
- 32位,64位的区别在于32 位cpu一次可以计算4个字节,64位则是一次可以计算8个字节
- 32位,64位指的是cpu的位宽
- cpu内部还有寄存器的概念
- 通用寄存器: 用于存放需要进行运算的数据,如 加法中的左右2个数,也可以认为是数据寄存器
- 程序计数器: 用于存储cpu 要执行下一条指令的内存地址,注意,是地址不是指令
- 指令寄存器: 用于存储指令本身(既程序计数器所指向的地址内容)
- 内存
- 输入/输出设备
- 输入设备向计算机输入数据,计算机计算过后将数据输出给输出设备
- 总线
- 可以认为是组件之间通信的方式,可分为3种
- 地址总线: 用于指定cpu要操作的内存地址
- 数据总线: 用于读写内存中的数据
- 控制总线: 用于发送和接收信号(比如中断和复位)
- 当cpu读写内存时的逻辑为:首先通过地址总线来指定内存的地址,再通过数据总线来传输数据
- 可以认为是组件之间通信的方式,可分为3种
- 中央处理器(CPU)
-
CPU执行程序的过程
- cpu先读取程序计数器的值,这个值是指令的内存地址,然后cpu 的控制单元通过地址总线 访问内存地址所指向的具体内容,数据准备好后,通过数据总线将数据传输给cpu,将这个指令数据存储到 指令寄存器中
- cpu分析指令寄存器中的指令,根据指令的类型,交给不同的逻辑控制单元,如运算的则交给逻辑运算单元,如果是存储类型,则交给控制单元
- cpu执行完一个指令之后,程序计数器自增指向下一个指令地址,自增的大小由下一个指令的大小所决定(而指令的大小是由cpu位宽所决定的,cpu位数所决定,如32位,则4个字节需要4个内存地址存放)
- 总结来说就是: cpu 先从程序计数器中拿到指令地址后,通过地址总线拿到具体操作,结束之后,程序计数器根据指令大小自增,这个循环叫做 CPU的指令周期
-
指令的执行速度
- 关键字: GHz
- GHz:指的是时钟频率,1GHz代表的是1秒会产生1G次数的脉冲信号,每一次脉冲信号都会触发 0,1转换,这称之为时钟周期
- 一条指令的执行,往往需要多个时钟周期才可以执行完毕
-
程序cpu的执行时间=cpu时钟周期数*时钟周期时间
- 时钟周期时间就是cpu主频,如 2.4GHz ,时钟周期时间就是1/2.4G
- 时钟周期数= 指令数*每条指令的平均需要的周期数
- 所以程序的执行时间可以认为是 指令数x每条指令的平均周期数x时钟周期时间
- 指令数:表示程序执行需要多少条指令,以及哪些指令,基本取决于编译器
- 每条指令的平均周期数
- 时钟周期时间: 既计算机主频,取决于计算机硬件,越高跑的越快,但是散热压力也就越大
-
总结:
- 64位相比32位的优势在于
- 64位一次可以计算超过32位的数字,而32位不行,所以大数方面上有优势
- 内存寻址方面: 32位最大的寻址地址是4G,而64位理论上2^64 ,足够大了
- 硬件的64位,32位指的是cpu的位宽,一次可以计算的大小,而软件的32位,64位指的是指令的位宽,32位的软件在64位上,兼容下是可以运行的,但是64位是无法运行在32位上的,因为32位的指令寄存器存放不了64位的指令
- 64位相比32位的优势在于
CPU的缓存一致性
-
直接映射
- 缓存数据是通过直接映射的方式,存储在内存中的
- 读取数据的时候,是按块读取的,也叫做内存块,每次读取都需要拿到内存块的地址,而直接映射就是 将 一个内存块的地址永久映射到在一个Cache Line 上,具体映射则是采用取模的形式
- 如若内存中有32个内存块,CPU Cache总共有8个cache line的话,则 15号内存中的数据如果在缓存中,则必定在7号cpu line中
- 每个Cache line所持有的信息
- Data: 既存储的数据
- 组标记Tag: 用于标记对应的内存块,因为如果是基于取模算法的话,15,23都是在7号cache line 中,tag 就能标明是哪个blockNumber(这个与hashMap原理是一样的)
- 有效位: 涉及到缓存一致性,如果无效,则无论是否在缓存中有数据,都会直接到内存中重新读取加载
- 一个内存地址所持有的信息
- Tag: 组标记: 用于表明数据在哪个block
- Index: 用于表明在哪个cache line 中
- offset: 因为cache line 中缓存的数据是不同的数据,而内存地址往往只是一个片段,offset 用于找到这个片段
-
数据读取过程,如果数据在Cpu Cache中的话
-
- 根据内存地址计算得到 index 所对应的cacheLine
- 找到cache line 之后,判断有效位是否有效,无效的话,从内存中重新读取加载
- cache line 中的 组标记tag 和内存地址的组标记tag 比较,不匹配则会从内存中重新读取加载
- 根据内存地址的偏移量获取得到数据
-
-
缓存分为3级缓存
- L1 Cache
- L2 Cache
- L3 Cache: 这个缓存为不同的cpu所共享
-
缓存一致性的方案
- 写直达
- 既将数据写入到缓存的同时,直接写到内存中 ,对性能影响较高
- 写回
- 关键是: 减少写入内存的次数
- 既: 当发生写操作,数据已经在cpu cache中的话,则直接写到cache,然后标记为脏的,然后下次写的时候,发现是脏的,就会先将数据写到内存中,再写到这个cache block中,再标记为脏的
- 缓存一致性
- 基于写回的特性, 只有写回的时候才会更新到内存,所以会有缓存一致性问题
- 解决方法
- 写传播: 当某个cpu更新数据时,必须传播到其他核心的cache
- 通过总线嗅探实现: 既当某个cpu更新了数据之后,发布一个事件,将该事件广播到其他核心
- 事务串行化:某个cpu对某个数据的操作,在其他核心中也是一模一样的
- 写传播: 当某个cpu更新数据时,必须传播到其他核心的cache
- MESI协议
- Modified: 已修改: 就是代表该cache line是脏的,但是还没有写到内存中
- Exclusive: 独占 : 独占和共享,都表明该cache line中的数据是一致的,区别在于独占代表数据只在一个cpu中
- Shared: 共享: 共享状态下修改数据需要先向总线嗅探广播一个事件
- Invalidated: 已失效
- 实现cpu缓存的一致性,用4个状态来标记Cache Line的不同状态
- how:
- 当对数据进行写的时候,当发现数据处于共享状态下,会向其他所有的cpu核心广播一个请求,这个请求的作用是,让各个cpu的核心把该状态的cpu设置为无效状态,然后开本身才开始更新数据,然后其他cpu读取的时候,会重新从内存中读取
- 总结
- 缓存一致需要满足如下
- 写传播
- 事务串行化
- 缓存一致需要满足如下
- 写直达
-
缓存的伪共享问题
-
2个cpu,核心1,核心2 ,读取数据A,B ,并且A,B都在同一个cache line 中,并且分别是2个线程在处理
-
则A,B都会被加载到Cache中,同时基于MESI协议, 该cache line 会被标记为共享状态(S: 从独占=>共享)
-
-
当2号CPU,尝试修改缓存时,消息总线会广播通知1号cpu 让其标记为无效状态
-
此时假设1号cpu 也尝试修改变量,则又会通知消息总线,让2号cpu 标记为无效状态
-
-
这就是伪共享
- 因为多个线程读写变量而导致缓存命中率低的现象
-
避免伪共享
-
-
关键是: 避免相关的变量在同一个cache line中: 既用空间换时间
- -
如Java中的disruptor 中的ringbuffer 中的padding 就是基于如此
中断
- what:
- 响应硬件设备的一种请求机制,会终止当前正在处理的程序,先处理中断请求
问题
-
什么是硬中断,什么是软中断
- 中断包含了硬中断和软中断,硬中断是为了快速处理,而软中断则是为了继续硬中断未完成的工,通常是耗时较长的工作,也可以认为是延迟处理
- 硬中断: 由硬件触发,快速立马处理中断事件,耗时
- 软中断: 由硬中断触发,用于处理硬中断未执行完成的任务,以内核线程的方式运行,延迟调用,耗时较长
- 区别
- 硬中断会打断cpu正在执行的任务,然后立即执行中断程序,而软中断是以内核线程的方式运行,并且每一个cpu都对应一个软中断内核线程,并且内核自定义事件也是软中断,如cpu调度,rcu锁等
-
中断会有什么问题
- 处理程序执行过长
- 因为中断是当运行其他程序时,收到中断会先处理中断,如果处理中断时间过长,会导致被中断的程序迟迟无响应
- 丢失中断
- 当中断处理,又触发了中断,则之前的中断会被丢失
- 如何解决
- linux将中断过程分为了2个阶段,分别是上半部分和下半部分
- 上半部分用来快速处理中断(硬中断): 一般是直接暂时关闭中断请求,
- **下半部分用于延迟处理上半部未完成的工作(软中断)😗*一般以内核线程的方式运行
- 如计算机中
- 当网卡收到网络数据包之后,会通过硬件中断通知内核有新的数据到来了,内核就会处理该中断事件
- 上半部: 为了快速处理: 所以会直接将数据读到内存
- 下半部分: 触发软中断,从内存中找到数据,然后进行逐层解析处理
- 当网卡收到网络数据包之后,会通过硬件中断通知内核有新的数据到来了,内核就会处理该中断事件
- 处理程序执行过长
内存管理
虚拟内存
- what: 操作系统会为每个进程分配一套独立的虚拟地址
虚拟地址和物理地址的映射
- 两种技术:
- 内存分段
- 内存分页
内存分段
-
分段机制下,程序被分为若干个逻辑分段,如代码段,栈段,堆段等
-
分段机制下,虚拟地址由2部分组成: 段选择子和段内偏移量
- 段选择因子:最重要的是段号,用作段表的索引
- 段内偏移量: 用于组合拼装,从而得到地址
-
-
原理:
- 分段机制下,通过段表将虚拟地址和物理地址映射起来
- 计算方式:
-
- 找到虚拟地址的段选择因子中的段号 ,然后再找到虚拟地址中的段偏移量
- 通过段号到段表中得到对应的段行,得到物理地址的基地址
- 通过基地址+段偏移量,可以得到该虚拟地址对应的物理地址
-
-
不足
-
内存碎片问题
-
如:
-
1G的物理内存,3个程序
- 游戏占用512M ,浏览器占用 128M,音乐占用256M,若关闭了浏览器,此时有 1024m-512-256=256M,若该256M内存不是连续的,则会被分为128M的2段,此时就会出现无法分配给一个200M的程序
-
所以其实能发现,内存段的大小,影响因素也很大
-
以及所有程序都加载到内存中会触发内存不足
-
-
-
内存交换效率低
- 因为内存空间不足时,会触发内存和磁盘的swap,这是一个效率很低的操作,所以如果内存交换的时候,是一个很大的内存,会造成系统卡顿
-
内存分页
-
将物理内存和虚拟内存切割分成一段段固定大小的页面,每个页面为4KB,虚拟地址和物理地址通过页表来实现转换
-
-
内存分页解决的问题
-
内存碎片问题:
- how: 因为虚拟内存和物理内存都被以固定大小的切割成了4kb的页面,内存释放的时候都是以页为单位释放的,因此释放的时候也就不会产生空隙,所以不会有碎片问题
-
内存交换效率低问题:
- 当内存空间不足时,操作系统会将其他正在运行的进程中的 最近未被使用的页面,给释放掉,既暂时写到swap磁盘中,(也称为换出),当需要的时候,再加载进来,并且因为是4kb大小,每次换入换出都是几个页面,因此效率很快
- how: 也是因为内存固定的被切分成4kb的页面的缘故,4kb远小于mb
-
并且程序可以在真正要使用的时候才去从虚拟内存中加载数据和指令到物理内存中
-
-
分页机制下的物理地址寻址
-
-
分页机制下,虚拟地址包含了 页号和页内偏移量,可以认为与分段机制类似: 段号和偏移量
- 流程
-
- 通过虚拟地址得到 页号和偏移量
- 根据页号,到自己进程的页表中找到对应的页表项,从而拿到物理页号
- 最后根据偏移量,找到对应的页内数据(既物理地址)
-
- 流程
-
-
内存分页存在的问题
-
空间上的缺陷
- 因为虚拟地址要对应实际的物理地址,因此页表必须包括所有的物理地址
- 32位环境下,虚拟空间内存为4G,一个页的大小为4kb,因此有100w个页,每个页地址用页表项存储,一个页表项4个字节,因此一个进程就有4MB,而100个进程就有400MB
-
解决方法:
-
多级页表
-
可以认为是一个hashMap
-
-
既将所有的页表项进行划分等级,某些页表项在一级,某些在二级,某些在三级
- 将1级页表有1024个页表项,同时又有1024个二级页表,每个二级页表又有1024个3级页表,这就是多级页表,物理页号存储在最后一级页表中
-
虽然页表更多了,但是内存并不会负载更高,因为 操作系统的局部性原理
-
操作系统并不会把4gb的所有虚拟地址都映射到内存中,因为大多数程序都不会使用4GB的虚拟内存空间,(所以其实虚拟内存设置的很大没啥太大影响),并且当物理内存紧张时,还会将部分页面置换出去
-
如果某个页表项的页面并没有被使用,则不会创建这个页表项对应的二级页表,所以是按需创建
-
如
-
假设某个一级页表,只有20%的页表项被使用,则 内存空间占比为
4kb(一级页表)+ 0.2*4MB(二级页表:4KB x 1024个页表)= 0.8MB 远比4MB 小
-
-
-
-
总结
- 多级页表的关键是
- 不加载所有的页表项到一个页表中,而是按需创建1024 * n个页表
- 多级页表的关键是
-
-
-
-
TLB:页表缓存
- 多级页表还是存在问题: 因为依赖树的衍生,所以定位到数据需要经过多级页表到最后一个页表才能定位到物理地址
- 作用: 用于存储最近经常访问的页表项
- 所以cpu在寻址时,先查找TLB,如果没找到,才会继续找常规的页表
段页式内存管理
- 先将程序分为多个有逻辑意义的段,然后再在每个段上进行内存分页
- 相关术语
- 逻辑地址: 程序所使用的内存地址,称为逻辑地址
- 线性地址: 通过段式内存管理映射的地址是线性地址,也叫虚拟地址
- 经过 段式内存管理 将 逻辑地址转为 线性地址 => 线性地址通过页式内存管理 转为物理地址
问题
-
linux 的内存是如何分布的
- 虚拟地址空间分为 内核空间和用户空间,不同位数的系统,地址空间范围也不同
-
-
32位下,1G的内核空间,3G的用户空间
-
64位下则都为128T的空间
-
- 内核空间和用户空间的区别
- 进程在用户态时,只能访问用户空间
- 只有进入内核态,才能访问内核空间
- 虚拟地址空间分为 内核空间和用户空间,不同位数的系统,地址空间范围也不同
-
虚拟内存中存的是什么东西,物理内存呢
-
内存分段和内存分页的好处和坏处
-
程序全部都装到内存中,如何避免内存不足
- 通过内存交换技术
- 既将某个程序不用的内存,先交换到磁盘中,这样整个区域都会被释放处于可用状态,然后重新读取的时候,重新申请内存空间,操作系统上的Swap空间,就是用于内存和硬盘的交换
- 通过内存交换技术
-
虚拟地址,物理地址存储的都是啥
- 数据(包含指令)
-
虚拟地址与物理地址的区别
- 程序中所使用的内存地址为虚拟地址
- 实际存在硬件空间的地址为物理地址
- 虚拟地址通过cpu中的内存管理单元(MMU),将虚拟地址转换为物理地址
进程和线程
进程
- what:
- 进程可以认为是一个程序执行的实例
- when:
- 进程的创建主要通过4种方式创建:
- 系统初始化
- 正在运行的程序执行了 创建进程的系统调用
- 用户请求创建一个新进程
- 一个批处理作业的初始化
- 进程的创建主要通过4种方式创建:
进程的终止
- when:
- 正常退出
- 出错退出
- 严重退出
- 被其他进程杀死
进程的状态
- 运行态: 该进程正占用着cpu
- 就绪态:(可运,但因为其他进程获取着cpu而暂时停止)
- 阻塞态:除非某种外部事件发生,否则进程不能运行)
即是cpu空闲也无法运行
- 挂起状态:
- 如果有大量处于阻塞状态的进程时,操作系统会将阻塞状态的进程的内存空间置换到磁盘中,等需要时,再次加载,此时进程是处于挂起状态,与阻塞状态是不同的,阻塞是等待某个事件返回,而挂起则可能是因为
- 通过sleep 让进程间歇性挂起,工作原理是设置一个定时器,到期后唤醒进程
- 用户主动挂起,如ctrl+z 挂起
- 挂起状态
- 阻塞挂起: 此时进程是在外存的,并等待某个事件的返回
- 就绪挂起: 此时进程也在外存,但是只要进入内存,立刻运行
- 如果有大量处于阻塞状态的进程时,操作系统会将阻塞状态的进程的内存空间置换到磁盘中,等需要时,再次加载,此时进程是处于挂起状态,与阻塞状态是不同的,阻塞是等待某个事件返回,而挂起则可能是因为
- 状态间的转换:
- 运行态->阻塞态: 等待输入
- 运行态->就绪态: cpu时间片给其他进程
- 就绪态-> 运行态: 获取得到cpu时间片
- 阻塞态->就绪态: 得到了输入,准备运行
- 注意
- 没有就绪态到阻塞态,也没有阻塞态到运行态
- 总之,阻塞态都是单向的,要么出,要么进
进程的结构
- 操作系统中用PCB进程控制块数据结构来描述进程
- 有进程必然有PCB
- what: PCB是进程的唯一标识,所以有进程必然有PCB
- PCB所包含的内容
- 进程描述信息
- 进程标识符: 标识每个进程,每个进程都有唯一的标识符
- 用户标识符: 这个进程所归属的用户
- 进程控制和管理信息
- 进程当前的状态: 就绪,阻塞,运行,挂起等
- 进程优先级
- cpu信息
- cpu中各个寄存器的值,当进程被切换时,cpu的信息都会保存在对应的pcb中,这样当进程从中断被唤醒重新执行时,可以从断点处执行
- 进程描述信息
- PCB的组织是以链表的形式组织的,并且所有相同状态的pcb存储在同个队列中
- 所有就绪等待的进程,存储在就绪队列
- 把所有阻塞的进程存储在阻塞队列
进程的控制
- 创建进程
- 允许进程创建子进程,子进程继承父进程的资源(环境变量,还有堆栈等虚拟地址空间里的东西)
- 申请PCB(PCB是有限的,可能会失败) => 为进程分配资源,资源不足则等待 => 初始化pcb => 进程调度决定是否将其加入就绪队列
- 终止进程(正常退出,异常退出,以及kill)
- 查找需要终止的pcb,如果处于执行状态,则终止其执行,并且将cpu分配给其他进程,然后如果有子进程,则应将所有的子进程也全部终止,将资源全部归还给操作系统,从pcb队列中删除
- 阻塞进程(当需要等待某一个事件时,调用阻塞语句自己阻塞等待,然后等待被其他进程唤醒)
- 找到要被阻塞的pcb,如果为运行状态,则保护现场,设置为阻塞状态之后,将其放到阻塞队列中
- 唤醒进程
- 从阻塞队列中找到要唤醒的pcb,将其从阻塞队列删除,并且设置为就绪状态,插入到就绪队列中,等待被调度
进程的上下文切换
- 进程的上下文切换实际是CPU的上下文切换
- CPU的上下文: 指的是cpu运行该任务时,所需要的环境
- 因此CPU上下文切换指的就是把上一个任务的CPU上下文(CPU寄存器和程序计数器)保存起来,然后加载新任务的上下文加载到CPU寄存器和程序计数器中,然后继续执行任务 , 根据任务的不同,切换分为进程上下文切换,任务上下文切换,和中断上下文切换,然后不同的任务数据也不一样
- 进程: 包含了虚拟内存,栈,全局变量等信息
- 进程上下文切换的时机(既触发cpu重新调度就会触发切换)
- 当进程的cpu时间片被用完时,从运行态变成了就绪态,系统重新调度,从而触发切换
- 资源不足时,进程被迫挂起
- 进程自己进行阻塞sleep
- 硬中断时
线程
-
线程是进程资源调度的最小单位,当进程只有1个线程时,进程=线程
-
线程的存在意义
- 为了并发式调度,如 读取文件,并不需要等待全部读取了再进行操作,完全可以读多少操作多少
-
线程的优缺点
- 优点
- 一个进程中可以存在多个线程
- 各个线程之间可以并行
- 线程之间共享同一片虚拟地址空间
- 缺点
- 当一个线程崩溃时,导致所属进程的线程全部崩溃
- 优点
-
线程相比进程的优缺点
- 线程创建的消耗更小,因为不需要去创建pcb信息,pcb里面存储了所有的资源(内存资源,文件资源等),地址空间等,而线程只需要从进程地址空间中拿出必须的资源即可(如栈),并且共享
- 线程上下文切换比进程上下文切换更高效
- 因为共享同一个虚拟地址空间,所以共享同一个页表,不需要发生切换
- 数据通信: 因为线程都在进程内,进程间通信因为进程都在内核,所以都需要有内核调用,而线程都在进程内,不需要走内核
- cpu上下文切换开销小: 进程的上下文切换需要涉及到pcb的保存现场,而线程的上下文切换,因为都在一个进程内,所以切换时,共享资源是不需要切换的,只需要更新私有数据即可
-
线程的上下文切换
- 操作系统的任务调度,实际调度对象全是线程,进程只是给线程提供资源而已
- 而线程的上下文切换,就是保存自己的私有数据,比如栈和寄存器
-
线程的实现
- 用户线程: 在用户态实现的线程,不由内核管理,由用户态的线程库进行管理
- 基于用户态实现,所以TCB(线程控制块)操作系统感知不到,既然无法感知,因此用户完全自主控制什么时候创建销毁阻塞
- 优点:
- 用户线程的切换由线程库函数来实现,无需用户态和内核态的转换,效率很快
- 缺点
- 操作系统不参与,因此当这个线程掉用了某个资源而阻塞时,
- 除非该线程收到放弃cpu,否则其他线程一直都无法执行,所以往往用户会自己实现用户线程监控(比如golang,golang对于长时间运行的就会舍弃cpu)
- 每个cpu获得的时间片更少 ,因为先分配给内核线程,再分配给用户线程,自然更少
- 内核线程: 内核态实现的线程,由内核管理
-
TCB都被操作系统所管理
-
-
优点
- 当某个线程阻塞时,该进程不会被阻塞(因为操作系统能知道该进程有哪些线程)
-
缺点
- 内核维护TCP
- 开销大: 因为创建,终止,销毁都是需要通过系统调用实现,
-
-
轻量级进程(注意是进程: Light-weight process): 内核中支持用户线程
- 用户线程: 在用户态实现的线程,不由内核管理,由用户态的线程库进行管理
-
线程模型
-
1:1 模型: 既1个用户线程:1个内核线程
-
n:1 模型: 既n个用户线程:1个内核线程
-
n:n 模型: 既n个用户线程对应n个内核线程
-
进程的调度
-
调度算法
- 非抢占式调度算法: 挑选一个进程,然后让该进程运行,直到被阻塞,或者被退出,否则一直处于运行状态
- 抢占式调度算法: 挑选一个 进程,然后让该进程只运行一段时间,该时间片结束时,如果该进程还没有结束,则会被挂起
-
进程的调度要依据不同的环境来选择
-
批处理系统
- 先来先服务
-
-
what:
- 非抢占式
- 最简单的调度算法,
-
优点:
- 简单,易于理解且在程序中运用
-
缺点:
- 当出现 多个进程,且多个进程之间运行一次程序时间差别较大时,会产生巨额延迟
-
- 最短作业优先
-
-
what:
- 非抢占式,优先选择运行时间最短的进行处理
-
when:
- 当有若干个同等重要的作业被启动时,可以考虑 先调用 时间最短的先执行
- 并且,只有在所有的作业都可以同时运行的情况下,最短作业优先算法才是最优的
- 原因:
- 假设有5个作业 A,B,C,D,E ,各自执行完毕分别需要 2,4,1,1,1 然后 关键点: 到来时机不同 分别为0,0,3,3,3
- 按照最短作业优先原则, 会先 A,B,C,D,E
- 但是很明显,先执行B 的效率会更高(因为B 执行完毕之后,C,D,E 都到了)
- 假设有5个作业 A,B,C,D,E ,各自执行完毕分别需要 2,4,1,1,1 然后 关键点: 到来时机不同 分别为0,0,3,3,3
-
优点
- 有助于提供系统吞吐量
-
缺点
- 饿死现象: 当有大量的短作业时,对长作业不有利,使得长作业不停的往后推,处于饿死现象
-
- 先来先服务
-
交互式系统的进程调度
-
轮转调度(
时间片概念
)-
-
what:
- 最古老,最简单,也最公平的调度算法
- 每个进程都被分配 一个时间段(时间片) ,如果在该时间片内未完毕,则将被剥夺时间片给其他进程,如果已经执行完毕了,则会立即释放时间片
-
缺点:
- 时间片设置的值的定义:
- 如果设置的太大,假如瞬时间有n个进程到来, 则第二个进程必须等待第1个进程 n 时间才会被执行
- 但是如果设置的太短,又会导致过多的上下文切换
- 时间片设置的值的定义:
-
-
优先级调度算法:
- what:
- 每个进程都会有一个优先级,优先级高的先被执行 (如发送邮件这种就是低优先级)
- 优点:
- 涉及到了权限,所以适用于多用户的情况
- 缺点
- 与最短作业优先算法一样也会有饿死现象: 低优先级的任务可能一直不会执行
- what:
-
多级反馈队列
-
-
how:
- 分为多个队列,每个队列优先级从高到低,但是时间片却逐渐增大
- 先来的进程先放到一级队列,如果在时间片内没有运行完毕,则放到第二级队列,以此类推,直到完成
- 当较高优先级的队列为空的时候,才会去调较低优先级队列,如果运行时,有新的较高优先级进入,则挂起当前,并将其移入到队
-
优点
- 兼容了长短作业,既短作业如果没有执行完,则会降级,同时给的时间片也给长了,有了较好的响应时间
-
-
先来先服务
-
最短作业优先
-
优先级调度算法
-
-
时间片轮转算法
-
多级反馈队列
进程间通信
-
**管道Pipe: 既 | **
-
what:
- 既用于传输字节的通道, 进程间可以创建管道,另一个进程从这个管道读取数据,对应到linux 就是 |
- 匿名管道: 既 |
- 命名管道: 通过 mkfifo 创建, 如mkfifo mingmingguandao
- 匿名管道和命名管道区别在于,匿名管道用于父子进程
-
how:
-
其实管道就是内核里的一串缓存,一端写入的数据是存储在内核中的
-
创建管道会返回2个描述符,一个是读取描述符f[0],一个是写入描述符f[1]
-
具体实现方式是: 通过父子进程,子进程会拥有父进程的文件描述符,子进程关闭写的 文件描述符f[1],父进程关闭读的文件描述符f[0]
-
-
如:
- sort -f | head
- sort 是shell的子进程,会创建返回2个文件描述符,sort 读取shell输入之后,又通过匿名管道创建一个子进程,同样子进程关闭写入描述符f(1),只读取,然后sort子进程将数据写入管道,head 读取
- 2个进程sort进程和head进程,当sort进程结束后,head 进程就可以读取 sort进程的结果
-
-
优点:
- 简单
-
缺点
- 效率低,不适合频繁传输
-
-
消息队列
- what
- 消息队列是保存在内核中的消息链表
- 优点
- 效率高
- 缺点
- 消息体是有大小限制的,不适合大数据传输
- 用户态和内核态的数据拷贝开销:因为数据是从用户态拿到的
- 具有延迟性
- what
-
共享内存
-
what
- 就是拿出一块虚拟内存地址空间,映射到相同的物理内存
-
优点
- 不会有用户态和内核态转换的开销
-
缺点
- 数据一致性
-
-
信号量
- what: 是为了解决共享内存数据一致性 的问题,防止被别人覆盖数据
- how:
- 两种原子操作
- P操作: 当尝试获取资源的时候,会有P操作,当相减如果信号量<0,则表明资源已被占用,该进程会被阻塞
- V操作: 当释放资源的时候,会有V操作(相加),当相加后大于0代表有进程在等待,则会唤醒阻塞的进程
- 两种原子操作
-
信号机制:
-
what:
- 是一种异步的 事件通信机制
-
how:
-
-
where:
-
当程序处于不正常状态时,通过信号机制通知 该进程下一步怎么处理
-
人为:可以从键盘中输入
-
程序: 访问不存在的位置(可以认为空指针等)
-
-
demo
- 如输入 exit 命令
-
有哪些信号量,可以通过 kill -l 查看
-
锁
-
互斥锁:
- 加锁失败之后就会释放CPU,给其他线程,然后自己进入阻塞状态
- 由操作系统内核实现
- 缺点:
- 有性能开销成本,因为是操作系统内核实现,会触发2次线程上下文切换
- 当加锁失败时,线程从运行态变为阻塞态
- 当锁被释放时,从阻塞态进入就绪态,等待被调度
- 有性能开销成本,因为是操作系统内核实现,会触发2次线程上下文切换
-
自旋锁: 加锁失败之后,不会释放cpu,而是忙等待
-
how
- 全部在用户态实现加锁和解锁
-
-
读写锁
- 适用于读多写少的场景
-
悲观锁
- what: 做任何操作之前都先加锁
-
乐观锁
- what: 先修改完资源,再验证这段时间内有没有发生冲突,有的话,则放弃本次操作,所以也可以认为是无锁编程
- demo
- 在线文档编辑
- git
- how:
- 通过版本号实现,
- 获取数据前,也会获得版本号
- 然后提价的时候,同时携带这个版本号,服务器收到数据之后,通过携带的版本号进行对比,对比通过则更新
- 通过版本号实现,
内存管理
-
物理内存:
- what: 内核的代码和数据都是存放在物理内存中的 ,就比如说16G内存,8G内存这种概念,都是物理内存
-
虚拟内存:
- why: 在64位寻址下,需要2^24TB的物理内存,显然不可能, 而每个程序的运行都需要占据一定的内存空间,为了防止内存过涨,因而引入了虚拟内存
- what: 让程序拥有超过系统物理内存的阈值,每个进程都有自己的地址空间
-
地址空间:
- what: 是程序的抽象内存,是进程可用于寻址的一套地址集合,每个进程都有一个自己的地址空间,并且进程隔离
-
物理地址:
- what: 内核的代码和数据都是存放在内存中的,cpu可以通过物理地址访问数据, 既 可以认为 物理地址是一个指向数据的唯一id
-
虚拟地址:
- what: 虚拟地址针对于虚拟内存而言的地址, 进程之间互相隔离
-
页表:
- what: 用于 虚拟地址转换为物理地址
- [外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-n1oSqfZ3-1639181489867)(/Users/joker/cmds/复习/操作系统/imgs/内存转换.jpeg)]
- what: 用于 虚拟地址转换为物理地址
-
MMU:
- what: 是一个硬件电路,称为内存管理单元,用途在于内存寻址 :把虚拟地址映射为物理内存地址 ,
-
页表项:
- what: 保存的是虚拟地址到物理地址的映射,以及存储一些管理标志
-
TLB:
- what: 转换检测缓冲区
- where: 在MMU中
- why: 在不分页的情况下,读取指令直接从内存中读取,但是有了分页之后,要访问页表从而会有更多次的内存访问(尤其是多级页表的时候),性能会有更多的降低,TLB的出现是因为发现, 大多数程序总是对少量的页面进行多次的访问,从而使得如果在TLB中存在,越过页表直接得到物理地址
- how: 将一个虚拟地址放入MMU 进行转换时,硬件首先将虚拟页号和TLB中的所有表项并行进行匹配,判断虚拟页面是否在其内,如果存在,则直接读取,而不必再去访问页表,如果不存在,则正常的通过页表查询
-
缺页中断:
- what: 当程序访问一个未映射的页面时候,会使得 CPU 到操作系统中找一个很少使用的页面写入到磁盘,再通过MMU修改映射关系
-
页表:
分页技术
- what: 在虚拟内存中, 程序会引用一组内存地址, 这些地址为虚拟地址, 一组虚拟地址构成虚拟地址空间,在没有虚拟内存的计算机上,系统直接将虚拟地址送到内存总线上(此时读写虚拟地址就是物理地址),但是有虚拟内存下,虚拟地址被送到MMU上进行地址转换
- 在虚拟地址空间中会按照大小切分 ,称为页面 ,在物理内存中对应的是页框
页面置换算法
- what:
- when: 当发生页面中断时
- why: 内存是有限的,当发生页面中断时,为了腾出新的页面的空间,需要将内存中的一些其他页面给置换出去,同时为了保证数据的一致性,置换出去的页面如果被修改过了,还需要更新页面磁盘的副本上
- 基本知识点:
- 在虚拟内存中,每个页面都有2个状态位,当页面被访问读或者写时,设置R位,当页面被修改时,设置为M位, 并且这些位在页表项都是存在的
时钟页面置换算法,以及扩展版:工作集时钟页面置换算法
- what: 就是将所有页面连接成一个环,置换出不使用的页面
- how:
- 将所有的页面,都存储在一个类似钟面的环形链表,一个表针指向最老的页面
- 当发生缺页中断时,先检查表针指向的页面
- 如果访问位是0,则置换出去,然后将页面放到该位置,同时指正往前移动一个位置
- 如果访问位置是1,则清除访问位,然后指针继续往前移动一个位置,重复直到找到 访问位是0的页面
最近未使用页面置换算法(NRU)
- how: 当启动一个进程时,所有页面的2个位都被操作系统设置为0,R位被定期的清0(比如说时间片用完了),目的是用来区别没有访问的页面和已被访问的页面,然后进行分级别:
- 0类: 没有被访问,也没有被使用
- 1类: 没有被访问,被修改
- 2类: 被访问,没有被修改
- 3类: 被访问,也被修改
- 然后淘汰的时候,会根据优先级最低的先淘汰掉
- 问题:
- 为什么时钟中断的时候,只是清理R位,但是不清理M位
- A: 数据一致性问题: 如果清0了,则替换的时候,不知道要不要写回到磁盘中保存数据
- 为什么时钟中断的时候,只是清理R位,但是不清理M位
- 优点:
- 简单,易于理解
- 缺点:
- 相比FIFO ,性能上差点
先进先出页面置换算法(FIFO)
- how: 链表维护,当不足的时,先将最开始的弹出去
- 缺陷:
- 可能置换的页面是常用的页面
第二次机会页面置换算法(Second Chance)
- how: 既当页面被置换的时候,看下是否设置了R位,如果是 ,则修改到链表尾端 ,不是则直接替换掉
- 优点:
- 弥补FIFO 的缺点
- 缺点:
- 性能,需要频繁在链表中移动页面
最近最久未使用页面置换算法(LRU)
- how:
- 硬件方案: 硬件对每个页面实现一个计数器,统计次数,当发生缺页中断时,操作系统检查所有页表项的计数器,找到值最小的页表项对应的页面
- 缺点:
- 需要内存中管理所有的页面,并且需要统计次数,同时,每次操作都需要进行链表移动,开销大
缓存
文件管理
- 文件
- what: 文件是进程创建的信息逻辑单元,一个磁盘含有几千甚至几百万个文件
死锁
-
资源 是否可抢占 取决于上下文,当 内存支持置换到磁盘和从内存中换出时,则表明是可抢占的资源
-
死锁的触发与不可抢占资源有关
-
触发的条件:(必然全部满足)
- 互斥条件
- 占有和等待条件
- 不可抢占条件
- 循环等待
问题
-
什么是写时复制:
以操作系统为例 当创建一个子进程的时候,复制内存代价相当昂贵,因此当创建子进程的时候,父进程赋予子进程自己的页表,但是指向的都是父进程的 页面,并且这些页面对于子进程而言,只可读,不可写,当需要写的时候,为该进程分配这个页面的副本,并且可读可写 这就是CopyOnWrite技术
-
卡是什么原因,为什么会卡
- 从 内存 缺页中断说起
-
什么是RAM
-
缺页中断的时候会发生什么
-
TLB 存在的作用
- TLB的存在是为了提高读取指令和数据的性能
- MMU通过虚拟地址找到对应的物理地址需要两步操作:
- 通过页表查询得到物理地址
- 通过物理地址读取指令和数据
- 而TLB可以认为是页表的本地缓存,存储了当前最可能访问的数据信息
-
什么是缺页中断:
- 当进程访问的虚拟地址在页表中无法找到时,就会发生一次缺页中断,进入系统内核空间分配物理内存,更新进程页表,最后再返回用户空间,恢复进程的运行
-
TLB中存储了什么:
- 缓存最近常被访问的页表项
-
程序的执行流程总结:
- 当程序启动的时候,操作系统会分配该程序一个地址空间,当程序获取得到CPU时间片时,cpu通过操作系统分配的MMU(内存管理单元),MMU 通过
-
什么是缺页中断:
- 触发时机
-
每个进程启动都会占据一定的内存,如何防止内存过涨
- A:
- 交换技术:
- 既该进程运行一段时间之后,将它存回磁盘,空闲进程主要存储在磁盘中,就不会占用内存
- 如linux 系统上的 swap空间,这段空间就是用于内存和硬盘交换的
- 虚拟内存:
- what:
- 每个程序都有自己的地址空间
- 地址空间由页组成,每个页都有连续的地址范围,并且每个页都映射到物理内存
- how:
- 程序运行时,并不是所有的页都会载入到内存中,只有在按需的时候才会将缺失的页载入到内存中
- what:
- 交换技术:
- A:
-
为什么当用户线程发起系统调用阻塞之后,会导致进程包含的用户线程都无法执行
- 因为在线程模型中,n:1 既多个用户线程对应1个内核线程的情况下,因为用户线程的tcb 操作系统无法感知,因此操作系统不知道改进程有哪些线程,所以只能全部阻塞,1:1 就不会有这个问题
-
为什么进程的上下文切换开销比线程的上下文切换开销大
- 因为进程的上下文切换设计
- 保存进程当前的pcb信息:当前cpu的寄存器值,以及程序计数器等信息
- 把原来的进程移到 就绪/阻塞队列中
- 更新被选中的进程的pcb
- 装载被选中的pcb到cpu中
- 而线程的上下文切换,不涉及到pcb,只需要保存自己线程的堆栈,程序计数器等信息即可
- 因为进程的上下文切换设计
-
内核线程和 用户级轻量级进程的区别
- 轻量级进程是一种由内核支持的用户线程,是内核线程的高级抽象,因此LWP是基于内核线程的,可以认为是内核空间中的用户线程
-
协程为什么比内核线程轻量
- 上下文切换开销小:
- 线程的上下文切换,涉及到用户态和内核态的切换开销,因为内部相比协程,有更多的寄存器现场保存
- 占用空间少
- 线程至少占用2m,而goroutine 只需要2k ,但是golang 的goroutine 只会被复用,不会被回收
- 上下文切换开销小:
-
什么是孤儿进程:
- 既父进程退出,但是其一个或者多个子进程依旧在继续运行,则这些子进程将成为孤儿进程,孤儿进程被init进程(进程号位1)的进程收养,并由init进程进行状态收集
-
什么是僵死进程:
- 一个进程使用fork创建子进程,若子进程退出,而父进程没有调用wait 或者waitpid获取子进程的状态信息,则该子进程的进程描述符依旧在系统中,则该进程称为僵死进程
- 保留的信息不回释放,进程号就会被一直占用.而os 进程号是有限的,大量的僵死进程将会导致系统崩溃
- 一个进程使用fork创建子进程,若子进程退出,而父进程没有调用wait 或者waitpid获取子进程的状态信息,则该子进程的进程描述符依旧在系统中,则该进程称为僵死进程
-
进程
- 是资源分配的最小单位
-
线程
- 是进程执行的最小单位
-
协程
- 协程是线程里的线程
- 协程的出现是为了弥补线程的缺点,线程的创建需要从 os 内核中申请,然后映射返回,意味着首先会有一个从用户态到内核态的开销,其次,一个线程的创建,是真的内核中一个线程的创建,线程数量是有限的, 线程存储的是 寄存器里的相关cpu 指令,堆栈空间
- 协程模拟了线程在内核中的存在, 协程也具有 自己的堆栈存储,cpu指令集,但是,都是在一个线程中存在,并且全程都在用户态操作,不受操作系统线程数量限制
-
互斥锁和自旋锁的区别
-
层面不同, 前者由操作系统内核态处理,后者则全部在用户态进行处理
-
前者当获取锁失败之后,则会释放cpu,进入阻塞状态,后者不会释放cpu,而是一直忙等待
-
后者适用于很快获取锁的情况
-
为什么消息队列会有用户态和内核态转换的开销
-
-
什么是缺页中断
- 当CPU访问的页面不在内存时,就会触发页面中断,请求操作系统将缺失的页面调度到物理内存中
- [外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-sNE5X2kC-1639181489866)(/Users/joker/Nutstore Files/我的坚果云/复习/操作系统/imgs/调度算法-缺页中断流程.png)]
- CPU请求访问页面时,会先访问页表MMU,然后通过页表找到对应的页表项
- 如果该页表项标识为有效,则直接返回,否则的话无效,则会向cpu发送一个缺页中断
- 操作系统收到缺页中断之后,开始调用缺页中断函数,先查找该页面所属的磁盘位置
- 找到磁盘中的页面之后,将其置换到物理内存中,如果内存空间不足这时候会触发页面置换调度算法,换出页面,换入新的页面
- cpu重新执行导致页面缺失的指令
- 当CPU访问的页面不在内存时,就会触发页面中断,请求操作系统将缺失的页面调度到物理内存中