在逻辑地址转为线性地址的时候,我们说过,用段寄存器中的值的前13位当作索引来索引全局描述符表, 在linux中,滑稽的是,它有意地采用数值来绕开了分段机制,对内存的管理主要采用了分页机制。 是怎样的呢? 先看看,linux在GTD中放入了些什么。查看文件 arch/i386/head.S
.quad 0x00cf9a000000ffff /* 0x60 kernel 4GB code at 0x00000000 */
.quad0x00cf92000000fff /* 0x68 kernel 4GB data at 0x00000000 */
.quad0x00cffa000000ffff /* 0x73 user 4GB code at 0x00000000 */
.quad0x00cff2000000ffff/* 0x7b user 4GB data at 0x00000000 */
这里仅仅摘下了12-15项的内容,且看下面。 我们再来看看在在各个段寄存器中的选择子是写什么数字,看看include/asm-i386/segment.h中的定义。
include/asm-i386/segment.h
#defineGDT_ENTRY_DEFAULT_USER_CS 14
#define__USER_CS (GDT_ENTRY_DEFAULT_USER_CS * 8 + 3)
#defineGDT_ENTRY_DEFAULT_USER_DS 15
#define__USER_DS (GDT_ENTRY_DEFAULT_USER_DS * 8 + 3)
#defineGDT_ENTRY_KERNEL_BASE 12
#defineGDT_ENTRY_KERNEL_CS (GDT_ENTRY_KERNEL_BASE + 0)
#define__KERNEL_CS (GDT_ENTRY_KERNEL_CS * 8)
#defineGDT_ENTRY_KERNEL_DS (GDT_ENTRY_KERNEL_BASE + 1)
#define__KERNEL_DS (GDT_ENTRY_KERNEL_DS * 8)
把其中的宏替换成数值,则为:
#define__USER_CS 115 [000000001110 0 11]
#define__USER_DS 123 [000000001111 0 11]
#define__KERNEL_CS 96 [00000000 1100 0 00]
#define__KERNEL_DS 104 [00000000 1101 0 00]
方括号后是这四个段选择符的16位二制表示,它们的索引号和T1字段值也可以算出来了
__USER_CS index= 14 T1=0
__USER_DS index= 15 T1=0
__KERNEL_CS index= 12 T1=0
__KERNEL_DS index= 13 T1=0
可以看到,用户代码段的寄存器中USRE_CS 115
把它拆开,用来索引GDT时,即找到第14项,可以看到
.quad 0x00cffa000000ffff /* 0x73 user 4GB code at 0x00000000 */
第16-31位全部为0,即段的起始地址为0,那么逻辑地址转为线性地址
即0x00000000+偏移=偏移。
这不是偶然现象,而是所有的情况都是这样的,不信可以在linux下用gdb调试程序输出寄存器的值看看,是不是115或者123。(段寄存器)
这里可以看自己随便写的函数然后调试的结果。
可以看到cs等几个寄存器的值一直都是用户CS和DS即115 123 。
不是偶然》。。。。
那么可以看到,逻辑地址和线性地址有着同样的形式(即数字上是一样的。)
故linux内存管理主要是采用分页,避开了分段(但硬件要求分段,故采用了避开的手段),但后面的3位(段选择子后面3位用来表示段的级别的,00-11是有用的,访问GDT还是LDT是有用的)。
这个图可能更清楚:
继而由线—物
我们知道Linux中用户进程线性地址能寻址的范围是0-3G,那么是不是需要提前先把这3G虚拟内存的页表都建立好呢?一般情况下,物理内存是远远小于3G的,加上同时有很多进程都在运行,根本无法给每个进程提前建立3G的线性地址页表。Linux利用CPU的一个机制解决了这个问题。进程创建后我们可以给页目录表的表项值都填0,CPU在查找页表时,如果表项的内容为0,则会引发一个缺页异常,进程暂停执行,Linux内核这时候可以通过一系列复杂的算法给分配一个物理页,并把物理页的地址填入表项中,进程再恢复执行。当然进程在这个过程中是被蒙蔽的,它自己的感觉还是正常访问到了物理内存。