在介绍Linux虚拟内存管理之前,我们必须介绍 X86 CPU
的分段和分页机制(比较枯燥的知识点),因为Linux虚拟内存管理是建立在分段和分页机制的基础上的。
分段的本意是按不同的功能来把内存划分成不同的段进行管理,例如:一个进程按不同的功能,可以划分为数据段、代码段和堆栈段等等。数据段和堆栈段可以进行读写操作但不能执行,而代码段能够执行但不能写(因为如果代码段能进行写操作,那么一些恶意的程序就可以随意改动要执行的代码进行一些非法的操作)。
从本意来看,分段是一个不错的内存管理方案。但是,分段机制有个致命的问题,就是当内存不足时,需要把整个段交换到磁盘中。如果段占用的空间很大,那么交换的代价就非常大,以致后来开发出分页机制来弥补这个问题。有了分页机制后,分段机制本应可以去掉的,但是 Intel 公司为了兼容旧版的CPU,保留了分段机制,所以新版的CPU也一直保留着分段机制。
在安装有 X86 CPU
的电脑开机后,首先会进入 实模式
,所谓的 实模式
是指代码中访问的内存地址都是物理地址,而且内存地址通过 段寄存器:偏移量
这种分段方式访问的,代表的实际内存地址是:
物理地址 = (段寄存器 << 4) + 偏移量
因为在实模式下段寄存器和偏移量都是16位的,所以下只能访问 1MB
的内存地址。X86 CPU
的段寄存器有6个,分别为: cs
、ds
、fs
、gs
、ss
和 es
。cs
是代码段寄存器,ds
是数据段寄存器,ss
是堆栈段寄存器,而 fs
、gs
和 es
是辅助段寄存器。
由于在实模式下只能访问 1MB
的内存地址,这对于现代操作系统来说是远远不够的,所以 Intel 公司开发出支持 保护模式
的 CPU,在 保护模式
下,还是通过 段寄存器:偏移量
的模式进行内存访问,但 段寄存器
不再是内存地址的一部分,而是指向一个内存基地址的描述符:段描述符(也叫段选择器)
,而偏移量也从原来的 16 位变为 32 位。如下图:
运行在 保护模式
下的操作系统需要提供一个 段描述符表
的数组让CPU能够通过段寄存器找到对应的 段描述符
,段描述符表
分为 全局描述符表GDT
和 局部描述符表LDT
,如下图:
为什么会有 全局描述符表(GDT)
和 局部描述符表(LDT)
这两种表?这是因为 Intel 当初希望操作系统开发者能够通过 全局描述符表
来访问内核的数据,而通过 局部描述符表
来访问进程的数据。但 Linux 基本不会用到 局部描述符表
,所以我们基本可以忽略。
段描述符
是一个占用64字节大小的数据结构,结构如下图:
说实话,有了分页机制后,分段机制就变成了鸡肋了(保留只是为了兼容罢了,而且Linux也只是应付式的使用)。所以对于 段描述符
这个结构有兴趣的可以自己翻阅一些 X86 CPU
相关的书籍或文字了,这里就不作详细介绍了。