复习资料一:操作系统复习1
复习资料二:操作系统复习2
目录
八、 内存
8.1 基本背景
内存管理的必要性:
- 程序必须装入内存才能被执行
- CPU可以直接访问的存储器只有主存和寄存器
- 寄存器通常可以在一个(或少于一个)CPU时钟周期内完成访问
- 完成主存访问可能需要多个CPU时钟周期
- CPU暂停(Stall):在读取内存数据时,CPU空闲
- Cache 位于主存和CPU寄存器之间,协调速度差异
- 内存保护需要保证正确的操作
8.1.1 合法性验证
内存空间在执行进程时独立运行,在计算机系统中,为了保证程序在执行过程中不会访问到非法的内存空间,CPU会利用基址寄存器(Base)和界限寄存器(Limit)来进行地址合法性验证。
- 基址寄存器( Base)是进程最小的合法物理内存地址;
- 界限寄存器(Limit)是进程地址的长度
当CPU执行指令时,会涉及到内存访问操作,这时会进行以下地址合法性验证的过程:
-
访问指令:当CPU执行一个访存指令(比如读取或写入内存的指令)时,会同时携带着一个相对地址。
-
基址和界限检查:CPU会将相对地址与基址寄存器中的值相加,得到实际的物理地址。然后,它会将得到的物理地址与界限寄存器中的值进行比较。
- 如果物理地址小于界限,说明访问合法,可以继续执行。
- 如果物理地址大于等于界限,说明访问非法,会产生一个异常,通常是内存访问越界异常。
这个过程保证了进程只能访问到它所拥有的合法地址空间,防止了程序越界访问其他进程的内存或系统内核的内存,从而保证了系统的安全性和稳定性。
8.1.2 指令/数据绑定
指令和数据绑定到内存地址可在三个不同阶段:
1. 编译时期( Compile time)
- 如果内存位置已知,可生成绝对代码。
- 如果开始位置改变,需要重新编译代码。
2. 加载时期( Load time)
- 如果存储位置在编译时不知,则必须生成可重定位( relocatable )代码。
3. 执行时期( Execution time)
- 如果进程执行时可在内存移动,则地址绑定可延迟到运行时。
- 需要硬件对地址映射的支持(例如基址和限长寄存器)。
8.1.3 逻辑/物理地址
逻辑地址空间的概念同物理地址空间相关联,它是内存管理的核心内容。
- 逻辑地址 Logical address,由CPU产生,在进程内的相对地址,也称虚拟地址或程序地址
- 物理地址 Physical addres,内存地址所有内存统一编址,也称绝对地址、实地址
内存管理单元(MMU)
MMU是计算机系统中的一个重要组件,主要负责将CPU生成的逻辑地址转换为物理地址,以实现内存的管理和保护。MMU可以在地址转换的过程中协调处理缓存(Cache)的访问,确保数据的一致性。
8.1.4 动态加载/链接
动态加载:在程序运行时,根据需要将模块(通常是代码或资源)从磁盘加载到内存中。
优点:
- 没有被使用的例程不被载入,更好的内存空间利用率
- 当需大量代码来处理不经常使用的功能时非常有用
动态链接:在动态链接中,程序在运行时会动态地加载需要的函数和库,这些函数和库在磁盘上单独存储。当程序执行到需要使用这些函数或库的时候,操作系统会将它们加载到内存中,然后将程序与它们连接起来。
优点:
- 节省内存:多个程序可以共享同一个库,节省了内存占用。
- 简化更新:如果库需要更新,只需替换单个库文件,而不需要重新编译整个程序。
- 减少可执行文件的大小:因为不需要将所有依赖的代码都打包进可执行文件中,所以可执行文件会相对较小。
缺点:
- 在程序启动时需要额外的时间来加载和链接动态库,可能会稍微增加启动时间。
- 如果某个程序依赖的库在系统中不存在或版本不匹配,可能会导致运行时错误。
区别 | 动态加载 | 动态链接 |
目的 | 根据需要在运行时加载模块,可以选择性地装载或卸载模块 | 在程序运行时将程序与所需的库连接起来,以使得程序能够调用库中的函数和资源,节省内存并允许库的独立更新 |
实现方式 | 由程序自己通过系统调用或加载器实现 | 由操作系统或运行时环境提供的动态链接器来实现 |
8.2 内存分配
内存分配(分区)是指将物理内存划分成不同的区域,用于存储运行程序的代码和数据。在内存分区管理中,常见的策略包括单一连续分配、固定分区分配和可变分区分配。
8.2.1 分配方式
比较 | 单一连续 | 固定分区 | 可变分区 |
分配方式 | 单道程序环境下,整个内存的用户空间由程序独占 | 多道程序管理,把可分配的主存空间分割成若干个连续区,分区大小固定,不同分区之间大小可以不同。一个分区一个程序 | 当一个进程到来的时候,它将从一个足够容纳它分区中分配内存 |
优点 | 1.实现简单 2.内存利用率高:不会出现内存碎片 | 1.支持多道程序设计,允许多个程序同时运行 2.简单实现 | 1.灵活性高:允许根据程序的大小动态地分配内存,不会浪费内存 2.支持多道程序设计:可以同时运行多个程序 |
缺点 | 1.限制程序大小:只能运行单一程序 2.浪费内存:如果程序的大小远小于分配的内存空间,会导致内存浪费 3.无法利用多道程序设计 | 1.内存利用率低:会出现内部碎片,因为每个分区的大小是固定的,如果程序的大小小于分配的区域,会导致内部碎片。 2.分区划分不灵活:分区大小预先确定,可能会导致某些程序无法运行或者浪费了内存 | 1.容易产生碎片:会出现外部碎片,因为分配的内存空间是动态的,当释放一部分内存后,可能会产生不连续的小块空闲内存,难以分配给大程序 2.实现复杂:需要采用分配算法来动态管理内存 |
8.2.2 分配算法
- 首次适应(First-fit): 分配最先找到的合适的分区
- 循环首次适应算法:从上次分配开始,循环查找可用的分区,避免了每次都从头开始查找。
- 最佳适应(Best-fit): 搜索整个列表,找到适合条件的最小的分区进行分配
- 最差适应(Worst-fit): 搜索整个列表,寻找最大的分区进行分配
在速度和存储空间的利用上,首次适应和最佳适应要好于最差适应。
8.2.3 碎片回收
在内存分配过程中未被利用的空间称为碎片。根据碎片形成的原因可分为外碎片(External Fragmentation)和内碎片(Internal Fragmentation)。
外碎片:
-
定义:外碎片是指内存中存在的一些不连续的小块空闲内存,它们总和足够分配给一个新的进程,但由于分散在内存中,无法被利用。
-
产生原因:外碎片通常是由于先前分配的内存块被释放,但它们的位置和大小不能完全满足新的内存请求,导致了一些不连续的小块空闲内存。
-
解决方法:
- 紧凑(Compaction):对内存进行整理,将碎片的小块内存合并成一个大块,以便容纳新的请求。这个操作需要花费额外的时间和资源。
- 动态分区分配:采用可变分区分配策略,动态地分配内存,减少了外碎片的产生。
内碎片:
-
定义:内碎片是指已经分配给进程的内存块中,有一部分空间没有被利用,但无法分配给其他进程。
-
产生原因:内碎片通常是由于分配给进程的内存块大于进程实际需要的内存大小,导致了一部分内存被浪费。
-
解决方法:
- 适当的分配策略:选择合适的内存分配策略,避免给进程分配过大的内存块。
- 内存池技术:将内存分成固定大小的块,按需分配,避免浪费。
回收内存存在4种情况
- a) 回收内存块前后无空闲块
- b) 回收内存块前有后无空闲块
- c) 回收内存块前无后有空闲块
- d) 回收内存块前后均有空闲块
8.3 内存管理
8.3.1 分页内存管理
分页内存管理将物理内存和逻辑内存分成固定大小的块,称为页(Page)。每个页的大小是固定的,通常为2的幂次方,例如4KB。
分页操作:
-
将逻辑地址空间和物理地址空间分割成相等大小的页,每页大小固定。
-
操作系统维护一个页表,它记录了每个逻辑页与物理页的对应关系。
-
页表:页表是一个数据结构,用于建立逻辑页与物理页之间的映射关系。通常,页表是一个数组,每个表项包括逻辑页号和对应的物理页号。
-
页表项:每个页表项包括逻辑页号和物理页号的对应关系,以及一些控制位(如访问权限、脏位等)。
地址转换过程:
-
程序访问逻辑地址:程序使用逻辑地址进行内存访问。
-
逻辑地址分解:将逻辑地址分解成两部分,高位表示页号,低位表示页内偏移。
-
查找页表:根据逻辑页号在页表中查找对应的物理页号。
-
物理地址生成:将找到的物理页号和页内偏移组合成物理地址。
-
访问内存:通过物理地址访问实际的内存位置。
优点 |
|
缺点 |
|
转换后备缓冲器(Translation Lookaside Buffer,TLB)
TLB是一个硬件高速缓存,用于存储最近的地址映射,以加速逻辑地址到物理地址的转换过程。TLB存储了页表中的部分映射,减少了对页表的访问次数。
当CPU执行一个指令时,会携带着逻辑地址进行访存操作。首先,CPU会查询TLB,看是否已经缓存了逻辑地址到物理地址的映射。
- 如果在TLB中找到了对应的映射,就可以直接从TLB中取得物理地址,避免了访问页表的开销。
- 如果在TLB中未找到对应的映射,CPU会向页表发起访问请求,将映射关系加载到TLB中,然后再次进行地址转换。
TLB更新策略:
全关联 | TLB中的每一个表项可以与所有的逻辑页号匹配,但相应的比较和查找操作开销较大。 |
直接映射 | 每一个逻辑页号只能匹配到TLB中的一个特定位置,提高了查询速度但会增加冲突。 |
组相联 | 介于全关联和直接映射之间,将TLB划分成多个组,每组内的表项可以相互匹配。 |
有效访问时间: EAT = λ (a + b) + (1 – λ) (a+2b)
a:查找需要的时间;b:内存一次存取需要的时间;λ:TLB命中率
页共享:指多个进程可以将相同的物理页映射到各自的地址空间中,从而实现共享内存的目的。这使得多个进程可以同时访问相同的数据,从而提高了程序间通信的效率和灵活性。
8.3.2 页表结构
典型的页表结构包括以下字段:
-
虚拟页号(VPN,Virtual Page Number):用于索引页表,表示逻辑地址空间中的页号。
-
物理页号(PFN,Physical Frame Number):表示虚拟页号对应的实际物理页号。
-
控制位(Access Bits):包括访问权限、脏位等信息。
-
其他管理信息,如缓存位、驻留位等。
层次页表 Hierarchical | 哈希页表 Hashed | 反向页表 Inverted | |
原理 | 将整个虚拟地址空间划分成多个层次结构,每一级的页表负责管理对应层次的地址 | 使用散列函数将虚拟页号映射到物理页号。 | 将物理页号作为索引,每个表项记录了物理页号、进程标识、虚拟页号等信息。 |
优点 |
|
|
|
缺点 |
|
|
|
32位逻辑地址、页大小4KB的页表结构应该怎么从逻辑地址映射到物理地址?
以下是从逻辑地址到物理地址的映射过程:
-
确定页偏移(Offset):
- 由于页的大小是4KB,也就是2^12,所以低12位用于表示页内偏移。
- 例如,逻辑地址为
0x12345678
,则页内偏移为0x678
。
-
确定页号(Page Number):
- 剩余的高位用于表示页号。
- 32位地址减去低12位的偏移,剩下的20位用于表示页号。
- 例如,逻辑地址为
0x12345678
,则页号为0x12345
。
-
查找页表:
- 使用页号在页表中查找对应的页表项(Page Table Entry,PTE)。
- 页表项记录了该虚拟页对应的物理页号和一些控制信息。
-
获取物理页号:
- 从页表项中获取物理页号。
-
组合成物理地址:
- 将物理页号和页内偏移组合成物理地址。
假设页表的页表项大小为4字节,每个页表可以管理2^10 = 1024 个页,那么页表的大小就是4KB(一个页)。假设存在多级页表结构,每个页表指向下一级页表或页表项。
例如,要将逻辑地址 0x12345678
映射到物理地址,可以按照以下步骤:
- 获取页内偏移:
0x678
- 获取一级页表索引(高10位):
0x12345
- 通过一级页表找到对应的二级页表
- 获取二级页表索引(中间10位):假设二级页表的索引为
0x234
- 通过二级页表找到对应的页表项
- 获取物理页号
- 将物理页号与页内偏移组合成物理地址
8.3.3 分段内存管理
分段内存管理将程序的逻辑地址空间划分为多个不同大小的段,每个段可以包含一个独立的模块或数据结构。每个段都有一个基地址和长度,用于确定段在物理内存中的位置。
在分段机制中,因为不同的段具有不同的长度,动态存储分配变得更加灵活。
分段表:
-
在分段机制中,通常会维护一个分段表,记录了每个段的基地址、长度和其他控制信息。
-
当程序需要动态分配内存时,可以在分段表中找到一个合适的段来分配空间。
分段内存分配算法
-
首次适应算法:可能会导致内存碎片。
-
循环首次适应算法:避免了每次都从头开始查找。
-
最佳适应算法:选择大小最接近需求的段进行分配,可以减少内存碎片,但可能会产生更多的碎片。
-
最坏适应算法:选择最大的可用段进行分配,可能会产生大量的内存碎片。
碎片清除:
- 合并相邻段:为了减少外部碎片,可以尝试将相邻的空闲段合并成一个大的空闲段。
- 碎片整理:定期执行内存碎片整理操作,将分散的小段合并成更大的段,以提高内存的利用率。
在分段内存管理中,共享机制允许多个进程共享同一段的内容,从而节省内存资源并提高程序的运行效率。
共享的应用场景:
-
共享代码段:多个进程可以共享相同的代码,减少内存占用。
-
共享只读数据段:多个进程可以共享只读数据,例如共享的配置信息。
-
共享某些资源的访问权限:多个进程可以共享一个锁或信号量等,以协调对共享资源的访问。
8.3.4 内存扩容
- 覆盖技术Overlaying
只在内存中保留那些在任何时间都需要的指令和数据,程序的不同部分在内存中相互替换。
- 交换技术Swapping
一个进程可以暂时被交换(swap)到内存外的一个备份区,随后可以被换回内存继续执行。
- 虚拟内存Virtual Memory
将磁盘空间作为辅助内存,使得程序能够在物理内存不足的情况下继续运行。
九、 虚拟内存
9.1 基本背景
虚拟存储技术是一种计算机系统中的内存管理方法,它允许程序在物理内存不足的情况下继续运行,通过将部分数据暂时存储在磁盘上来扩展可用内存空间。
目的:在有限的物理内存资源下,使得程序能够运行更大规模的程序或者同时运行多个程序。
功能 :
-
扩展内存空间:允许程序使用比物理内存更大的地址空间。
-
多任务操作:使得多个程序可以同时在有限的物理内存中运行,通过合理的页面置换策略,实现了多任务操作。
-
提高程序运行效率:通过合理的页面置换策略,使得正在执行的程序所需的数据常驻在物理内存中,从而提高程序的运行效率。
局部性原理: 1968年,Denning指出:程序在执行时将呈现出局部性规律,即在一较短的时间内,程序的执行仅局限于某个部分;相应地,它所访问的存储空间也局限于某个区域.
概念定义:
- 虚拟地址空间:分配给进程的虚拟内存
- 虚拟地址:在虚拟内存中指令或数据的位置
- 虚拟内存:把内存和磁盘有机结合起来使用,得到一个容量很大的“内存”,即虚存
决定虚拟存储器大小的两个因素:操作系统字长、内存外存容量和
实现方式:
- 虚拟页式(虚拟存储技术+页式存储管理):请求分页、预调页
- 虚拟段式(虚拟存储技术+段式存储管理)
9.2 虚拟页式
虚拟页式存储管理将物理内存划分为固定大小的块(称为页框),将逻辑内存划分为相同大小的块(称为虚拟页),并且在需要时将虚拟页映射到物理页框上。
基本思想:
- 进程开始运行之前,不是装入全部页面,而是装入一个或零个页面
- 运行之后,根据进程运行需要,动态装入其他页面
- 当内存空间已满,而又需要装入新的页面时,则根据某种算法置换内存中的某个页面,以便装入新的页面
9.2.1 请求分页(按需调页)
"请求分页" 是指在程序运行时,如果发现需要的虚拟页不在物理内存中,会触发一个缺页异常(Page Fault),操作系统需要将相应的虚拟页从磁盘加载到物理内存中。
缺页处理流程:
-
步骤1:中断处理:
- CPU会产生一个缺页异常,操作系统将接管控制权。
-
步骤2:查找虚拟页:
- 操作系统会检查页表,查看要访问的虚拟页是否已经在物理内存中。
-
步骤3:虚拟页不在内存:
- 如果虚拟页不在物理内存中,操作系统会执行以下步骤:
- 选择一个物理页框来存储将要加载的虚拟页。
- 如果选择的物理页框已经被占用,需要选择一个牺牲页(Victim Page)来进行替换,通常会使用页面置换算法(如LRU)来选择牺牲页。
- 将要加载的虚拟页从磁盘读取到物理页框中。
- 更新页表,将虚拟页与物理页框建立映射关系。
- 如果虚拟页不在物理内存中,操作系统会执行以下步骤:
-
步骤4:恢复程序执行:
- 一旦虚拟页被成功加载到物理内存,程序的执行将会在中断之前被打断的地方继续进行。
写回策略:如果发生了页面置换,且替换的页被修改过,通常需要将被替换的页写回到磁盘。
请求分页的性能讨论:
- 缺页率(缺页的概率):0 <= p <= 1.0 (p=0,无缺页;p=1,每次访问都缺页)
- EAT = (1 – p) * 内存访问时间 + p * 页错误时间
- 页错误时间 = 处理缺页中断 + 页交换出去时间 + 读入页时间 + 重启进程开销
性能优化:为了提高性能,操作系统通常会使用缓存和预读技术来优化磁盘的读取操作。
9.2.2 页面置换
在物理内存不足时,选择一个页面将其从物理内存中换出,以便为新的页面腾出空间。
页面置换步骤:
-
物理内存不足:当物理内存不足以容纳当前运行程序所需的所有页面时,需要选择一个页面将其从物理内存中置换出去,以腾出空间。
-
选择牺牲页面:
- 选择哪个页面被置换出去是一个关键决策。这通常会使用页面置换算法来决定,常见的算法包括:
- 最近最少使用(LRU):选择最长时间未被使用的页面进行置换。
- 最不经常使用(LFU):选择被访问次数最少的页面进行置换。
- 先进先出(FIFO):选择最早进入物理内存的页面进行置换。
- 最优置换置换算法(OPT):选择将来不再需要的或最远的将来才会被使用的页
- 选择哪个页面被置换出去是一个关键决策。这通常会使用页面置换算法来决定,常见的算法包括:
-
更新页表:页面置换涉及到更新页表,将被置换出的页面的映射关系从页表中移除,同时将新页面的映射关系添加到页表中。
-
写回磁盘:如果被置换的页面被修改过(脏页),通常需要将其写回磁盘,以保证数据的一致性。
- 先进先出(FIFO)算法:置换最早进入物理内存的页面
Belady异常(Belady's Anomaly)是指在某些情况下,增加物理内存的数量反而会导致缺页次数增加的现象。这种现象违反了直觉,因为通常来说,增加物理内存应该能够减少缺页次数,提升程序的性能。FIFO算法中,先进入物理内存的页面最先被淘汰。在某些情况下,增加物理内存可能会导致更早进入内存的页面被淘汰,从而增加了缺页次数。
- 最优置换(OPT)算法: 置换将来不再需要的或最远的将来才会被使用的页
- 最近最少使用(LRU)算法:置换最长时间未被使用的页面
- 最不经常使用(LFU)算法:置换访问次数最少的页面
- 如果访问位为0,直接置换
- 如果将要交换的页访问位是1,则把访问位设位0,把页留在内存中
- 以同样的规则,替换下一个页
9.2.3 页框分配
页框的分配必须满足每个进程所需要最少的页数。
- 固定分配:
- 平均分配:每个进程分得的页框数相同
- 按比率分配:根据每个进程的大小来分配
- 优先级分配:按照进程优先级分配页框
页框置换:
- 全局置换:进程在所有的页框中选择一个替换页面;一个进程可以从另一个进程中获得页框。
- 局部置换:每个进程只从属于它自己的页框中选择。
颠簸Thrashing 一个进程的页面经常换入换出:
如果一个进程没有足够的页,那么缺页率将很高,这将导致:
- CPU利用率低下.
- 操作系统认为需要增加多道程序的道数
- 系统中将加入一个新的进程
工作集(Working-Set)模型:
Δ 工作集窗口
固定数目的页的引用
WSi(进程Pi的工作集) = 最近Δ中所有页的引用 (t)
工作集大小:
- 如果 Δ 太小,那么它不能包含整个局部
- 如果 Δ 太大,那么它可能包含多个局部
- 如果 Δ = ∞ ,那么工作集合为进程执行所接触到的所有页的集合
缺页率(PFF)策略:
设置可接受的缺页率:
- 如果缺页率太低,回收一些进程的页框
- 如果缺页率太高,就分给进程一些页框
9.3 内核内存分配
内核内存分配是OS内核管理和分配物理内存的过程。它涉及到为内核数据结构、驱动程序和其他内核组件分配合适的内存区域,以保证内核正常运行。
内核内存分配通常采用以下几种算法:
-
伙伴系统(Buddy System):将内存分割为大小相等的块,以2的幂次方为基准,以便在分配和释放时快速找到合适的块。
-
SLAB分配器:将内存划分为多个SLAB(固定大小的内存块),每个SLAB可以用于分配特定大小的内存对象,提高了内存分配的效率。
-
SLUB分配器:在SLAB分配器的基础上进行了改进,简化了内部数据结构,提高了性能。
内核在使用内存块时有如下特点:
- 内存块的尺寸比较小;
- 占用内存块的时间比较短;
- 要求快速完成分配和回收;
- 不参与交换。
- 频繁使用尺寸相同的内存块,存放同一结构的数据;
- 要求动态分配和回收。
9.3.1 伙伴(Buddy)算法
首先将整个可用空间看作一块: 2^n。假设进程申请的空间大小为s,如果满足:,则分配整个块;否则,将块划分为两个大小相等的伙伴,大小为2^n-1,一直划分下去直到产生大于或等于s的最小块。
9.3.2 Slab 分配
SLAB分配通常在Linux内核中被广泛应用,特别是在管理内核中的数据结构、文件系统缓存、网络协议栈等方面。SLAB分配器将内存池划分为大小相等的SLAB(固定大小的内存块),每个SLAB可以用于分配特定大小的内存对象。
工作方式:
- SLAB分配器会在内存池中预先分配一定数量的SLAB,每个SLAB用于分配特定大小的内存对象。
- 当内核需要分配特定大小的内存时,SLAB分配器会从相应大小的SLAB中分配一个内存对象,如果SLAB中没有足够的空闲内存单元,就会分配一个新的SLAB。
- 当内存对象不再需要时,它会被返回到相应大小的SLAB中,以便重复利用。
优点:
-
减少内存碎片:SLAB分配器通过预先划分固定大小的内存块,减少了内存碎片的产生。
-
提高内存分配效率:由于SLAB已经划分为固定大小的内存块,分配和释放操作更加高效。
-
减轻内存管理的负担:SLAB分配器可以有效地管理内存池,减轻了内核内存管理的负担。
缺点:
-
可能导致内存浪费:如果内核中有许多不同大小的内存对象,使用SLAB分配器可能会导致部分SLAB内存浪费。
-
不适用于所有情况:SLAB分配器适用于某些特定的内存分配场景,但并不是所有情况下都是最优选择。
复习资料四:操作系统复习4