《操作系统导论》笔记(二)内存的虚拟化

本文详细介绍了内存虚拟化的概念,包括地址转换的动态重定位方法,以及分段和分页机制。动态重定位利用基址和界址寄存器实现地址转换,分段解决了内部碎片问题,而分页则进一步解决了外部碎片,通过页表和TLB加速地址转换。此外,还讨论了多级页表和与外存交互的交换空间策略。

摘要生成于 C知道 ,由 DeepSeek-R1 满血版支持, 前往体验 >

1、内存虚拟化的概念

程序以进程的形式运行,每个进程有片专门用于该进程的地址空间,该地址空间包含运行的程序的所有内存状态。该地址空间主要包含 程序代码、堆和栈三个部分,如图13.3所示:
在这里插入图片描述
这里这个进程地址空间为16KB,实际上一般不止这么大,比如32位地址空间。所谓虚拟化内存,就是指运行中的进程认为自己享有一片私有的、很大的地址空间,且空间地址从0开始;但实际上进程并不运行在以地址0开始的内存空间中,而是以其他地址开始的空间,并且不是一个进程独享内存,而是许多进程共享内存。

虚拟化内存是由OS和硬件共同完成的,对用户级程序员和运行的程序是透明的。我们在程序中,比如打印出来的某个指针指向的地址,都是虚拟的地址,都是程序所认为的其独享的以0开始的地址。实际上,作为用户级别的程序员,能看见的所有地址都是虚拟地址。

2、实现内存虚拟化的机制:地址转换

我们在程序中写的地址、所看的地址,都是虚拟地址,将由操作系统和硬件翻译成实际物理地址,这种机制叫地址转换。

2.1、一些假设

假设1:假设每个进程的地址空间必须连续的存放在物理内存中
假设2:每个进程地址空间不是很大,小于物理内存的大小(比如这里的16KB)
假设3:假设每个进程所需的进程地址空间大小完全一样(比如都是16KB)

2.2、进程地址空间在内存中可能的样子

在这里插入图片描述
图15.1是进程自己认为的地址,15.2是它实际在内存中的位置。

问题:如何知道某个进程地址空间具体被放在内存中的位置?

2.3、硬件工作:动态重定位(基址加界址)

硬件引入两个寄存器,基址寄存器和界址寄存器。OS在将进程的全部内容从外存引入到内存时,将其在物理地址空间的首地址放入基址寄存器,比如图15.2就该放入32KB,将进程地址空间的大小放入界址寄存器,比如16KB。虚拟地址到物理地址的转换由以下公式实现:

物理地址=虚拟地址+基址寄存器的内容

界址寄存器提供了访问保护,不允许程序访问其进程地址空间以外的内容。这种地址重定位是在程序运行时发生的,而且我们甚至可以在进程开始运行后改变其地址空间,这种技术称为动态地址重定位

这种基址加界址寄存器的硬件结构在CPU中,每个CPU一对。我们将CPU中负责地址转换的部分称为MMU。

2.4、OS的工作

(1)进程创建时,OS要决定将进程的地址空间分配到内存中的哪个区域中。解决方法:由于假设每个进程所需的进程地址空间大小完全一样,可以把内存分为进程地址空间大小的n个块,用一个类似于数组的东西记录内存中每个块是否已经被使用,然后选一个空闲的给将创建的进程。
(2)进程终止时,把内存回收,同时更新空闲列表
(3)在进行进程切换时,保存上下文,即将原本执行的进程的基址寄存器和界址寄存器内容保存在内存中,放在某种每个进程都有的结构中,比如进程控制块(PCB);同时将马上要执行的进程的基址寄存器和界址寄存器内容写入对应寄存器。

3、实现内存虚拟化的机制:分段

观察如13.3和图15.1,会发现除了程序代码、堆和栈三个部分外,进程空间中还有一大部分是未使用的,如果将整个进程地址空间都一块一块的放入内存,实际上有一部分是未使用的,造成了内存浪费,这个浪费的地方称为内部碎片

