Linux 内核临时页表初始化
Linux 内核临时页表初始化
基于 linux 2.6.30.4分析x86平台相关代码
- 64位系统: 使用四级分页或三级分页,跟硬件有关。
- 未开启PAE(物理地址扩展)的32位系统: 只使用二级分页,页上级目录和页中间目录里的值全为0。
- 开启PAE的32位系统: 使用三级分页,这种情况下被排除在外的是页上级目录,也就是页上级目录中所有值都为0。
表项
实际上页全局目录、页上级目录、页中间目录、页表都是保存在一个一个页框中,我们知道常规情况下页框大小为4K(特殊情况有2MB、1GB),也就是页框的布局都是以4K倍数的地址进行排列的,要寻址一个页框,只需要20位地址就足够了。这些目录和页表中保存的都是表项,页全局目录保存的是页全局目录项,页中间目录保存的是页中间目录项,在32位系统中这些项都是32位(20位是所指页框的基地址,12位是标志位)的,在开启PAE后会变成64位,这些项保存着很多标志,我们罗列几个重要的:
Present
标志:为1,所指的页在内存中,为0,不在。- 所指的页框基地址:占20位。
Accessed
标志:每当分页单元对相应页框进行寻址时设置。Dirty
标志:只用于页表项,每次对一个页框进行写操作时设置。Read/Write
标志:读写权限标志。User/Supervisor
标志:所指的页的特权级(进程能否访问)。Page size
标志:为1表示指的是2MB或4MB的页框。也就是页表是2MB或者4MB。
在这些里面,最重要的或许就是所指页框基地址了,一个页中间目录项保存的页框基地址就是对应的页表的基地址,而页表项中保存的页框基地址,就是页(用于保存数据)的地址。而Present标志是用于判断是否发生缺页异常处理的标志。由于这些标志加上所指的页框基地址一共32位,一个4K的页框就能够保存1024个表项。
物理地址扩展(PAE)
这个技术是用于X86_32位体系下的,因为32位线性地址最多能表示4GB大小的空间,而PAE技术将物理地址线扩大到36条,也就是CPU能够寻址64GB大小的物理内存。但是物理地址线扩大到36条,但是线性地址还是使用32位,这时候没办法用32位的线性地址去表示64GB大小的物理内存。实际上PAE做的就是让内核有多个“主内核页全局目录”,第一个主内核页全局目录寻址04GB的地址,第二个寻址58GB的地址,所以当寻址不同区域的地址时,只需要将不同的“主内核页全局目录”基地址存入cr3中。这些多个主内核页全局目录被称为页目录指针表(PDPT)。
开启PAE后,32位系统寻址方式将大大改变:
- 二级分页会变成三级分页
- 表项的大小也由原来的32位变成了64位(原来是12位标志+20位页框基地址,变成12位标志+24位页框基地址)。
- 页框大小将可选择4K或者2MB,通过修改表项中的Page size标志即可指定所指页框大小。
- 线性地址表示也变成如下:
- 当把线性地址映射为4KB的页时(页目录项中的PS标志清零),32位线性地址按以下方式解释:
- cr3 : 指向一个PDPT;
- 位31-30 : 指向PDPT中4项中的一个;
- 位29-21 : 指向页目录中512个项中的一个;
- 位20-12 : 指向页表中512个项中的一个;
- 位 11 - 0: 4KB页中的偏移量;
- 当把线性地址映射为2MB的页(页目录项中的PS标志置1)时,32位线性地址按下列方式解释:
- cr3 : 指向一个PDPT;
- 位31-30 : 指向PDPT中4项中的一个;
- 位29-21 : 指向页目录中512个项中的一个;
- 位 20 - 0: 2MB页中的偏移量;
- 当把线性地址映射为4KB的页时(页目录项中的PS标志清零),32位线性地址按以下方式解释:
临时内核页表的构造
x86系统刚刚启动的时候,运行在实模式下,这个时候线性地址就是物理地址。为了进入32位保护模式,首先就要启用分页。这就要求我们构建一个页表:这张页表把线性地址映射转换为物理地址。
为了解决构造页表时鸡生蛋 蛋生鸡的问题,Linux使用了一个临时的内核页表。它只有两个页表项(这里指用来索引页框的最后一级页表)。在不启用PAE(Page Addression Extension) 和 PSE (Page Size Extension)的情况下,一个页表可以指向 2^10 = 1024
个内存页,一个内存页 4k , 所以两个页表允许索引8M的内存。
swapper_pg_dir
顶层的页目录 (page directory) 使用全局变量 swapper_pg_dir
定义。
|
|
.fill 1024, 4, 0
表示用0填充1024个 4 byte 长度的内存(一个页目录项 page table entry 的大小是32bits 即 4Byte)。
pg0
ULK3 P74 原文 : 临时页全局目录放在 swapper_pg_dir地址处,临时页表在pg0地址处,紧接在内核bss段之后。在linux 2.6.30.4中没有找到pg0变量。在分析代码时发现,应该是替换为了变量 __brk_base
|
|
初始化过程
|
|
stosl 指令 : 相当于将eax中的值保存到 ES:EDI 所指向的地址中,若设置了EFLAGS中的方向位,则EDI自减4否则自增4。
loop 指令 : 借助ECX寄存器作为计数实现循环,每轮循环先将ECX减一,再判断ECX的值,循环直到ECX为0时止。
leal 指令 :load effective address 功能是取偏移地址
mov 是将数据从源传到目的; lea是将源目的地址传到目的
例如
movl 18(%eax), %ebx # 将内存中 %eax+18内存处的内容,传递到 %ebx中;
leal 18(%eax), %ebx # 将(18+%eax中内容)的值,传入%ebx;
|
|
分步骤来看
初始化变量
设置页全局目录
pde : page directory entry 页目录表
先将
pg0
的物理地址+PDE_IDENT_ATTR -> %ecx
; 即将页表的地址加上页表的属性写入到%ECX
;%ecx -> swapper_pg_dir
的物理地址;即将页表的地址写入到页目录中;%ecx -> swapper_pg_dir
的物理地址+page_pde_offset
;将页表地址写入到内核页表所在位置;4+%edx
; 即将指针往后移动四个字节,指向第二个页目录项,为初始化第二个页表做准备;最后将1024写入
%ecx
, 作为初始化页表时的循环变量。
初始化页表
```c
11:
stosl ;将%eax的内容(0+PRESENT+RW)复制到 %es:%edi ,即pg0第一个表项处,并%edi+4 ;
addl $0x1000,%eax ; %eax+0x1000, 即 %eax+4K,
loop 11b ; %ecx-=1, 若%ecx!=0, 跳转到标号11处,继续执行,即循环1024次。
```
初始化剩下的页表
其中
_end
在前边连接器脚本中定义,MAPPING_BEYOND_END
定义如下,PTE_IDENT_ATTR
为页表的属性。1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25
/* * This is how much memory in addition to the memory covered up to * and including _end we need mapped initially. * We need: * (KERNEL_IMAGE_SIZE/4096) / 1024 pages (worst case, non PAE) * (KERNEL_IMAGE_SIZE/4096) / 512 + 4 pages (worst case for PAE) * * Modulo rounding, each megabyte assigned here requires a kilobyte of * memory, which is currently unreclaimed. * * This should be a multiple of a page. * * KERNEL_IMAGE_SIZE should be greater than pa(_end) * and small than max_low_pfn, otherwise will waste some page table entries */ #if PTRS_PER_PMD > 1 #define PAGE_TABLE_SIZE(pages) (((pages) / PTRS_PER_PMD) + PTRS_PER_PGD) #else #define PAGE_TABLE_SIZE(pages) ((pages) / PTRS_PER_PGD) #endif /* Enough space to fit pagetables for the low memory linear map */ MAPPING_BEYOND_END = \ PAGE_TABLE_SIZE(((1<<32) - __PAGE_OFFSET) >> PAGE_SHIFT) << PAGE_SHIFT
启用分页
初始化过程的最后执行了 jmp 3f
, 标号3处执行PAE相关的判断和操作,最后跳转到了标号6处执行。
|
|