linux-0.11中保护模式建立过程的分析[4]

来源:互联网 发布:交通数据 编辑:程序博客网 时间:2024/04/30 22:03

 前面提到,第114行代码 .org 0x1000 之前的代码都将会给页目录表覆盖(这里的覆盖不是指更改了磁盘上system镜像文件,它是指更改了内存中的system镜像文件)。为什么这部分代码可以给覆盖呢?其实这部分代码在整个内核运行当中只是在初始化的时候运行一次而已,在运行完以后,他就处于无用的状态了。以其让它占用这部分内存空间,不如将这部分内存空间挪作它用,所以Linus将它用作了页目录表。它是如何被占用的呢?linux先从地址0x00000(页目录表所在地)开始对5页内存(1页目录 + 4页页表)清零。到这里,head.s的前半部分代码就给清掉了。

198 setup_paging:
199         movl $1024*5,%ecx       /* 5 pages - pg_dir+4 page tables */
200         xorl %eax,%eax
201         xorl %edi,%edi                  /* pg_dir is at 0x000 */
202         cld;rep;stosl
接着就填写页目录项,因为只有4个页表,所以只填了4项。页目录项的结构与也表项的结构一样,由4字节表示。下面的以第203行代码来说明这些参数的意思:_pg_dir是第16行的符号,表示地址0x00000$pg0+7表示值0x00001007,它是一个页目录项,是页目录表中的第1项。即第一个页表所在的地址 = 0x00001007 & 0xfffff000 = 0x1000;第一个页表的属性 = 0x00001007 & 0x00000fff = 0x07;表示该页存在,用户可读写。
203         movl $pg0+7,_pg_dir          /* set present bit/user r/w */
204         movl $pg1+7,_pg_dir+4           /*  --------- " " ------ */
205         movl $pg2+7,_pg_dir+8           /*  --------- " " ------ */
206         movl $pg3+7,_pg_dir+12          /*  --------- " " ------ */

接着就是填写页表,为了方便管理内存,Linus在设计上让前16M的线性地址恒等于物理地址,意思就是说,内核在使用前16M的线性地址访问物理内存时,比如说地址0x0000ffff,那么经过页变换后,它就是访问了物理地址0x0000ffff。好像页变换对内核来说已经失效了,线性地址就是物理地址了。他为什么要这样设计呢?前面已经说过,linux-0.11支持最大的物理内存是16M,内核要管理这16M的物理内存,它必须清楚这16M内存的使用情况,要不然就谈不上管理了。如果内核给出一个线性地址,但经过页变换以后却不知道映射到哪个物理内存了。这样能方便的知道物理内存的使用情况吗?所以,在这里,Linus就巧妙的绕过了页变换对内核的作用了。下面6行代码填写了4个页表的所有内容,共有:4(页表)X 1024(一个页表的项数)= 4096 项。也就是能映射 4096 X 4K = 16M的物理内存。从第四个页表最后一项按倒序开始填写页表项。一个页表最后一项的位置为1023*4=4092,所以最后一项的物理地址为$pg3+4092。每一项填写的内容是:当前项映射页的物理地址 + 该页的属性(这里都是7)。16M物理内存最后一页的物理地址是16M – 4096 = 0xFFF000,所以最后一项的内容为 0xfff000 + 7 = 0xfff007。第209行的作用是以4递减edi寄存器(一个页表项占4个字节),第211行的作用是eax减去0x1000(一页内存的大小),第210行是填写页表项,直到eax的内容为0,也就填写完了4个页表。

207         movl $pg3+4092,%edi
208         movl $0xfff007,%eax     /*  16Mb - 4096 + 7 (r/w user,p) */
209         std
210 1:      stosl       /* fill pages backwards - more efficient :-) */
211         subl $0x1000,%eax
212         jge 1b
接着就是把页目录表的地址写到控制寄存器CR3(215),然后置位控制寄存器CR0PG(217),开启内存的分页管理功能;最后就像前面所说的执行 ret 指令(218)返回到main.cmain()函数开始执行。
213         xorl %eax,%eax          /* pg_dir is at 0x0000 */
214         movl %eax,%cr3          /* cr3 - page directory start */
215         movl %cr0,%eax
216         orl $0x80000000,%eax
217         movl %eax,%cr0          /* set paging (PG) bit */
218         ret                 /* this also flushes prefetch-queue */

    到这里,32位保护模式已经完全建立好了。也许你会问:那么对于16M以外的线性地址又是怎样处理的呢?他们的页表又在哪里呢?前面在讲解内存的分段处理的时候,已经说过4G线性地址空间将会给分成64段,那么在创建任务的时候内核就会给任务分配64M的地址空间,这是由增加全局描述符项以创建局部描述符表和增加页目录项以创建页表来实现的。创建局部描述符表和创建页表所要的内存空间将由内存管理程序动态分配和回收。

总结一下:80X86可以工作在两种模式下:实模式和保护模式。CPU刚上电的时候,是工作在实模式下的。在实模式下,CPUIPEIP)中存放的虚地址通过段寄存器左移四位与编译地址相加得到物理地址,寻址空间1MB在保护模式下,要分清三个地址概念:逻辑地址,线性地址,物理地址。简单的理解就是:逻辑地址就是程序中的地址,物理地址就是实际的物理内存的地址,那么线性地址是逻辑地址到物理地址转换的一个中间产物。当没开启分页管理功能时,线性地址就是物理地址。当开启分页管理功能时,逻辑地址经过段变换后就变成了线性地址,然后线性地址又经过页变换就转换成了物理地址了。可以总结一句话:不管是实模式还是保护模式,要想准确寻址,都要先设置好相关寄存器。

 

 

 

附:图7和图8来自于赵烔博士编著的《linux内核完全注释》,图3来自于谢煜波的《操作系统引导探究》

 

 

参考文献

linux内核完全注释》赵烔编著

《操作系统引导探究》     谢煜波

 

原创粉丝点击