3.1、何为分段

在MMU中引入不止一个基址和界址寄存器对,而是给进程空间中的每一个逻辑段都来一对。将进程空间分为 代码段、栈和堆段,运用基址和界址技术,将各个段放到不同的内存区域,从而避免了虚拟地址空间中未使用的部分占用内存。

3.2、如何确定虚拟地址属于哪个段?

方法:用虚拟地址的前几位来区分该虚拟地址具体指的哪个段。比如我们有代码段、栈和堆段,则将虚拟地址的前两位用于区别。

3.3、分段的缺点

由于各个段大小不一定相同,物理内存中容易充满许多空闲的小内存块,这些小块占据了相当的内存,但由于其一般很小,导致很难将这些小内存块分配给新的段,这就造成了内存的浪费。这些小内存块称为外部碎片。进而导致了各种复杂的空闲内存空间管理算法(17章),一个更直接的方法就是不分配大小不同的内存块,也即是第18章的分页

4、实现内存虚拟化的机制:分页

4.1、何为分页

为了避免分段带来的外部碎片,将进程地址空间(虚拟内存)划分为大小相同的块,将物理内存划分为同样大小的块,分别称为虚拟页和物理页,如图所示:
在这里插入图片描述

4.2、页表

为了记录进程空间的每个虚拟页放在物理内存中的位置,OS为每个进程保存一个数据结构,称为页表。 对于图18.1和图18.2所示的例子,页表中就应该有四个条目:虚拟页0→物理页帧3、VP1→PF7、VP2→PF5、VP3→PF2。

4.2.1、基于页表的地址转换

对于图18.1和图18.2所示的例子,虚拟地址空间有64字节,一共分为4个虚拟页,每个虚拟页为16字节。遍历64字节的虚拟地址空间需要6位二进制来表示地址:
在这里插入图片描述
其中前两位用于表示4个虚拟页,后四位用于确定每一个页中的地址,即16字节空间。
在这里插入图片描述
对于图18.1和图18.2所示的例子,假设现在有虚拟地址010101,那么前两位表示第一个虚拟页,查询页表发现其对应于第七个物理页,则物理页号为111,后四位页内偏移量0101保持不变,则实际的物理地址为1110101。

4.2.2、页表的大小及存放位置

实际中一个进程空间往往为32位甚至64位,而不是仅仅只有64字节。假设将32位的虚拟地址空间分为大小为4KB的页,则一共有2^20个页,及32位的虚拟地址分为20位的VPN和12位的偏移量。
那么这个进程的页表需要2^20个页表格条目(PTE),假设每个PTE需要4个字节,则页表需要4MB。这只是一个进程,多个进程的页表加起来所占的内存空间将会非常大。
一般页表存储在内存中,或者交换到外存中。页表过大的问题也是引发后面的多级页表出现的一个原因。

4.2.3、页表中的具体内容

页表就是一种数据结构,用于将虚拟地址(或实际上,是虚拟页号)映射到物理地址(物理页号)。最简单的页表为线性页表,就是一个数组。操作系统通过VPN检索该数组,并在该索引处找到该VPN对应的页表项(PTE)。
至于每个PTE中的内容,就是一串二进制位,包含有:有效位、保护位、存在位、修改位(脏位)、访问位等等,最后当然还有对应的物理页号(PFN)。有效位:通常用于指示特定地址转换是否有效。例如:当一个程序开始运行时,它的代码和堆在其地址空间的一端,栈在另一端。所有未使用的中间空间都将被标记为无效。有效位实现了分段的目的,即避免太多的内部碎片,有效位对支持稀疏地址空间至关重要,通过简单地将地址空间中所有当前未使用的页面标记为无效(问题在于原本没用的页可能随着堆和栈的增长而被使用,怎么动态辨别呢),我们不需要为这些页面分配物理帧,从而节省大量内存。存在位:表示该页是在内存还是在磁盘上,存在位有助于我们研究如何将部分地址空间交换到磁盘,从而支持大于内存的地址空间。

