您的位置:首页 > 其它

内核临时页表建立

2015-08-28 15:54 381 查看
背景:为什么要建立内核临时页表:当内核被解压到线性地址0x100000后,为了继续启动内核,即启动内核的第一进程即swapper进程,内核需要建立一张临时页表供其使用。

当内核从16位的实模式进入到保护模式(通过在汇编代码中的setup函数中设置linux的cr0寄存器的PE位),内核要创建一个有限的地址空 间,容纳内核的代码段、数据段、初始页表和用于存放动态数据结构的共128KB大小的空间,此时通常的程序设计者假定,内核使用的段、临时页表和 128KB的内存范围可以全部放在RAM前8MB的空间内。于是我们需要做的工作是建立一个页表可以对内存的前8MB的物理地址进行寻址。

由于进程的线性地址空间分成两部分:

0x00000000到0xbfffffff(0-3G)的线性地址,无论进程运行在用户态还是内核态都可以寻址。

0xc0000000到0xffffffff(3G-4GB)的线性地址,只有内核态进程才可以寻址。

为了保证在实模式和保护模式下都可以很容易的对这8MB寻址。因此,内核必须建立两个映射。把从0x00000000到0x007fffff的线性 地址和从0xc0000000到0xc07fffff的线性地址都映射到从0x00000000到0x007fffff的物理地址中。

此时可以通过与物理地址相同的线性地址或者通过从0xc0000000开始的8MB线性地址对RAM的前8MB进行寻址。

开始建立内核临时页表

内核的临时页表是通过arch/i386/kernel/head.s汇编代码中的startup_32()函数实现的。

建立一个完整的二级页表,需要建立一个页全局目录和一个页表。由于只需要映射8MB的地址空间,一个页表可以容纳1024项,每个页大小为4k,8MB=2*1024*4k;所以只需要2个页全局目录项和一张页表即可。

页全局目录项的建立:

页全局目录项有1024项,但是我们每次寻址8MB的空间只需要2个页全局目录项即可,由于我们要同时考虑用户态和内核态的寻址所以我们需要4个页全局目录项分别寻址8MB的用户空间和8MB的内核空间。

要建立页全局目录我们首先要知道页全局目录存放的物理地址,而变量swapper_pg_dir 存放了页全局目录的线性地址

(swapper_pg_dir的线性地址可以在/boot/System.map文件中找到)通过执行swapper_pg_dir - __PAGE_OFFSET计算可以获得swapper_pg_dir 的物理地址(其中__PAGE_OFFSET为0xc0000000是内核线性空间的起始地址)。

知道了临时页全局目录的地址之后,要做的便是初始化临时页全局目录:

页全局目录的初步初始化:

417 ENTRY(swapper_pg_dir)

418 .fill 1024,4,0

这两行汇编代码执行了页全局目录的初步初始化,它的意思是从swapper_pg_dir开始,填充1024项,每项为4字节,值为0,正好是4K一个页面。

页全局目录进一步初始化:

为映射8MB地址空间,内核通过填充swapper_pg_dir中第0项,1项,768项和769项实现(768和769是通过计算内核线性地址 空间对应的页目录偏移量获得的)。前两项是给用户线性地址映射,后两项给内核线性地址映射。内核会将swapper_gp_dir的0项和768项字段设 置为pg0的物理地址(pg0中存放第一张页表的地址),而1项和769项设置为紧随pg0后的页框的物理地址(一般是pg0+4k)。

页表的建立

由于物理地址8MB=2*1024*4k,可以对应2048个页框,建立两个页表的话只需从0x00000000开始,以此将每隔0x1000的地址填入页表中的每一项中即可:0x0000,0x1000,0x2000,…0x3ff00

0x40000,0x41000,…0x7ff000

内核中有变量pg0,表示对应的页表。建立页表的过程如下:

091 page_pde_offset = (__PAGE_OFFSET >> 20);

092

093 movl $(pg0 - __PAGE_OFFSET), %edi

094 movl $(swapper_pg_dir - __PAGE_OFFSET), %edx

095 movl $0x007, %eax

096 10:

097 leal 0x007(%edi),%ecx

098 movl %ecx,(%edx)

099 movl %ecx,page_pde_offset(%edx)

100 addl $4,%edx

101 movl $1024, %ecx

102 11:

103 stosl

104 addl $0x1000,%eax

105 loop 11b

106

107

108 leal (INIT_MAP_BEYOND_END+0x007)(%edi),%ebp

109 cmpl %ebp,%eax

110 jb 10b

111 movl %edi,(init_pg_tables_end - __PAGE_OFFSET)

大致意思是从0开始,把连续的线性地址映射到物理地址。0x007正好表示PRESENT+RW+USER(在内存中,可读写,用户页面,这样在用 户态和内核 态都可读写)。由于每个页表项有32位,但其实只需保存物理地址的高20位 就够了,所以剩下的低12位可以用来表示页的属性。

结束条件:从代码中可知,当映射到当前所操作的页表项往下INIT_MAP_BEYOND_END(128K)处映射结束。

建成的页表示意图如下



这样总的页表建立之后的示意图如下



开启页面映射之后就可以引用内核的变量,但是还不能启动start_kernel,要启动swapper进程还需要设置内核堆栈

193

194 lss stack_start,%esp

然后设置中断向量表

215 call setup_idt

检查CPU类型

载入gdt(原来的gdt是临时的)和ldt

302 lgdt cpu_gdt_descr

303 lidt idt_descr

最后,调用start_kernel

327 call start_kernel

至此启动start_kernel函数
内容来自用户分享和网络整理,不保证内容的准确性,如有侵权内容,可联系管理员处理 点击这里给我发消息
标签: