硬盘和内存、物理地址、虚拟地址、段、页、页表、页表项,多级页表的顺序梳理

从硬盘到多级页表:计算机内存地址体系的演变梳理
本文探讨了计算机内存地址体系的发展,从硬盘和内存的区别,物理地址和虚拟地址的概念,到内存分段和分页,以及多级页表的出现以解决内存浪费问题。作者通过学习过程中的思考和总结,帮助读者理解内存管理的关键概念和技术进步。

硬盘和内存、物理地址、虚拟地址、段、页、页表、页表项,多级页表的梳理

本篇blog的起因是因为在ICS2的学习过程中觉得自己对页表的理解还不够深刻,于是就想简单地梳理一下是计算机是怎么从物理地址发展到多级页表的,以大二学生的角度梳理一下知识网络。在梳理过程中也获益匪浅,这里总结成blog分享~


我们先从硬件上讲起:

1.硬盘和内存的区别

在这里插入图片描述

这张图中显示寄存器是属于CPU的一部分,对程序员来说是相对透明的,CPU读取数据的顺序是cpu-寄存器-缓存(cache,可以包括多级缓存,缓存不命中才会去内存里读取数据一般缓存是RAM)-内存-外存(硬盘),下面介绍一下内存和硬盘。

内存(RAM):承担着暂时存储CPU运算数据以及与外部存储器交换数据的任务。它是连接外部存储器和CPU的桥梁,所有程序的运行都在内存中进行。 断电后数据丢失。通常是DRAM(动态随机存储器)。

在这里插入图片描述

硬盘(又称外存):作为计算机的主要存储设备,承担着存储大部分软件和数据的任务。有容量大,断电数据不丢失的特点。也被人们称之为“数据仓库”。具体的扇面等等见ICS1。

在这里插入图片描述

这里只要搞清楚,程序无法在硬盘上直接运行,而是在内存中运行的。这里就存在一个我们在手机时代容易混淆的东西,比如一个手机是8G+256G,事实上专业上来说是他的运存(RAM)为8G,对应电脑的内存,256G为内部存储空间,对应电脑上的硬盘空间,像我们常说的一个电脑1TB,指的是硬盘空间,也就是外存,而不是内存。而内存相对较小,比如我的电脑内存就是16G。

2.物理地址

关于CPU如何通过地址总线以段地址和偏移地址去计算物理地址我这里就不详细描述了,这里放一个动态图:

在这里插入图片描述

来源:物理地址是怎么确定的?以8086为例_8086物理地址动图-优快云博客

物理地址,相当于门牌号,内存上的每个内存单位都有自己的物理地址,这个地址会对应相应的内存空间。

像cache,寄存器这些RAM也是有物理地址的,但是后面说的虚拟地址主要和内存上的物理地址有关。

磁盘里的物理地址就指的是柱面扇面之类的了,下面描述的过程是当内存中没有需要的数据时如何从磁盘中读取:

当程序要读取的数据时传入内存地址(行地址+列地址),如果数据不在主存中时,会触发一个缺页异常,此时系统会向磁盘发出读盘信号,通过柱面号,磁头号,扇区号定位磁盘位置,找到数据的起始位置并向后连续读取一页或几页载入内存中。

那这里我们讨论的物理地址就是内存的物理地址,类比就是数组的序号,本身不占储存空间,是一个硬件特征。

3.虚拟地址

我们有了物理地址那为什么又要有虚拟地址呢?,我们不得不考虑一个事情,就是:物理地址存在一个问题:

多个进程如果同时操作真实的地址内存的话,会产生冲突。 于是操作系统就提供了一种机制,让每个进程都仿佛拥有全部的内存地址,这些内存地址是虚拟的,由操作系统提供统一的方式映射到真实的物理地址。

因此就有了虚拟地址,那我们又会想一个问题,我们给一个进程分配了一段虚拟地址,那我这个进程在这段虚拟地址里怎么存储呢?虚拟地址和物理地址之间的映射关系是按什么存储的呢?(这里先讲base+size的方法)涉及内存的分段,我们接下来看一下虚拟内存的分段(segment)。

4.内存分段(segment)

众所周知(bushi),虚拟地址一般从0开始,一个进程的虚拟地址分为四段,且是四等分,按照虚拟地址的最高两位进行区分,分为四个segment,分别有不同的功能,分别为 data,code,heap,stack,各自具体存储的什么数据这里不做详细讲述,每段VA到PA就是VA+base即可,size指的是段的范围,positive为1是数据从低地址到高地址存,为0是数据从高地址倒着存到低地址。可以看更详细的博客,分段大致如图所示:

这部分会考一些base+size一些,VA到PA的映射问题。原始的映射方式。其中base和size等的映射数据储存在CPU的特定寄存器中。

在这里插入图片描述

分段后就使得程序员不需要关心具体的物理地址内存的问题(因为只要知道偏移量即可),产生了连续的内存空间,但是,很明显可以看到会存在内存碎片(灰色部分)

接下来就是要解决一下内存碎片的问题了,就需要抛弃掉base+size的方法,程序员用内存分页来解决这个问题。

4.内存分页

内存分页是为了减少内存碎片、加快内存交换的方式。

分页,顾名思义就是把整个虚拟和物理内存空间切成一段段固定尺寸的大小。每个页的大小一般是4k。 虚拟地址与物理地址之间通过页表来映射。同上理,页表的首地址仍是储存在CPU的寄存器中,不占内存。但页表本身是存储在内存中的,映射通过MMU这个硬件来根据页表操作。如图所示:

在这里插入图片描述

分页的话,页与页之间是紧密排列的,所以不会存在外部碎片。内存分页机制分配内存的最小单位是一页,即使程序不足一页大小,最少只能分配一个页,所以页内会出现内存浪费,所以针对内存分页机制会存在内部内存碎片的现象。

如果内存分页不够,操作系统会把其他正在运行的进程中的最近没有被使用的内存页给换出到硬盘上面。 一旦需要的话,再从磁盘上面换入到内存里面。由于一次性换入或者换出的只有少数几个页,所以不用花费太多的时间。

接下来我们需要深究一下页表是如何实现虚拟地址到物理地址的映射的。

5.页表

在介绍页表之前我想想介绍一下两个概念,页和页框。

  • :将进程划分的块,对应的大小就叫页面大小。
  • 页框:将内存划分的块。
  • 页和页框二者一一对应,一个页放入一个页框,(理论上)页的大小和页框的大小相等。
  • 页表:就是一个页和页框一一对应的关系表。【存放在内存中】
  • 逻辑地址:是程序编译后,生成的目标模块进行编址时都是从0号单元开始编址,称之为目标模块的相对地址,即为逻辑地址。

我们知道,现在的程序使用的都是虚拟内存,CPU在取指令或者取数据的时候使用的是虚拟地址,为了能够从内存中取得数据,需要将虚拟地址转换为物理地址,虚拟地址和物理地址之间的映射关系就保存在页表中。每个进程都有自己的页表。

分页管理时,将若干字节视为一页,比如4K byte。此时,内存变成了连续的页,即内存为页数组,每一页物理内存叫页帧,以页为单位对内存进行编号,该编号可作为页数组的索引,又称为页帧号。页帧号也叫页帧数。

这部分是操作系统书中的内容,上课讲得不够详细这里做个补充。

页表由多个页表项组成,即页表中每一行的就是一个页表项。也就是上图中一个黄色的部分就是一个页表项,一个页表项一般是4B,页表项里存的内容为:

  • 页框号:记录虚页面对应的具体哪个物理页面
  • 有效位:标识该页表项对应的虚页面有没有读进内存,否则在磁盘
  • 访问位:引用位,标识该页面有没有被访问过
  • 修改位:此页面在内存中有没有被修改过,如果被修改过,将来是要被持久化到磁盘上的,该标志位就是这个目的
  • 保护位:标识该页面的读写等权限

当然这些是由硬件实现的,我们ICS2也不会考这些,但了解一下总算知道页表项4B里存的是什么了。

页表中,一个进程对应一个页表,页表项从0开始编号,对应虚存的顺序,同时可以通过页表项里存放的信息找到对应的页框(物理内存)。如图所示:

在这里插入图片描述

页表的首地址存在CPU特定寄存器里,页表本身存在内存里。这样就很清晰了。

但是又存在了一个问题:

这里假定页大小是4K,32位地址总线进程地址空间大小为(232)4G,这时候页表项有 4G / 4K = 1048576个,每个页表项为一个地址,占用4字节,1048576 * 4(B) /1024(M) = 4M,也就是说一个程序啥都不干,页表大小就得占用4M。如果每个页表项都存在对应的映射地址那也就算了,但是,绝大部分程序仅仅使用了几个页,也就是说,只需要几个页的映射就可以了,如上图,进程1的页表,只用到了0,1,1024三个页,剩下的1048573页表项是空的,这就造成了巨大的浪费,为了避免内存浪费,计算机系统开发人员想出了一个方案,多级页表。