4.3、目前分页的缺点

(1)由于页表在内存中,进程每一次访问某个内存地址时,需要先找到该进程对应的页表,从页表中读出PTE,转换为物理地址,这是需要CPU(也就是操作系统)完成的。(前面讲的基于基址和界址寄存器的方法在地址转换时全部由硬件完成,不需要CPU干预)得到物理地址后,再次访问内存,进行相应的操作。因此相当于进行了两次的内存访问操作,降低了速度。
(2)页表太大,挤占内存。
缺点(1)导致了TLB的出现,缺点(2)导致了多级页表的出现。

4.4、快速地址转换:TLB

4.4.1、TLB的基本概念

为了解决每次内存操作需要两次访问内存的问题,增加一个硬件设施,称为地址转换缓存:TLB。对每次内存访问,由硬件先检查TLB,看看其中是否有期望的地址转换映射,如果有,就完成转换(TLB在CPU附近,速度很快),而不用再到内存中去访问页表。.
TLB中存放有经常用到的地址转换映射,TLB本质上是一种硬件缓存,有点类似于CPU和内存之间的Cache缓存,只是两者缓存的内容不同,Cache缓存的是数据和指令,TLB缓存的地址转换。大部分硬件缓存都有以下特点:
(1)速度很快,但容量很小。这是材料和物理特性决定的。
(2)由于容量小,其中只存放经常使用的部分,进而导致有“缓存命中”和“缓存未命中”两种情况。
(3)所谓的“经常使用的部分”,都是基于局部性原理,时间和空间的局部性,我们在编写程序的时候要注意迎合这种局部性。

4.4.2、TLB的具体流程

发生内存操作时,虚拟地址给出VPN,硬件检测TLB中有没有对应的VPN,有则TLB命中,得到物理地址,如果未命中,则去内存页表中去找,找到后将对应的地址映射更新到TLB中,下次再用(很有可能马上就会再用,局部性)时就能TLB命中了。

4.4.3、谁来处理TLB未命中

如果是CISC的机器,则硬件处理TLB的未命中;如果是RISC,则是软件:TLB未命中时,硬件抛出异常,转到异常处理程序,程序中去内存找到地址并更新TLB,然后返回,最后硬件再次查找TLB(此时就会命中)。

4.4.4、TLB的具体内容

虚拟页号(VPN)+对应的物理页帧(PFN)+一些辅助位

辅助位中有一位叫做有效位,TLB的有效位跟页表的有效位有区别。页表中有效位是区别该页有没有被进程所使用,TLB中的有效位是区别该TLB表项是否对应当前进程,这个在4.4.5的进程切换时有用,也就是说每次运行一个新的进程时,前几次基本上都是TLB未命中的,后面才慢慢更新。

4.4.5、进程间切换时的TLB处理

需要注意的一点是,一般只有一个TLB,其中存放的是当前正在运行的进程的地址转换。当发生进程切换时,需要对TLB中的内容进行更新。主要手段是TLB中的有效位,当切换新进程时,将原来进程的表项的有效位置为无效状态。

4.4.6、TLB的替换策略

在未命中后,需要更新TLB,如果TLB已经满了,将谁覆盖掉呢? 待写

4.5、多级页表

为了解决页表占用太多内存的问题,引入了多级页表,在将多级页表之前,先讲一些其他的致力于解决这个问题的方法。

4.5.1、使用更大的页

直白的方法,把每个页的大小变大,那么总页数就下降了,页表也就小了。缺点:页太大,出现内部碎片,也就是说每个页内部的空间不能够被充分应用,所以不被采用。一般都是几个KB大小的页。

4.5.2、将分段和分页混合

