分类: LINUX
2008-11-24 12:18:58
linux内核采用页式存储管理,对于i386来说,linux采用了使段式映射的过程实际上不起什么作用(除特殊的VM86模式外)的方法。在elf格式的可执行代码中,ld总是从0x8000000开始安排程序的“代码段”。
如果一个程序已经运行,整个映射机制都已经建立好,并且CPU正在执行main()中的“call 08048568”(假如)。
首先是段式映射阶段:0x08048586是一个程序的入口,在执行的过程中是由CPU中的“指令计数器”EIP”指向的,所以在代码段中,故i386CPU用CS的当前值来作为段式映射的选择码,即段描述符表中的下标,而CS(内核在建立一个进程时都要将段寄存器设置好)被设置为USER_CS(DS,ES,SS都被设置为USER_DS,即在Linux内核中堆栈段和数据段是不分的),
而宏USER_CS被定义为0x23(USER_DS为0x2B,KERNEL_CS为0x10,KERNEL_DS为0x18),展开为:0000 0000 00100 0 11,即index=4,TI=0,RPL=3,TI都是0,全都使用GDT,在linux内核中基本不使用局部段描述表LDT,LDT只是在VM86模式中才使用,初始的GDT中0x23对应于0x00cffa000000ffff,转换为二进制为:K_CS
0000 0000 1100 1111 1111 1010 0000 0000 0000 0000 0000 0000 1111 1111 1111 1111
每个段都是从0开始的整个4GB虚存空间,逻辑地址到线性地址的映射保持原值不变。
然后是页式映射阶段:每个进程都有其自身的PGD,指向这个目录的指针保持在每个进程的mm_struct数据结构中,每当调度一个进程进入运行的时候,内核都要为即将运行的进程设置好控制寄存器CR3,而MMU的硬件则总是从CR3中取得指向当前页面目录的指针,不过,CPU在执行程序时使用的是虚存地址,而MMU硬件在进行映射时所用的则是物理地址。当我们在程序中要转移到地址0x08048568的时候,进程正在运行中,CR3早已设置好,指向我们这个进程的页面目录了,先将线性地址0x08048568按二进制展开:
0000 1000 0000 0100 1000 0101 0110 1000,最高10位为0000 1000 00,即十进制32,所以i386CPU(确切地说是CPU中的MMU)就以32为下标去页面目录中找到其目录项,找到页面表后,CPU再来看线性地址中的中间10位,0x08048568的中间10位为0001001000即72,于是CPU就以此为下标在已经找到的页表中找到相应的表项,和目录项相似,当页面表项的P标志位为1时表示所映射的页面在内存中,32位的页面表项中的高20位指向一个物理内存页面,在后边添加12个0就得到这物理内存页面的起始地址,在其起始地址上加上线性地址的最低12位,如这里的0x568,就可以得到最终的物理内存地址了。