Linux内核:内存寻址

内存地址分为三种:逻辑地址,线性地址,物理地址。在分段的CPU结构中,程序中引用的地址都是逻辑地址,逻辑地址经过分段单元成为线性地址。然后经过分页单元成为物理地址,物理地址就是硬件电路寻址的实际地址。如果CPU体系结构不支持分段,那么逻辑地址等于物理地址。一般RSIC指令的CPU都不支持分段,如arm。复杂指令的CPU支持分段,如x86。

一.分段

1.硬件中的分段:因为x86体系结构是分段的,所以程序是由很多段组成。程序的逻辑地址有段与偏移量组成。实现这种分段机制有特殊的硬件来完成,主要有段寄存cs, ss, ds, es, fs, gs,全局段描述符寄存器gdtr与局部段描述符寄存器ldtr。前三个段寄存器有固定的用途:cs 代码段寄存器。ss 栈段寄存器。 ds数据段寄存器。逻辑地址由16位段选择符和32位偏移量组成,段寄存器装有段选择符,段选择符由索引号与特权级。当段选择符装入段寄存器时,硬件单元就会按照里面的索引号,根据是全局段还是局部段从gdtr或ldtr寄存器段描述符的初始地址,算出线性地址。在这期间还进行相应的权限检查。

2.linux中的分段:linux的设计目标就是简单实用就好,而分段单元使得程序变得复杂。所以Linux不喜欢分段。但在在x86平台上必须分段,所以linux采用一种巧妙的方法,在用户态与内核态使用四个段,分别是用户代码段,用户数据段,内核代码段,内核数据段。而且段描述符的基地址都是0,这样换算下来的逻辑地址等于线性地址。分段的唯一用途就是可以检查权限。

二. 分页

1. 硬件中的分页:分页使得线性地址转换成物理地址,还有一个关键的任务就是进行线性地址访问权限的检查。为了方便,线性地址分为相应的组:称为页。而物理地址也相应的分为组:称为页框。页的大小由处理器决定。有些处理器支持不同大小的也,如s3c2440。x86支持4k大小的也。分页根据页的大小分为常规分页与扩展分页。扩展分页允许页的大小为4M而不是4k。常规分页采用三级页表,32位线性地址分为三个部分,最高十位是目录中间十位为页表,最后12位偏移量。在打开硬件分页功能前,首先应该初始话页表,然后把页目录的物理地址存放在cr3寄存器中,线性地址转换成物理地址是这样进行的。首先分页单元取线性地址的最高十位,以它为索引找到页目录项,然后取页目录中的base字段加上线性地址的中间十位找到页表项,然后加上线性地址的最后12位偏移量就是物理地址。当然这其中与做相应的权限检查。

2. linux中的分页:为了保持可移植性,无论硬件采用几级分页,linux都采用的四级分页:页全局目录,页上级目录,页中间目录,页表。如果硬件是两极分页,linux采用中间两极分页全是0的方式消除中间的分页。linux的进程很大程度上依赖于分页技术。不同的进程分配不同的地址空间,有效的减少了寻址错误。分页技术是虚拟内存机制的基础。linux提供了一些宏来简化页表的操作。

三. 物理内存布局

在系统初始化的时候,linux必须对物理内存做一定的映射,指定那些物理内存可以用,那些不可以用。内核保留的页框为:不可用的物理地址的页框范围,这个也就是没有安装物理内存的地址。另外一个是含有内核代码和已初始化数据结构的页框。由于硬件的原因,linux一般安装在物理地址的0x00100000开始的地方,但也不是绝对的,如mini2440,物理地址开始就是0x30000000,所以内核安装在0x30008000起始地址处。在启动内核的早期阶段,内核访问BIOS来确定物理内存的大小,有的体系结构通过启动代码传过来的内核参数来了解物理内存的大小。随后内核运行一些函数来初始化内存映射,初始化内核的一些表量来描述内存的物理布局。

四. 进程页表

进程的线性地址空间分为两部分:

(1) 从0x00000000到0xbfffffff的线性地址,无论进程运行在内核态还是用户态都可以寻址

(2) 从0xc0000000到0xffffffff的线性地址,只有内核态的进程才能寻址。

这也就是说,在内核代码的线性地址都是大于0xc0000000的,但是可以寻址小于0xc0000000的地址,而用户态的代码只能寻址小于0xc0000000的线性地址。。也全局目录小于0xc0000000的地址,具体的页目录项依赖具体的进程,但是大于0xc0000000的页目录项都是相同的都等于内核页全局目录。

五. 内核页表

内核维持着一个自己使用的页表,即内核页全局目录,这个页全局目录的最高目录项,为其他普通进程提供参考模型。在linux启动的最初阶段,CPU还没有开启分页功能,所以线性地址就等于物理地址。在开启分页功能之前,内核必须初始化页全局目录以及页表。这分为两个阶段:

(1)临时内核页表

临时页全局目录存放在swappper_pg_dir变量中,临时页表在gp0变量出存放。紧接着在内核未初始化的数据段后面。假如内核使用的全部内存可以存放在8M的空间里,因为一个页表可以映射4M的地址,所以8M的空间需要两个页表,也就是需要两个页目录项。分页第一阶段的目的是实模式下与保护模式下都可以访问这8M的地址。所以内核需要将0x00000000到0x007fffff的线性地址与从0xc0000000到0xc07fffff的线性地址都映射到从0x00000000到0x007fffff的物理地址。

(2)当RAM小于896M的最终内核页表

内核几乎会映射全部的物理地址,但是内核的线性地址空间限制在0xc0000000到0xffffffff的1G的空间内,所以如果物理地址大于1G那么就映射不来了。所以要采用一些特殊的方法来实现。预留128M的线性地址空间就是做临时映射的。所以内核只固定线性映射896M的线性地址,这部分的线性地址与物理地址是线性的映射关系。

(3) 当RAM大于896M小于4G之间的最终内核页表

这部分的物理地址大小已经超过了内核的线性地址大小。在线性地址的最高128M,内核可以采用固定映射或者临时映射来对高于1G内存的动态映射。这种映射不是线性关系,可以任意映射。

六. 总结

在不同的平台上linux的物理空间分布有很大的不同,分页与分段的实现也有很大的不同。但是linux为了实现可移植性,努力做到之间的差距达到最小。对内存寻址,分页分段的学习有助于理解linux内存管理与linux进程的相关知识。是理解linux内核的基础。

;