传统分页是对进程的整个地址空间进行划分虚拟页,然后对应物理页帧,形成页表。从图15.1可以看出,实际上地址空间中有很多的空闲空间,没有必要为这些空间安排页表项(问题:既然老是说中间是空闲的,为什么当初设置进程地址空间的时候要设置成这么大呢?直接设置得小一点不就得了)。
本节的方法是:不为进程的整个地址空间提供单个页表,而是为每个逻辑分段各提供一个(代码,堆,栈),每个段仍然有基址寄存器和界址寄存器,只不过基址寄存器中存的是对应页表在内存中的起始地址,界址寄存器存放的是页表的范围。
方法的缺点:由于每个段的大小不同,所以每个段的页表大小也不同,由于页表必须连续存放,虽然物理内存被划分为了大小相同的页,但是由于页表大小不同,虽然都是单个页的整数倍,还是会由页表导致出现外部碎片。

4.5.3、多级页表的基本概念

在这里插入图片描述
多级页表和之前的混合分页分段一样,都是致力于解决进程空间中没有用到的部分所对应的页表项目在内存中的占用。多级页表基本思想如下:首先,将进程空间对应的整个页表分成页大小的单元,一个单元中包含若干个页表项PTE;然后,如果整页(整个单元)的PTE都是无效(通过页表的有效位来判别),则不分配该页的页表(意思将其移除内存)。为了追踪页表的各个页(单元)是否有效(全部PTE无效就是整个单元无效),引入了一个称为页目录的新结构,从页目录可以知道整个页表的哪些页是有效的,以及其具体位置,也能知道哪些页是无效的。这样做还可以将整个页表分散存储到内存,避免了寻找一块连续内存空间来存储页表。多级页表也有缺点,因为页目录也是存放在内存中,如果发生TLB未命中,需要访问三次内存才能获取到数据。

图20.2左边是线性页表,将其分成了四个页大小的单元,分别存放在物理内存的201到204页。可以看到其中间的两个单元的PTE全是无效的,也就是说物理内存的202到203页中的页表其实是浪费的。图20.2右边是多级页表,页目录中国的每一项称为PDE,PDE至少包含有效位和物理页帧号PFN,PDE中的PFN是该PDE所对应的页表单元在内存中存放的位置,PDE的有效位指该单元内部的PTE是有全部无效。

4.5.4、多级页表的的地址转换

假设一个进程的地址空间为16KB,单个虚拟页大小为64B,则虚拟地址要14位,VPN8位,偏移量6位。由于VPN有8位,如果用线性页表,则有2^8一共256个PTE,假设每个PTE为4B,则整个线性页表为1KB。
由于单个页面大小为64B,则1KB的页表将分为16个页面,每个页面有16个PTE。
在这里插入图片描述
在这里插入图片描述
对于14位的虚拟地址,如果采用多级页表,将页表首先分为16个单元,则需要取虚拟地址的VPN的前4位用于标识该VPN属于所对应的PTE属于这16个单元的哪一个。
我们已经页目录的起始地址PDB,已知每个PDE的大小,则可以通过VPN的前四位找到该虚拟地址所对应的PDE。
找到PDE后,如果无效,则引发异常。如果有效,从PDE能够找到对应的页表单元,接下来通过VPN的后几位去找对应单元中的具体某个PTE。
《操作系统导论》P166有总结进程产生虚拟地址到最终读取到数据的整个流程。

5、实现内存虚拟化的机制:与外存进行交互

之前所讲的都是假设进程的地址空间小于物理内存空间,而且进程的所有页面均存放在内存中。实际不是这样,进程地址空间可以比物理内存空间更大,那么这就意味着内存中只能存放一部分的进程页面,剩下的页面存放在外存中,需要用到时再从外存传输到内存。

