四、内存管理
1、基本框架
Linux内核的设计要考虑到在各种不同的微处理器上的实现,还有考虑到在64位的微处理器(如Alpha)上的实现,所以不能仅仅针对i386结构来设计它的映射机制,而要以只要假象的、虚拟的微处理器和MMU(内存管理单元)为基础,设计出一种通用的模式,再把它分别落实到具体的微处理器上。因此,Linux内核的映射机制被设计成三层,在页面目录和页表之间增设了一层“中间目录”。在代码中,页面目录称为PGD,中间目录称为PMD,而页表称为PT。PT的表项称为PTE。PGD,PMD,PT均为数组,相应的,在逻辑上也把线性地址从高到低分为4各位段,个占若干位,分别用作目录PGD的下标、中间目录PMD的下标、页表中的下标和物理页面内的位移。
就i386微处理器来说,CPU实际上不是按三层而是按两层的模型来进行地址映射,这就需要将虚拟的三层映射落实到具体的两层的映射,跳过中间的PMD层次。
2、地址映射的全过程
i386微处理器一律对程序中的地址先进行段式映射,然后才能进行页式映射。而Linux所采用的方法实际上使段式映射的过程中不起什么作用。
下面通过一个简单的程序来看看Linux下的地址映射的全过程:
#include
greeting()
{
printf(“Hello world!
”);
}
main()
{
greeing();
}
|
该程序在主函数中调用greeting 来显示“Hello world!”,经过编译和反汇编,我们得到了它的反汇编的结果。
08048568:
8048568: 55 push1 %ebp
8048856b:89 e5 mov1 %esp,%ebp
804856b: 68 04 94 04 08 push1 $0x8048404
8048570: e8 ff fe ff ff call 8048474 <_init+0x84>
8048575: 83 c4 04 add1 $0x4,%esp
8048578: c9 leave
8048579: c3 ret
804857a: 89 f6 mov1 %esi,%esi
0804857c :
804857c: 55 push1 %ebp
804857d: 89 e5 mov1 %esp,%ebp
804857f: e8 e4 ff ff ff call 8048568
8048584: c9 leave
8048585: c3 ret
8048586: 90 nop
8048587: 90 nop
|
从上面可以看出,greeting()的地址为0x8048568。在elf格式的可执行代码中,总是在0x8000000开始安排程序的“代码段”,对每个程序都是这样。
当程序在main中执行到了“call 8048568”这条指令,要转移到虚拟地址8048568去。
首先是段式映射阶段。地址8048568是一个程序的入口,更重要的是在执行的过程中有CPU的EIP所指向的,所以在代码段中。I386cpu使用CS的当前值作为段式映射的选择子。
内核在建立一个进程时都要将其段寄存器设置好,把DS、ES、SS都设置成_USER_DS,而把CS设置成_USER_CS,这也就是说,在Linux内核中堆栈段和代码段是不分的。
Index TI DPL
#define_KERNEL_CS 0x10 0000 0000 0001 0|0|00
#define_KERNEL_DS 0x18 0000 0000 0001 1|0|00
#define_USER_CS 0x23 0000 0000 0010 0|0|11
#define_USER_DS 0x2B 0000 0000 0010 1|0|11
_KERNEL_CS: index=2,TI=0,DPL=0
_KERNEL_DS: index=3,TI=0,DPL=0
_USERL_CS: index=4,TI=0,DPL=3
_USERL_DS: index=5,TI=0,DPL=3
|
TI全都是0,都使用全局描述表。内核的DPL都为0,最高级别;用户的DPL都是3,最低级别。_USER_CS在GDT表中是第4项,初始化GDT内容的代码如下:
ENTRY(gdt-table)
.quad 0x0000000000000000 /* NULL descriptor */
.quad 0x0000000000000000 /* not used */
.quad 0x00cf9a00000ffff /* 0x10 kernel 4GB code at 0x00000000 */
.quad 0x00cf9200000ffff /* 0x18 kernel 4GB data at 0x00000000 */
.quad 0x00cffa00000ffff /* 0x23 user 4GB code at 0x00000000 */
.quad 0x00cff200000ffff /* 0x2b user 4GB data at 0x00000000 */
GDT 表中第一、二项不用,第三至第五项共四项对应于前面的四个段寄存器的数值。
将这四个段描述项的内容展开:
K_CS: 0000 0000 1100 1111 1001 1010 0000 0000
0000 0000 0000 0000 1111 1111 1111 1111
K_DS: 0000 0000 1100 1111 1001 0010 0000 0000
0000 0000 0000 0000 1111 1111 1111 1111
U_CS: 0000 0000 1100 1111 11111 1010 0000 0000
0000 0000 0000 0000 1111 1111 1111 1111
U_DS: 0000 0000 1100 1111 1111 0010 0000 0000
0000 0000 0000 0000 1111 1111 1111 1111
|
这四个段描述项的下列内容都是相同的。
·BO-B15/B16-B31 都是0 基地址全为0
·LO-L15、L16-L19都是1 段的界限全是0xfffff
·G位都是1 段长均为4KB
·D位都是1 32位指令
·P位都是1 四个段都在内存中
不同之处在于权限级别不同,内核的为0级,用户的为3级。
|
由此可知,每个段都是从地址0开始的整个4GB地虚存空间,虚地址到线性地址的映射保持原值不变。
再回到greeting 的程序中来,通过段式映射把地址8048568映射到自身,得到了线性地址。
如果您对本文有任何疑问或者建议,请到讨论区发表您的意见:
>>
论坛入口 <<
上一篇:
基于i386的Linux实现特点剖析——关于中断
下一篇:
给Red Hat8.0加上五笔输入法
【文章评论】
【收藏本文】
【推荐好友】
【打印本文】
【我要投稿】 【论坛讨论】