linux 临时页目录和页表初始化分析
Posted 青青子衿,悠悠我心。
tags:
篇首语:本文由小常识网(cha138.com)小编为大家整理,主要介绍了linux 临时页目录和页表初始化分析相关的知识,希望对你有一定的参考价值。
linux 内核在编译的时候会初始化一个静态的临时的 全局页目录(page global directory) 和一个 页表(page table)。初始化是在 arch/i386/kernel/head.S 中的 startup_32 汇编函数中初始化的。这个还包含了其他的初始化部分,因此,只截取和分页相关的部分,部分汇编代码如下:
/*
* Initialize page tables. This creates a PDE and a set of page
* tables, which are located immediately beyond _end. The variable
* init_pg_tables_end is set up to point to the first "safe" location.
* Mappings are created both at virtual address 0 (identity mapping)
* and PAGE_OFFSET for up to _end+sizeof(page tables)+INIT_MAP_BEYOND_END.
*
* Warning: don\'t use %esi or the stack in this code. However, %esp
* can be used as a GPR if you really need it...
*/
page_pde_offset = (__PAGE_OFFSET >> 20);
movl $(pg0 - __PAGE_OFFSET), %edi
movl $(swapper_pg_dir - __PAGE_OFFSET), %edx
movl $0x007, %eax /* 0x007 = PRESENT+RW+USER */
10:
leal 0x007(%edi),%ecx /* Create PDE entry */
movl %ecx,(%edx) /* Store identity PDE entry */
movl %ecx,page_pde_offset(%edx) /* Store kernel PDE entry */
addl $4,%edx
movl $1024, %ecx
11:
stosl
addl $0x1000,%eax
loop 11b
/* End condition: we must map up to and including INIT_MAP_BEYOND_END */
/* bytes beyond the end of our own page tables; the +0x007 is the attribute bits */
leal (INIT_MAP_BEYOND_END+0x007)(%edi),%ebp
cmpl %ebp,%eax
jb 10b
movl %edi,(init_pg_tables_end - __PAGE_OFFSET)
swapper_pg_dir 的值是全局页目录的线性地址,但是由于它的线性地址是在0xC0000000之后的,所以只需要将它减去0xC0000000即 __PAGE_OFFSET 之后,就可以得到它的物理地址,换句话说,这行代码:
movl $(swapper_pg_dir - __PAGE_OFFSET), %edx
是将 swapper_pg_dir 的物理地址保存在 %edx 寄存器中。同样,这一样代码:
movl $(pg0 - __PAGE_OFFSET), %edi
是将 pg0 的值也就是页表的物理地址保存在 edi 寄存器中。内核的线性地址是从0xC0000000开始的,但是实际上的物理地址是从0x00000000开始的。第一阶段的页表初始化的目的是为了不管是实模式还是受保护模式,都可以通过分页机制访问内存,所以需要将从0x00000000开始的物理地址和0xC0000000开始的线性的地址都映射到对应的页表和页目录项中。0x00000000的页目录索引为0,0xC0000000的页目录索引为768,所以页目录的初始化从这两处开始,具体的代码如下:
leal 0x007(%edi),%ecx /* Create PDE entry */
movl %ecx,(%edx) /* Store identity PDE entry */
movl %ecx,page_pde_offset(%edx) /* Store kernel PDE entry */
addl $4,%edx
edi 寄存器的值 pg0 的地址,也就是页表的起始地址,加上0x007是为了添加对应的权限标志位。将计算的值保存在 ecx 寄存器之后,在将它写入 内存地址为 edx 处,从前面就可以知道,edx 的值是 swapper_pg_dir 的地址,也就是全局页目录的地址 。
接下来继续将 ecx 寄存器的值写入到页表偏移 page_pde_offset 字节的地址,这个 page_pde_offset 是什么东西呢?它的定义如下:
page_pde_offset = (__PAGE_OFFSET >> 20);
而 __PAGE_OFFSET 的含义是内核线性地址的起始地址,在 i386 架构下它的值是0xC0000000,那么乍一看将 __PAGE_OFFSET 右移20位是一个很奇怪的事。但是,我们知道,如果要计算一个线性地址的全局页目录的索引,只需要将线性地址右移22位即可。由于全局页目录的每一项的大小为4个字节,因此,如果要计算某一项的偏移的字节数量,还需要乘以4,或者换个说法,左移2位,到这里就很明白了,右移动20位就相当于先右移动22位,然后再向左移动2位,这么一抵消,也只需要右移20位即可,因此 page_pde_offset 计算的是线性地址 __PAGE_OFFSET 对应的全局页目录项偏移的字节数量,更直白一点,就是项768偏移的字节数量。这样,这一行代码:
movl %ecx,page_pde_offset(%edx) /* Store kernel PDE entry */
还是页表的物理地址写入到全局页目录中的768项中。写完之后,将 edx 的值加4,毕竟需要往后移动一项了。
接下来开始填充页表,首先是这条指令:
movl $1024, %ecx
将1024写入到寄存器 ecx 中,这条指令是和下面的 stosl 指令联动的,用来表示 stosl 将会循环执行1024次。接着从物理地址0x00000000(物理页帧0的地址)开始,将地址值写入对应的表项,每循环一次将地址加上0x1000(下一物理页帧的地址)。同时, edi 的寄存器的值也会加4(下一表项的地址)。待循环结束之后,edi 寄存器的值就是紧随着页表之后的起始地址。到这里还远远没有结束,那么填充什么时候结束呢?这就和 INIT_MAP_BEYOND_END 宏有关了,该宏的定义如下:
#define INIT_MAP_BEYOND_END (128*1024)
128*1024的含义是128KB,再结合下面的指令:
leal (INIT_MAP_BEYOND_END+0x007)(%edi),%ebp
cmpl %ebp,%eax
jb 10b
可以知道,映射的物理页地址必须大于或者等于页表之后再偏移128KB(也就是 INIT_MAP_BEYOND_END)的地址,如果小于,那么就跳回 10b,继续重复上述的过程。最后将 edi 寄存器(也就是页表再加上128KB的地址)写入到 init_pg_tables_end 这个变量中,这个变量表示的就是内核的结束地址。
以上是关于linux 临时页目录和页表初始化分析的主要内容,如果未能解决你的问题,请参考以下文章
Linux从头学15:页目录和页表-理论 + 实例 + 图文的最完全最接地气详解
Linux从头学15:页目录和页表-理论 + 实例 + 图文的最完全最接地气详解