linux内存寻址总结
1 三种不同的地址
逻辑地址:每个逻辑地址由一个段和偏移量组成
线性地址:是个32位的无符号数,即4G的地址空间
物理地址:微处理器的引脚发送到内存总线上的电信号形成的地址,一般由32或36位的无符号数组成
具体可以参见这篇文章:
逻辑地址到物理地址的转换过程:
逻辑地址经过内存控制单元(MMU)的分段单元硬件电路之后转换成线性地址,线性地址经过分页单元的硬件电路转换成物理地址。在MP中,多个CPU通过内存仲裁器对RAM进行并发的访问(每个RAM有一个仲裁器),在UP中也是有内存仲裁器的,因为CPU和DMA控制器要并发的对RAM进行访问
2 段选择符和段寄存器
一个逻辑地址由两部分组成:段选择符和段内的偏移量,段选择符是一个16位的字段,偏移量则是一个32位的字段。
(1) 为了减少地址转换的时间和代码的复杂度,处理器提供了6个段寄存器。当程序执行的时候,段选择符必须事先加载到段寄存器中,下面我们来看一下段寄存器的图示:
它由可见部分和不可见部分组成,可见部分即上面讲的段描述符的16位。不可见部分按照intel手册上讲可以用做descriptor cache,即在加载段选择符的时候,会同时从段选择符指向的段描述符中加载段基址,段限和其他一些访问权限信息如S G等。这么做的目的是在进行地址转换的时候不用访问GDT表,而直接就可以转换了。但是我感觉Linux的实现中不是这么做的,首先我们来想,假设在32位的系统中,段寄存器是32位的,段描述符就占去了16位,剩下只有16,不可能装下20位的段限,32位的段基址和其他一些东西等等。intel的设计可能只是一厢情愿而已,但是也有文章提到,为了加速逻辑地址到线性地址的转换,80x86提供了一种附加的非编程的寄存器(程序员不可见),每个可编程的寄存器含有8个字节的段描述符,由相应的段选择符来指定,即:当一个段选择符被加载时,有段选择符指定的段描述符就有内存加载到非编程寄存器中,那么这个逻辑地址的转换就不需要访问GDT和LDT了(段寄存器和非编程寄存器的对应关系应该是上图讲的差不多)。
(2) 下面我们再来看看段描述符:(以代码段描述符为例)
每个段描述符占8个字节,放在GDT或LDT中,通常只定义一个GDT,如果每个进程除了放在GDT中的段之外还需要创建附加的段的话,就可以定义自己的LDT。至于段描述符中每个字段的意思,我就不详细介绍了,详细可见《深入理解linux内核》。
(3)下面我们再来看下GDT表:
GDT表的基址存放在gdtr中,需要注意的是,GDT表的第一项总是为0,这就确保了空的段选择符的逻辑地址被认为是无效的,所以GDT表也就共有8191 = 2^13 - 1 项
系统中每个CPU都有一个GDT表,除少数几种情况外,GDT中的大部分表项都是相同的。但是,每个处理器都应该有他自己的TSS,其次,LDT和TLS也于当前进程有关,所以也是不同的。这里有个LDT表是因为大多数情况下,linux并不使用局部描述符表,所以就在GDT中定义了一个公共的LDT,而不是每个进程都维护一个LDT。
通过以上分析我们来总结一下逻辑地址到线性地址的转换:
根据段选择符中的index和gdtr中的地址计算出段描述符的地址(gdtr + index * 8),然后从段描述符中取得段基址base,与offset相加即得到线性地址,在这个过程中包括了段限的检查等一些安全性检查。
3 如何把线性地址转换成物理地址
前面提到,分页单元把线性地址转换成了物理地址。分页单元把所有的RAM分成固定长度的页框(也叫物理页),每一个页框包含一个页。页指的是一个数据块,也框指的是一块实际的物理内存。
页目录项和页表项有相同的结构,我们现在来看一下linux中的四级分页模型(linux2.6.11开始)
主要分为:
(1)页全局目录
(2)页上级目录
(3)页中级目录
(4)页表
这里主要分几种情况:
(1)未启用物理地址扩展的32位系统,两极页表已经足够了,linux通过把也上级目录和页中级目录的项数设置为1,并把这两个也目录映射到也全局目录的某个适当的目录项而实现。
(2)启用了物理地址扩展的32位系统使用的是三级页表。
这里bits31-30对应的是也全局目录,bits29-21对应的是页中级目录,bits20-12对应的是页表。
下面我们来总结一下,就一句话:线性地址到物理地址的转换是有硬件按照页表的不同的组织形式来自动转换的,对用户来说是透明的。
4 物理内存的布局
我们首先来看一下前3M的物理内存布局:
首先说明,上图是错误的,我省劲就不自己画了。途中的_text应该和0x100是重合的,指的是一个位置。
内核从0x100开始存放,到_etext结束,然后_etext到_edata是初始化数据区,之后到_end是未初始化数据区。
阅读(167) | 评论(0) | 转发(0) |