写在前面
- 记录一下操作系统的相关知识;
- 主要的参考如下:
一、内存管理
1. 内存映射方式
- 虚拟内存地址均需要映射到物理内存地址才能被CPU访问;
- 虚拟内存地址和物理内存地址之间映射方式有以下三种:
- 分段式;
- 分页式;
- 段页式;
- 也参考:
1.1 分段式
- 机制:
- 将虚拟内存分成若干段,然后通过段表将不同段映射到物理内存的不同区域;
-
作用:
- (1) 动态重定位:引入了段表进行内存映射,虚拟内存映射的地址不用都从某个固定的物理地址开始映射,而是可以通过段表找到映射的物理地址;
- (2) 避免未使用的虚拟内存映射:无需将虚拟内存完全映射到物理内存上,只需映射虚拟内存中已经使用的段数据,段间的未使用虚拟内存无需映射,这样可以节省映射占用的物理内存空间;
- (3) 进程间可以共享段:可以节省映射的空间,如内核映射的段是由各个进程共享的;
-
不足:
- (1) 会产生外部内存碎片:
- 原因:
- 因为是使用多少空间就映射多少空间,而且映射的粒度较大,所以物理内存的段和段之间是有空隙(外部内存碎片)的;
- 解决方式:
- 通过Swap机制重排物理内存:使物理内存排列紧凑消除内存碎片;
- 使用分页机制:减小Swap机制交换物理内存和磁盘数据的粒度,提高效率;
- 原因:
- (1) 会产生外部内存碎片:
1.2 分页式
- 机制:
- 将虚拟内存和物理内存均切分成页大小(4KB),然后通过页表将虚拟内存的页映射到物理内存的页中;
-
作用:
- 避免外部内存碎片:由于页的大小是固定的,内存分配的最小单位是页,所以分配的页和页之间是紧凑的,不会产生外部碎片;
-
不足:
-
(1) 会产生内部内存碎片:
- 原因:
- 因为内存分配的最小单位是页,即使不满页大小也会分配一页,所以会产生内部空隙(内部内存碎片);
- 原因:
-
(2) 页表过大:
- 原因:
- 为了避免内部内存碎片过大,页的粒度是比较小的,因此总的页数量很大;
- 由于每个页都需要记录虚拟内存到物理内存的映射关系,因此页表也会很大;
- 解决方法:
- 采用多级页表;
32
位系统通常采用二级页表,64
位系统通常采用四级页表;- 但会影响虚拟地址和物理地址的转换效率;
- 原因:
-
(3) 地址映射的效率低:
- 原因:
- 因为多级页表需要增加转换的步骤,因此转换的开销更大;
- 解决方法:
- 采用TLB(Translation Lookaside Buffer)缓存页表部分地址;
- 进行虚拟地址转换时,首先查找TLB,看是否已缓存对应的物理地址,如果没有,再用多级页表查找;
- 在TLB命中率高时可以大幅提升查表的速度;
- 原因:
-
1.3 段页式
- 机制:
- 是分段式和分页式的结合;
- 首先通过段表确定段基地址,再通过页表确定物理页地址;
1.4 Linux的内存映射方式
- Linux采用分页式进行内存映射;
- (1) 虚拟内存中是分段的;
- 包括:
- 内核虚拟内存段对应的物理内存各个进程是共享的,但该段的物理内存并非按照分段连续分配,而是通过页表分散分配的,物理内存本质上还是分页式映射;
- 此外,还有代码段、数据段、堆段、文件映射段和栈段;
- 分段有利于访问控制和内存保护;
- 虚拟内存中的分段是用GDT(Global Descriptor Table)描述的,其中包含了不同段的起始地址、大小和访问权限等信息;
- 虚拟内存的分段如下:
- 包括:
- (2) 但物理内存中并没有做分段隔离;
- 虽然有段的概念,但所有段的段基地址都是0;
- 也就是说,每一段的虚拟页都可以映射到任一物理页上;
- 这提高了物理内存的使用效率;
- Linux采用的内存映射方式如下:
- 虽然有段的概念,但所有段的段基地址都是0;
- (1) 虚拟内存中是分段的;
2. 内存回收
2.1 三种内存回收方式
- (1) 后台内存回收:
- 在物理内存紧张时,唤醒kswapd内核线程回收内存;
- 异步执行回收;
- (2) 直接内存回收:
- 如果申请内存的速度超过后台内存回收的速度,就会直接阻塞申请内存的进程,直至回收够足够的物理内存;
- 同步执行回收;
- (3) OOM机制:
- Out of Memory机制;
- 如果回收内存后物理内存仍不满足申请的内存,则杀死一个物理内存占用较高的进程并回收其内存,直至有足够的内存可用;
2.2 两种可回收内存
-
(1) 文件页:
- 即内核缓存的磁盘数据和文件数据;
- 回收方式分为未修改的页和已修改的页:
- 未修改的页(干净页):直接释放物理内存;
- 已修改的页(脏页):先写回磁盘,再释放内存;
-
(2) 匿名页:
- 临时产生的非持久化数据,如堆和栈上的数据;
- 回收方式:
- 通过Linux的Swap机制将匿名页写入Swap分区或者Swap文件中,然后释放物理内存;
- 等需要访问匿名页时,再从磁盘读入到内存中;
2.3 Swap机制
-
作用:
- 当申请使用的物理内存不够时,将部分物理内存写入到磁盘,然后释放该部分的物理内存;
- 当申请(正在使用)的物理内存超过了空闲物理内存大小时:
- (1) 如果开启了Swap机制,则尝试用Swap机制回收内存;
- (2) 如果没有开启Swap机制,则直接OOM;
- 注意:
- 使用
malloc()
是申请虚拟内存,并不会马上分配物理内存的; - 但如果要读写虚拟内存,就需要申请物理内存,此时会导致物理内存不足;
- 也就是说,如果不开Swap机制,则虚拟内存并不能真正发挥虚拟内存的能力,能同时使用的虚拟内存局限在物理内存中,无法扩展到整个虚拟内存空间,但如果是不同进程的话,也可以扩展到物理内存的大小之外,只是限制了单一进程能够同时使用的虚拟内存大小;
- 使用
-
Swap换出的两种磁盘空间:
- (1) Swap分区:
- 是磁盘上的独立区域,和其他文件系统隔离,彼此不能访问;
- 访问速度较快,但要求磁盘空间充足;
- (2) Swap文件:
- 创建在已有的文件系统中,是专门用于Swap的普通文件;
- 访问速度较慢,但可以为系统留出更多的磁盘空间,也可以动态调整大小或者删除,具有更高的灵活性;
- (1) Swap分区:
-
Swap换出内存的选择算法:
- 使用了改进的LRU算法:
- 将数据分为冷数据和热数据;
- (1) 活跃LRU链表(active list):保存最活跃的页;
- (2) 不活跃内存页链表(inactive list):保存不太活跃的页;
- 然后分别进行LRU算法;
- 如果某个页被访问一次,则将它放到inactive list中;
- 文件页是放在inactive list的头部;
- 匿名页是放在active list的尾部;
- 如果某个页被访问两次,则将它放到active list中;
- 文件页是放在active list的尾部,再访问一次才放到头部;
- 匿名页是放在active list的头部;
- 预读的页放到inactive list中;
- active list换出的页放到inactive list中;
- 如果某个页被访问一次,则将它放到inactive list中;
- 将数据分为冷数据和热数据;
- 可以避免传统LRU算法的:
- (1) 预读失效:
- 原因:
- 读取某页时会顺便读和它相邻的页;
- 如果它相邻的页之后不会被访问,则为预读失效;
- 避免方法:
- 使用两个LRU,预读的页放到inactive list中;
- 原因:
- (2) 缓存污染:
- 原因:
- 非热点访问的页被放到了active list中,将热点访问的页挤出了active list;
- 避免方法:
- 提高进入active list的门槛,即需要被访问两次才能被放入active list中;
- 原因:
- (1) 预读失效:
- 使用了改进的LRU算法:
3. 内存页面置换算法
3.1 最佳页面置换
- 机制:
- 将未来最长时间不访问的页面换出物理内存;
- 但由于未来再次访问的时间不能得知,因此实际是不能实现的;
3.2 先进先出置换
- 机制:
- 将驻留时间最长的页面换出物理内存;
3.3 最近最少使用置换
- 最近最少使用(Least Recently Used, LRU);
- 机制:
- 选择最长时间没有被访问的页面换出物理内存;
- 近似最佳页面置换算法;
- 可以用双向链表+指向哈希表来实现;
3.4 时钟页面置换
- 机制:
- 使用环状链表;
- 如果访问位是
0
则换出物理内存,如果是1
则置0
并移动指针,直至找到访问位为0
的页;
3.5 最不常用置换
- 最不常用(Least Frequently Used, LFU);
- 机制:
- 记录每个页面的访问数量;
- 选择访问次数最少的页面换出物理内存;
二、进程管理
1. 进程
1.1 进程的七种状态
- 核心状态有
3
种:- 运行状态:
- 占用CPU执行;
- 就绪状态:
- 等待分配CPU执行;
- 阻塞状态:
- 等待某个事件的发生,不能占用CPU执行;
- 运行状态:
- 首尾状态有
2
种:- 创建状态:
- 进程创建的状态,只能转入就绪状态;
- 结束状态:
- 进程结束的状态,只能从运行状态转出;
- 创建状态:
- 挂起状态(进程不在物理内存中,被换出到磁盘)有
2
种:- 就绪挂起状态;
- 阻塞挂起状态;
1.2 进程控制块PCB
-
进程控制块(Process control block, PCB);
- 是进程存在的唯一标识;
- 操作系统通过PCB控制进程的切换和生命周期;
-
包含的信息如下:
- (1) 进程描述信息:
- 进程标识符PID;
- 用户标识符;
- 进程归属的用户;
- (2) 进程控制和管理信息:
- 进程状态;
- 进程优先级;
- (3) 资源分配信息:
- 虚拟内存信息(含用户空间和内核空间);
- 已打开的文件信息(描述符表和打开文件表)和I/O设备信息;
- (4) CPU相关信息:
- CPU寄存器值;
- CPU程序计数器值;
- (1) 进程描述信息:
-
组织方式:
- 通过链表组织,包括:
- 就绪队列;
- 阻塞队列;
- 每个队列均用链表组织PCB结构块,如下:
- 通过链表组织,包括:
1.3 进程上下文切换
- 发生在内核态;
- 由内核管理和调度;
- 切换过程:
- 将换出进程的信息保存在该进程的PCB中;
- 加载换入进程的PCB信息,执行换入进程;
- 切换发生场景:
- 时间片耗尽;
- 进程因自身原因或者外部中断而被挂起;
2. 线程
2.1 线程的三种实现方式
-
(1) 用户线程;
- 在用户空间实现的线程,由用户管理(创建和销毁);
- TCB放在用户空间内,内核只能访问PCB,无法直接访问TCB;
- 上下文切换是在用户态中;
-
(2) 内核线程;
- 在内核空间实现的线程,由内核管理(创建和销毁);
- TCB放在内核空间中,内核可以直接访问;
- 上下文切换是在内核态中的;
-
(3) 轻量级进程;
- 内核支持的用户线程;
- 建立在内核上,但可以由用户自行管理(创建和销毁),性能更高;
- 类似于进程的管理方式,但更加轻量级;
- 与父进程共享进程地址空间和系统资源;
- 因此需要的资源更少,类似于线程;
- 创建和销毁是在用户态中,但上下文切换是在内核态中的;
- 创建方式:
- Linux由
clone()
系统调用(参数是CLONE_VM
)创建轻量级进程; std::thread
或者pthread
创建的是轻量级进程;
- Linux由
2.2 线程控制块TCB
- 线程控制块(Thread Control Block, TCB);
- 包含线程的状态信息:
- CPU程序计数器值;
- CPU寄存器值;
- 虚拟内存的用户栈指针(有自己的用户栈);
3. 进程调度算法
- 虽然线程是CPU调度的基本单位,但这里仍然是以进程的视角来进行调度的;
- CPU调度的线程是仅包括内核线程或者轻量级进程,并不包括用户线程;
- 从这个角度看,内核线程也可以看作是“更重”一点的轻量级进程,然后它们都可以归在进程这块讨论;
- 也就是说,CPU调度包括:
- 进程;
- 内核线程;
- 轻量级进程;
- 所以还是用进程的视角来描述调度的过程比较恰当;
3.1 先来先服务调度
- 先来先服务(First Come First Served, FCFS);
- 机制:
- 非抢占式;
- 取就绪队列的头进程运行,直至运行结束;
- 特点:
- 对长作业有利;
- 适合CPU密集作业,不适合I/O密集作业;
3.2 最短作业优先调度
- 机制:
- 非抢占式;
- 取运行时间最短的进程运行,直至运行结束;
- 特点:
- 对短作业有利;
3.3 高响应比优先调度
- 机制:
- 非抢占式;
- 先计算优先权,再取优先权大的进程运行,直至运行结束;
- 但由于要求服务时间难以得知,所以实际是不能实现的;
[3.4] 时间片轮转调度
- 时间片轮转(Round Robin, RR);
- 机制:
- 抢占式;
- 每个进程被分配一个时间片运行,时间片用完则切换上下文运行下一个进程;
3.5 最高优先级调度
- 机制:
- 非抢占式;
- 选择就绪队列中优先级最高的进程运行,直至运行结束;
[3.6] 多级反馈队列调度
- 多级反馈队列(Multilevel Feedback Queue, MFQ);
- 机制:
- 抢占式;
- 综合了时间片轮转和最高优先级两个算法;
- 使用多个队列;
- 优先级高的队列时间片短,优先级低的队列时间片长;
- 同一队列中的进程使用时间片轮转,但更高优先级队列的进程可以抢占优先执行;
- 如果在当前队列的时间片中没有运行完,则被降级到下一个队列;
[3.7] 完全公平调度
- 完全公平调度(Completely Fair Scheduler, CFS);
- 机制:
- 抢占式;
- 基于时间片轮转和优先级实现;
- 每个进程有两个时间:
- 实际运行时间:
调度周期 * 进程权重 / 所有进程权重之和
;- 也就是按权重按比例分配某个调度周期作为时间片;
- 并不是按照进程个数平均分固定大小的时间片;
- 虚拟运行时间:
总实际运行时间 * 1024 / 进程权重
;- 如果完整执行完若干个调度周期,则
虚拟运行时间 = 若干调度周期 * 1024 / 所有进程总权重
; - 也就是在每个调度周期结束时,如果每个进程都运行且只运行一遍,则每个进程的虚拟运行时间都是相同的;
- 因此在调度周期的过程中,看哪个进程的虚拟运行时间短就可以决定当前应该运行哪个进程;
- 如果完整执行完若干个调度周期,则
- 实际运行时间:
- 用红黑树记录当前各个进程的虚拟运行时间:
- 调度时,选择红黑树中虚拟运行时间最小的进程(表明该进程比别的进程更少运行);
- 按照其权重决定的时间片(实际运行时间)运行该进程;
- 更新该进程的总实际运行时间和对应的虚拟运行时间,重新放入红黑树等待下一次调度;
- 参考:
4. 线程和进程的区别
-
线程和进程之间的本质区别:
- 是否共享虚拟地址空间;
-
线程与进程的最大区别:
- 线程是CPU调度的基本单位,进程是资源分配的基本单位;
三、磁盘管理
1. 磁盘寻道算法
1.1 先来先服务
- 机制:
- 优先寻道先到达的请求;
1.2 最短寻道时间优先
- 机制:
- 优先寻道从当前磁头位置开始寻道的时间最短的请求;
1.3 SCAN算法
- 扫描算法;
- 机制:
- 从一端扫描到另一端,再返回扫描;
1.4 CSCAN算法
- 循环扫描算法;
- 机制:
- 扫描到一端后,不是从该端返回扫描,而是重新从头开始扫描;
1.5 LOOK算法
- 机制:
- 从一端扫描到另一端,再返回扫描;
- 并不需要扫描到一端尽头,而是在该方向上的最后一个请求处立即返回;
1.6 CLOOK算法
- 循环扫描算法;
- 机制:
- 扫描到一端后,不是从该端返回扫描,而是重新从头开始扫描;
- 并不需要扫描到一端尽头,而是在该方向上的最后一个请求后立即重新从头开始扫描;