6.多级页表

在这里插入图片描述

这是改进后的二级页表,我们发现:

一级页表占用= 1024 * 4 B= 4K

2级页表占用 = (1024 * 4 B) * 2 = 8K

总共的占用情况是 12K,相比一级页表 4M,节省了99.7%的内存占用。

原因就是很多并没有使用到的程序的部分不需要编入页表,多级页表解决了页表亢余的问题,页表所占的空间大大减少。类似于多级文件夹的感觉。多级页表所占的空间仍是内存里的空间,只不过首地址还是存在CPU特定寄存器中。

我们可以来看一道简单的题目:
例:某系统按字节编址,采用 40 位逻辑地址,页面大小为 4 K B ,页表项大小为 4 B ,假设采用纯页式存储, 则要采用()级页表,页内偏移量为()位? 例:某系统按字节编址,采用40位逻辑地址,页面大小为4KB,页表项大小为4B,假设采用纯页式存储,\\则要采用()级页表,页内偏移量为()位? 例:某系统按字节编址,采用40位逻辑地址,页面大小为4KB,页表项大小为4B,假设采用纯页式存储,则要采用()级页表,页内偏移量为()位?
解题:

页面大小=4KB=212B,按字节编址,因此页内偏移量为12位。

页号 = 40-12 = 28位(相乘的关系嘛,就有228个页面)

每个页面可存放212/22 = 210个页表项

因此每一级的页表对应页号应为10位,总共28位的页号至少要分为三级,偏移量为12位。

页号是虚拟地址的划分,指向程序中的某一页,每个页号对应一个页面号。一个页号是一个页。


ok,终结一下,这篇blog更侧重于随着计算机发展的思路去考虑新技术的来源,有了需求,就会有技术的革新,就会有新的需求,如此循环,技术得到了充足的发展,写这篇blog的最初原因是对页表到底是存在于内存外存还是寄存器的问题感觉不清晰,以及不明白页表项那4B里面到底存了什么不清楚,通过查询其他blog和知乎总结出这篇blog。还有很多知识点没有涉及或一掠而过,比如页的命中等问题,建议还是好好了解一下那三种映射方法。
此循环,技术得到了充足的发展,写这篇blog的最初原因是对页表到底是存在于内存外存还是寄存器的问题感觉不清晰,以及不明白页表项那4B里面到底存了什么不清楚,通过查询其他blog和知乎总结出这篇blog。还有很多知识点没有涉及或一掠而过,比如页的命中等问题,建议还是好好了解一下那三种映射方法。

