目录
一、前言
二、深入理解页表
三、页表的实际组成
四、总结:
一、前言
页表是我们之前在讲到程序地址空间的时候说到的,它是物理内存到进程程序地址空间的一个桥梁,通过它物理内存的数据和代码才能映射到进程的程序地址空间中,在信号这一节我们又提到了内核空间的页表,讲到了该内核级页表不同于前面说的用户级页表是每个进程都有的,内核级页表整个系统只有一份。
但是之前我们对页表的理解还都处于一个比较简单的层面,事实上页表的实现还是比较复杂的,这次我们深入理解一下页表的构成及其功能。
二、深入理解页表
这是我们之前所理解的简化版的页表, 我们将页表的映射功能抽象成了两栏 ,实际上页表并不只有简单的两栏,其比较复杂,且不是用一张表就可以表述出来的。
为了方便理解,我们暂且对复杂的页表结构做简单抽象,以一级页表来表示
物理地址和虚拟地址我们都已经知道了是什么了,剩下三栏中都分别代表什么呢?
1、RWX权限:我们知道Linux中一切皆文件,这里即我们所熟知的读、写、执行权限,表示的是进程对物理内存的访问权限 。我们直到硬件是不具备有访问控制能力的,也就是谁都可以对硬件进行读写,但是得益于操作系统,为了安全性,软件限制了我们的访问。
2、U/K权限:U表示User,K表示Kernel,即表示的用户和内核,就是在内核中的信号一篇中提到的用户态和内核态,用以区分访问内存的用户权限和内核权限。
3、是否命中:当CPU需要访问指定内存的数据的时候,会用虚拟地址通过页表向物理内存中查询数据。但是程序中的数据不是一下子全部加载到物理内存的,即页表中可能不存在指定的物理内存,所以CPU需要访问数据的时候,可能会存在一次找不到的情况,称为 未命中。
当CPU访问数据没有命中时,整个进程会从CPU上拉下来 先不运行,接着操作系统会将未命中的数据从磁盘程序中加载到指定的物理内存中,然后CPU才会再次运行此进程。
所以是否命中这一栏其实是 表示的是此次CPU访问数据是否在物理内存中找到了。
这种进程数据不一次性加载到物理内存的机制, 是因为进程地址空间的存在才存在的.
可以允许进程在使用指定数据或代码的时候才将代码和数据真正加载到物理内存中. 这样可以更有效地利用内存资源
我们知道了CPU从虚拟地址到物理内存的数据查询机制,下面看一下页表是以什么形式存在的。
三、页表的实际组成
我们以32位环境为例,即进程地址空间和物理内存最大都为4GB,如果使用一级页表(即只使用一张页表),想要将虚拟地址空间和物理地址一一对应下来,这个页表需要储存多少行条目?
如果页表的一行只表示一个地址,那么32位的计算机就有着2的32次方个地址,然而页表中的一行不止存储一个地址,至少有两个,且在32位环境下地址的大小为4字节,所以页表中一行条目的大小是8个字节,要存储所有的地址的话这个页表得有多大呢?2^32 * 8 = 34,359,738,368,单位是字节,一共是32GB,而我们的物理内存最大才是4GB.很明显,以一级页表来将虚拟内存对应的物理内存全部映射到是不可能的。
所以事实上,在操作系统中的页表是多级页表,在32位系统中,采用的是两级页表的形式。
在对二级页表做介绍之前我们先来补充一些概念:
在32位环境下,物理内存和虚拟地址空间大小都是4GB,同时在CPU访问数据时,提供的虚拟地址也就是32位的。虚拟地址和物理地址的映射需要通过页表来完成,CPU需要有能力提供覆盖 所有物理地址内存的地址,32位环境下,就是32位进制,虽然CPU给页表提供的虚拟地址是32位的,但是却不是直接将32位作为一个整体在页表中查找物理地址的。而是将32位二进制分为了 10+10+12的形式。即:
- 虚拟地址和物理地址:在32位系统中,虚拟地址和物理地址的空间都是4GB。
- 虚拟地址的处理:虽然虚拟地址是32位的,CPU在查找物理地址时并不会直接使用整个32位地址,而是将其拆分成三段。
- 地址拆分的方式:虚拟地址被分为三部分:前两部分各有10位,最后一部分有12位。这样做是为了有效地在内存中查找和映射物理地址。
// CPU提供的32位二进制地址 // 会分为10、10、12位的三部分来进行查找 0000 0000 00 0000 0000 00 0000 0000 0000 xxxx xxxx xx yyyy yyyy yy zzzz zzzz zzzz
事实上CPU以这样的形式查找物理内存是因为页表设计形式是下面这样的:
32位环境下,也表映射的实现使用的是二级页表,情况如下:
可以看到在二级页表中出现了page这一框,这个page又是什么呢?
在之前我们介绍Linux的文件系统的时候,讲到 操作系统的I/O操作的基本单位通常都是4KB,为了方便操作操作系统也会以4KB为单位的大小来管理内存,即操作系统会将物理内存以4KB位基本单位,并将其称为页或者页框,也就是这里的 page 。除了物理内存之外,磁盘中的程序在进行编译的时候也是按照4KB为单位划分好的,程序中的4KB单位被称为 页帧。
那么操作系统对于程序地址空间也是按照4KB为基本单位进行管理的。
Linux内核中的page是一个结构体如下:
所以4GB的内存是 4*1024*1024*1024 字节,4KB大小是 4*1024 字节,所以说操作系用中会存在着 1024*1024个page ,所以为了方便管理,操作系统会将这些page统一以一个数据结构维护起来,最终对于内存的管理其实就是对于此数据结构的管理。
所以CPU对于物理内存的查找实际上是这样的:
- 首先使用的是虚拟地址的最高的10位,在页目录中查找到对应的页表的地址,再通过该地址查找到对应的页表。
- 接着通过虚拟地址的中间的10位查找对应的page的起始地址,这个page的起始地址其实就是个真实的物理地址,找到的就是物理内存中的一页page。
- 最后虚拟地址的最后12位起到的是一个偏移量的作用,我们称虚拟地址的最后12位为 页内偏移量。
- 所以我们找到page的起始地址,将虚拟地址的最低12位作为偏移量,就能够找到一个准确的物理地址。
但是这个虚拟地址的最后12位可以刚好覆盖完一个page的全部地址吗?我们可以计算一下:page的大小是4KB,即4*1024=4*2^10=2^12,而虚拟地址的最低12位刚好可以覆盖到page的全部地址。
四、总结
1、进程虚拟地址和物理内存的解耦
- 在二级页表中,每个页表条目记录的是页面(page)的位置,未加载的页面会存储为
null
。当程序的数据没有加载到某个页面时,CPU查找时就会发生“未命中”情况。 - 这意味着,CPU在查找物理内存时,不关心页面的内容,只关心该页面是否存在。程序的数据是以页面为单位加载到内存中的。
- 通过页表,虚拟地址和物理内存之间实现了解耦。虚拟地址到物理地址的转换过程中,只能判断物理地址是否存在,而不会涉及具体的数据内容。
2、页表设计的优点
-
节省内存:
- 如果使用一级页表,整个4GB的内存地址空间都需要为每个页面创建一个对应的页表项,这会占用大量内存。
- 而使用多级页表,页目录的大小一般为KB级别,且由于第二级页表是按需创建的,因此只在需要时才分配内存。这样可以显著节省内存。最坏情况下,内存占用也只是MB级别。
-
方便管理:
- 多级页表的结构类似于一颗多叉树。第一层页表(页目录)指向第二层页表,第二级页表就像树的节点一样,可以按需创建、删除和管理。
- 这种结构使得管理更加灵活和高效,尤其是当内存需求不均匀时,可以动态分配和释放内存。