Linux内存管理之一:基本概念篇
物理地址、线性地址(虚拟地址)和逻辑地址;阐述段式管理和页式管理基本概念;Linux操作系统内存管理和虚拟内存概念;为内核开发做一个基础铺垫。
内存是linux内核所管理的最重要的资源之一,内存管理子系统是操作系统中最重要的部分之一。对与立志从事内核开发的工程师来说,熟悉linux的内存管理系统非常重要。
1、物理地址、线性地址(虚拟地址)和逻辑地址之间的关系
物理地址是指出现在cpu外部的地址总线上的寻址物理内存的地址信号,是地址变换的最终结果。
逻辑地址是程序代码经过编译后在汇编程序中使用的地址。
线性地址又名虚拟地址,在32位cpu框架下,可以表示4G地址空间,用16进制表示就是0x00000000到0xffffffff。
地址转换:cpu要将一个逻辑地址转换为物理地址,需要两步:首先cpu利用段式内存管理单元,将逻辑地址转换成线性地址,在利用页式内存管理单元,把线性地址最终转换为物理地址。
2.段式管理与页式管理
什么是段式管理?
16位cpu内部拥有20位的地址线,它的寻址范围就是2的20次方,也就是1M的内存空间。但是16位的菜谱用于存放地址的寄存器只有16位,因此只能访问65536个存储单元,64K。为了能够访问1M的内存空间,cpu就采用了内存分段的管理模式,并在cpu内部加入了段寄存器。16位cpu把1M内存空间分为若个逻辑段,每个逻辑段的要求如下:
1、 逻辑段的起始地址(段地址)必须是16的倍数,即最后4个二进制必须全为0.
2、 逻辑段的最大容量为64K
物理地址的形成方式:
由于段地址必须是16的倍数,所以值的一般形式为XXXX0H,即前16位二进制位是变化的,后4位是固定的0,鉴于段地址的这种特性,可以只保存前16位二进制位来保存这个段基地址,所以每次使用时要用段寄存器左移4个0(乘以16)来得到实际的段地址。在确定了某个存储单元所属的段后,只是知道了该存储单元所属的范围(段地址->段地址+65536),如果想确定该存储单元的具体位置,还必须知道该单元在段内的偏移。有了段地址和偏移量,就可以唯一的确定内存单元在存储器中具体位置。
逻辑地址=段内偏移量
由于逻辑地址得到物理地址的公式为:PA=段寄存器的值*16+逻辑地址
段寄存器是为了对内存进行分段管理而增加的,16位cpu有四个段寄存器,程序可同时反问四个不同含义的段。
1、CS+IP:用于代码段的访问,CS指向存放程序的段基址,IP指向下条要执行的指令在CS段的偏移量,用这两个寄存器就可以得到一个内存物理地址,该地址存放着一条要执行的指令。
2、SS+SP:用于堆栈段的访问,SS指向堆栈段的基地址,SP指向栈顶,可以通过SS和SP两个寄存器直接访问栈顶单元的内存物理位置。
3、DS+BX:用于数据段的访问。DS中的值左移四位得到数据段起始地址,再加上BX中的偏移量,得到一个存储单元的物理地址。
4、ES+BX:用于附加段的访问。ES中的值左移四位得到附加段起始地址,再加上BX中的偏移量,得到一个存储单元的物理地址。
32位pc的内存管理能然采用“分段”的管理模式,逻辑地址同样由段地址和偏移量两部分组成,32位pc的内存管理和16位pc的内存管理有相同之处,也有不同之处,因为32位pc采用了两种不同的工作方式:实模式和保护模式。我们一般使用在保护模式的。
1、 实模式
在实模式下,32位的cpu的内存管理与16位的cpu是一致的。
2、 保护模式
段基地址长达32位,每个段的最大容量可达4G,段寄存器的值是段地址的“选择器”(Selector),用该“选择器”从内存中得到一个32位的段地址,存储单元的物理地址就是该段地址加上段内偏移量,这与32位cpu的物理地址计算方式完全不同。
32位cpu内有6个段寄存器,其值在不同的模式下具有不同的含义:
1、 在实模式下:
段寄存器的值*16就是段地址
2、 在保护模式下:
段寄存器的值就是一个选择器,间接指出一个32位的段地址
什么是页式管理?
从管理和效率的角度出发,线性地址被划分为固定长度的组,称为页(page),例如32位的 ,线性地址最大可为4G,如果用4KB为一个也来划分,这样的这个线性地址就被划分为2的20次方个页。
另一类“页”,也称之为“物理页”,或者是页框、页帧。分页单元把所有的物理内存也划分为固定长度的管理单位,它的长度一般与线性地址页是相同的。
1、 分页单元中,页目录的地址放在cpu的cr3寄存器中,是进行地址转换的开始点。
2、 每个进程,都有其独立的虚拟地址空间,运行一个进程,首先需要在它的页目录地址放到cr3寄存器中,将其他进程的保存下来。
3、 每个32位的线性地址被划分为三个部分:页目录索引(10位),页表索引(10位):偏移(12位即4k)。
依据一下步骤进行地址转换:
1、 装入进程的页目录地址(操作系统在调度进程时,把这个地址装入cr3)
2、 根据现行线性地址的前10位,在页目录中找对应的索引项,页目录中的项是一个页表的地址。
3、 根据线性地址的中间10位,在页表中找到页的起始地址。
4、 将页的起始地址与线性地址的最后12位相加,得到物理地址。
这样的二级模式是可以覆盖4G的物理地址空间的,
页目录中共有:2^10项,也就是说有这么多个页表;页表对应有:2^10页;每个页可寻址:2^12个字节。即2^32=4GB。
3.Linux内存管理
Linux内核的设计并没有全部采用Intel所提供的段机制,仅仅是有限度地使用了分段机制。这不仅简化了linux内核的设计,而且为把linux移植到其他平台创造了条件,因为很多RISC处理器并不支持段机制。
在linux中,所有段的基地址均为0,由此可以得出,每个段的逻辑地址空间范围是0-4GB。因为每个段的基地址为0,因此逻辑地址与线性地址保持一致(即逻辑地址的偏移量字段的值与线性地址的值总是相同的),在linux中所提到的逻辑地址和线性地址(虚拟地址),可以认为是一致的。看来,linux巧妙地把段机制给绕过去了,而完全利用了分页机制。
前面介绍的是i386的二级管理架构,不过有的cpu使用的三级,甚至是四级架构,linux2.6.29内核为每种cpu提供统一的界面,采用了四级页面管理架构来兼容二级、三级、四级管理架构的cpu。
这四级分别为:
1、 页全局目录(Page Global Directory):即pgd,是多级页表的抽象最高层。
2、 页上级目录(Page Upper Directory):即pud。
3、 页中间目录(Page Middle Directory):即pmd,是页表的中间层。
4、 页表(Page Table Entry):即pte
4.虚拟内存
Linux操作系统采用虚拟内存管理技术,使得每个进程都有一个独立的进程地址空间,该空间是大小为3G,用户所看到和接触的都是虚拟地址,无法看到实际的物理地址。利用这种虚拟地址不但能起到保护操作系统的作用,而且更重要的是用户程序可使用比实际物理内存更大的地址空间。
Linux将4G的虚拟地址空间划分为两个部分---用户空间和内核空间。用户空间从0到0xbfffffff,内核空间从3G到4G。用户进程通常情况下只能访问用户空间的虚拟地址,不能访问内核空间。例外情况是用户进程通过系统调用访问内核空间。