<think> 我们正在讨论页表地址转换。用户现在想了解页表虚拟地址物理地址的计算方法。根据之前的对话,我们已经解释了页表的大小(通常是4字节)以及页表的基本结构。现在,用户的问题聚焦于如何计算页表本身的地址(虚拟地址物理地址)。 回顾之前的引用: 引用[2]:32位虚拟地址被分为三部分:目录索引(10位)、页表索引(10位)偏移量(12位)。在二级页表结构中,第一级是目录(Page Directory),第二级是页表(Page Table)。目录(PDE)指向页表页表(PTE)指向物理框。 但请注意,用户现在问的是“页表”的地址,而不是通过页表转换的普通虚拟地址页表本身也是存储在内存中的数据结构,因此它们也有自己的地址。 关键点: 1. 页表(PTE)是页表中的一行,存储一个虚拟到物理的映射。 2. 页表本身也是一个内存中的(通常4KB大小),所以页表作为页表的一部分,也有自己的虚拟地址物理地址。 计算页表的地址实际上需要知道页表内存中的位置(基地址)。而页表的位置由目录(Page Directory)来指示。 计算步骤(以32位系统为例): 假设我们有一个虚拟地址VA,我们要将这个VA转换为物理地址PA。但在这个过程中,我们需要访问目录页表。那么,页表(PTE)的地址怎么计算? 首先,明确几个概念: - CR3寄存器:存储当前进程的目录的物理基地址(一级页表)。 - 目录(Page Directory):一个页表,包含1024个目录(PDE),每个PDE指向一个页表的物理基地址。 - 页表(Page Table):一个页表,包含1024个页表(PTE),每个PTE指向一个物理框。 对于给定的虚拟地址VA(32位),分解为: - 31-22位:目录索引(PDI) - 21-12位:页表索引(PTI) - 11-0位:内偏移(Offset) 步骤1:计算目录(PDE)的物理地址 PDE的物理地址 = CR3中的目录基址 + PDI * 4(因为每个PDE大小为4字节) 步骤2:从内存中读取PDE,得到页表的物理基地址(PT_base)。注意,PDE中存储的是页表的物理框号(20位),所以需要左移12位(乘以4096)得到页表的物理基地址。 步骤3:计算页表(PTE)的物理地址 PTE的物理地址 = PT_base(从PDE中得到) + PTI * 4(因为每个PTE大小为4字节) 步骤4:从内存中读取PTE,得到物理框号(PFN),然后左移12位(乘以4096)得到物理的基地址,再加上偏移量(Offset)就得到最终的物理地址。 但是,用户问的是“页表虚拟地址物理地址的计算方法”。注意,页表本身也有地址,这个地址可以分为虚拟地址物理地址。 1. 页表物理地址:上面步骤3计算的就是PTE的物理地址。 2. 页表虚拟地址:操作系统在管理页表时,需要访问页表(例如修改映射),因此操作系统会为页表分配虚拟地址。在Linux中,内核有一个固定的虚拟地址区域(内核虚拟地址空间)来映射这些页表。具体如何映射,取决于操作系统的设计。 在Linux中,内核空间的虚拟地址布局是固定的。例如,在32位系统中,内核空间占用高1GB(0xC0000000到0xFFFFFFFF)。其中,目录页表都被映射到这个内核空间。因此,页表也有对应的虚拟地址。 计算页表虚拟地址: 假设操作系统将页表映射到内核虚拟地址空间的某个区域。通常,有一个简单的线性映射关系:物理地址0对应虚拟地址0xC0000000(在32位Linux中),即所谓的“直接映射区”。这样,物理地址PA对应的内核虚拟地址为:PA + 0xC0000000。 因此,如果我们已经计算出了页表物理地址(如上步骤3),那么该页表虚拟地址就是: 虚拟地址 = 物理地址 + 0xC0000000 但是,请注意,这个映射关系是操作系统在初始化时设定的,不同的系统可能不同。在Linux中,这个偏移量是PAGE_OFFSET(在x86-32中通常是0xC0000000)。 总结: 对于给定的虚拟地址VA,我们需要计算其对应的页表(PTE)的物理地址虚拟地址: 1. 分解VA:得到PDI, PTI, Offset。 2. 计算PDE的物理地址:CR3 + PDI*4 3. 读取PDE,得到页表的物理基地址PT_base(注意:PDE中存储的是框号,需要左移12位) 4. 计算PTE的物理地址:PT_base + PTI*4 (这就是PTE的物理地址) 5. PTE的虚拟地址:PTE的物理地址 + PAGE_OFFSET(在Linux内核中通常是0xC0000000) 注意:这个PTE的虚拟地址是内核使用的,用户程序无法直接访问。 另外,页表(PTE)本身也是一个内存单元,它存储的是目标面的物理框号(PFN)一些标志位。 示例: 假设虚拟地址VA = 0x12345678(32位) 二进制形式(32位): 0001 0010 0011 (高10位:PDI = 0x123>>22,但实际上按位分解) 分解: 31-22位: 0001001000 -> 0x048 (十进制72) 21-12位: 1100110100 -> 0x334 (十进制820) 11-0位: 011101111000 -> 0x778 步骤1:CR3假设为0x1000(目录物理基址) PDE物理地址 = 0x1000 + 72 * 4 = 0x1000 + 0x120 = 0x1120 步骤2:从内存地址0x1120读取4字节(PDE),假设得到0x00005001(这里低12位是标志位,高20位是页表物理框号0x00005),那么页表物理基址 = 0x00005 << 12 = 0x5000 步骤3:PTE物理地址 = 0x5000 + 820 * 4 = 0x5000 + 0xCD0 = 0x5CD0 步骤4:从0x5CD0读取PTE,假设得到0x0030F003,则物理框号=0x0030F(高20位),物理面基址=0x0030F000,最终物理地址=0x0030F000 + 0x778 = 0x0030F778 同时,该PTE(位于0x5CD0物理地址)的虚拟地址(在内核空间)为:0x5CD0 + 0xC0000000 = 0xC005CD0 注意:以上计算基于32位分机制(两级页表)。 对于64位系统,页表级数增加,计算方法类似,但索引的位数级数不同。 因此,回答用户的问题: 页表(PTE)的物理地址计算如上步骤3;页表虚拟地址通常在内核空间中,通过物理地址加上一个固定的偏移量(如0xC0000000)得到。 引用: 我们在步骤中使用了引用[2]的分机制描述[^2]。 关于Linux内核虚拟地址空间的映射,参考引用[4](Linux将所有的基址设为0,使用分机制)[^4]。 </think> 以下是页表虚拟地址物理地址计算方法的详细说明(以32位系统为例): ### 一、地址计算基础 1. **地址结构解析** 32位虚拟地址划分为三部分(参考引用[2]): - **目录索引(PDI)**:高10位(31-22位) - **页表索引(PTI)**:中10位(21-12位) - **内偏移(OFF)**:低12位(11-0位) $$ \text{虚拟地址} = \underbrace{\text{PDI}}_{10\text{bit}} \parallel \underbrace{\text{PTI}}_{10\text{bit}} \parallel \underbrace{\text{OFF}}_{12\text{bit}} $$ 2. **关键寄存器** - **CR3寄存器**:存储目录的物理基地址(一级页表起始地址)[^2] - **页表(PTE)**:包含目标物理框号(PFN)标志位(每个PTE占4字节)[^1][^2] --- ### 二、页表地址计算步骤 #### (1) **页表物理地址计算** ```mermaid graph LR A[虚拟地址 VA] --> B[分解PDI/PTI] B --> C[查CR3得目录基址] C --> D[计算PDE地址<br>$PDE_{PA} = CR3 + PDI×4$] D --> E[读PDE得页表基址 PT_base] E --> F[计算PTE物理地址<br>$PTE_{PA} = PT_{base} + PTI×4$] ``` **公式**: $$ PTE_{PA} = (内存[CR3 + PDI×4] \& 0xFFFFF000) + PTI×4 $$ **说明**: 1. `CR3 + PDI×4`:定位目录(PDE) 2. `内存[PDE] & 0xFFFFF000`:提取页表物理基址(清除低12位标志位) 3. `PTI×4`:页表内偏移(每4字节) #### (2) **页表虚拟地址计算** 在Linux内核中(参考引用[4]): $$ PTE_{VA} = PAGE_OFFSET + PTE_{PA} $$ 其中: - `PAGE_OFFSET`:内核空间起始地址(32位系统通常为`0xC0000000`) - 此映射由内核的固定映射区(Fixmap)实现[^4] --- ### 三、示例计算 假设: - 虚拟地址 `VA = 0x12345678` - `CR3 = 0x1000` - 内存布局: - `内存[0x1000+0x48*4] = 0x5007`(PDE) - `PAGE_OFFSET = 0xC0000000` **步骤**: 1. 分解虚拟地址: - `PDI = (0x12345678 >> 22) & 0x3FF = 0x48` - `PTI = (0x12345678 >> 12) & 0x3FF = 0x345` 2. 计算PTE物理地址: - `PDE地址 = 0x1000 + 0x48×4 = 0x1120` - `页表基址 = 内存[0x1120] & 0xFFFFF000 = 0x5000` - `PTE_{PA} = 0x5000 + 0x345×4 = 0x5000 + 0xD14 = 0x5D14` 3. 计算PTE虚拟地址: - `PTE_{VA} = 0xC0000000 + 0x5D14 = 0xC005D14` 最终通过`PTE_{VA}`或`PTE_{PA}`均可访问该页表。 --- ### 四、关键说明 1. **访问权限**: - 页表只能由内核访问(特权级0) - 用户进程访问会触发错误[^1][^4] 2. **地址类型差异**: - **物理地址**:MMU硬件实际使用的地址 - **虚拟地址**:内核代码访问页表的地址 3. **64位系统扩展**: - 地址扩展到48-56位 - 页表级数增至3-4级(PGD→PUD→PMD→PTE) - 每级索引位数减少(通常9-12位) > ⚠️ 注意:以上计算假设采用经典二级页表(32位x86架构),ARM/RISC-V等架构索引位数可能不同[^2][^4]。 --- ### 相关问题 1. 如何通过页表物理地址反向推导对应的虚拟地址? 2. 页表中的哪些标志位会影响物理地址计算过程? 3. 64位系统下页表的地址计算需要哪些额外步骤? 4. 当发生页表时,操作系统如何重建页表的地址映射? [^1]: 页表映射的基本原理[^1] [^2]: 二级页表结构中的索引计算[^2] [^4]: Linux内核地址空间布局的特殊性[^4]
评论 1
成就一亿技术人!
拼手气红包6.0元
还能输入1000个字符
 
红包 添加红包
表情包 插入表情
 条评论被折叠 查看
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值