5.1、交换空间

  一般不是所有的外存都能用于扩展内存,一般从外存上指定一部分空间用于与内存交互,这个空间叫做交换空间。系统最初运行时,属于某个进程的物理页从外存调入内存,随着不同的进程的不断运行,会有越来越多属于不同进程的物理页从外存调入内存,操作系统为设置物理内存上界,如果实际物理内存比如被使用了80%,则由交换守护进程/页守护进程(P170)将物理内存中的某一些页替换出去,代表性的替换策略是LRU。
  总结来说,为了支持巨大进程地址空间(P165),引入了交换空间的概念,为了交换空间和内存之间的页替换,引入了LRU,当然LRU的思想适用于任何缓存替换策略。实现完整的LRU很难,操作系统一般实现的是近似LRU(P181)

### 操作系统导论学习笔记概述 操作系统是计算机系统的核心组件,它负责管理硬件资源和提供应用程序运行的环境。以下是关于《操作系统导论》课程的一些关键知识点总结[^1]: #### 一、操作系统的基本概念 操作系统可以被定义为一个程序集合,用于管理和协调计算机硬件与软件资源。它提供了用户与计算机硬件之间的接口,并确保多个程序能够高效地共享资源[^3]。 #### 、计算机系统的组成部分 从两个视角理解操作系统: 1. **硬件视角**:包括处理器(CPU)、内存(RAM)、输入/输出设备(I/O)等。 2. **软件视角**:操作系统通过抽象层将硬件资源提供给应用程序使用,从而实现对硬件的保护和高效利用。 #### 三、计算机系统组织 - **中断(Interrupt)**:用于处理外部事件,例如键盘输入或硬盘读写完成信号。 - **陷阱(Trap)**:由程序主动触发的中断,通常用于请求操作系统服务。 - **存储结构**:包括主存(RAM)、辅助存储(硬盘等)以及缓存(Cache)。 - **I/O结构**:描述输入输出设备如何与处理器交互[^1]。 #### 四、计算机系统体系结构 根据处理器的数量和分布方式,计算机系统可以分为以下几类: - **单处理器系统**:只有一个CPU。 - **多处理器系统(Parallel System)**:包含多个CPU,支持并行计算。 - **集群系统**:通过网络连接多个独立计算机协同工作。 #### 五、操作系统的演化阶段 1. **手工操作阶段**:用户直接操作硬件。 2. **批处理阶段**: - 单道批处理系统:一次只能运行一个任务。 - 多道批处理系统:允许多个任务同时驻留在内存中。 3. **分时操作系统**:允许多个用户同时使用计算机资源。 4. **实时操作系统**:满足严格的响应时间要求。 5. **网络操作系统**:支持多台计算机通过网络协作。 6. **分布式操作系统**:管理分布在不同地理位置的计算机资源。 #### 六、操作系统的主要功能模块 1. **进程管理(Process Management)**:负责创建、调度和终止进程。 2. **内存管理(Memory Management)**:分配和回收内存资源,确保多个程序之间不会相互干扰。 3. **存储管理(Storage Management)**:包括文件系统管理和大容量存储器管理。 4. **保护和安全(Protection and Security)**:防止未经授权的访问和恶意攻击[^1]。 #### 七、系统调用 系统调用是操作系统提供的接口,允许应用程序请求操作系统服务。例如,`fork()` 和 `exec()` 是常见的系统调用,用于创建新进程和执行新程序[^2]。 #### 八、红黑树在操作系统中的应用 红黑树是一种自平衡叉搜索树,具有较高的查询效率(时间复杂度为 O(log n))。在操作系统中,红黑树常用于实现有序数据结构,例如 Linux 内核中的虚拟内存管理[^4]。 ```c // 示例代码:C语言中的简单系统调用 #include <stdio.h> #include <unistd.h> int main() { pid_t pid = fork(); // 创建子进程 if (pid == 0) { // 子进程中执行 execl("/bin/ls", "ls", "-l", NULL); // 执行 ls 命令 } else if (pid > 0) { // 父进程中等待子进程结束 wait(NULL); } return 0; } ``` ###
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值