表,因为在进入保护模式后, 内核继续初始化直到建立完整的内存映射机制之前, 仍然需要用到页表来映射相应的内存地址。 临时页表的初始化是在arch/i386/kernel/head.S中进行的:
swapper_pg_dir是临时页全局目录表, 它是在内核编译过程中静态初始化的.
pg0是第一个页表开始的地方, 它也是内核编译过程中静态初始化的.
内核通过以下代码建立临时页表:
ENTRY(startup_32)
…………
/* 得到开始目录项的索引,从这可以看出内核是在swapper_pg_dir的768个表项开始进行建立的, 其对应的线性地址就是0xc0000000以上的地址, 也就是内核在初始化它自己的页表 */
page_pde_offset = (__PAGE_OFFSET >> 20);
/* pg0地址在内核编译的时候, 已经是加上0xc0000000了, 减去0xc00000000得到对应的物理地址 */
movl $(pg0 - __PAGE_OFFSET), %edi
/* 将目录表的地址传给edx, 表明内核也要从0x00000000开始建立页表, 这样可以保证从以物理地址取指令到以线性地址在系统空间取指令的平稳过渡, 下面会详细解释 */
movl $(swapper_pg_dir - __PAGE_OFFSET), %edx
movl $0x007, %eax
leal 0x007(%edi),%ecx
Movl %ecx,(%edx)
movl %ecx,page_pde_offset(%edx)
addl $4,%edx
movl $1024, %ecx
11:
stosl addl $0x1000,%eax
loop 11b
/* 内核到底要建立多少页表, 也就是要映射多少内存空间, 取决于这个判断条件。在内核初始化程中内核只要保证能映射到包括内核的代码段,数据段, 初始页表和用于存放动态数据结构的128k大小的空间就行 */
leal (INIT_MAP_BEYOND_END 0x007)(%edi),%ebp
cmpl %ebp,%eax
jb 10b
movl %edi,(init_pg_tables_end - __PAGE_OFFSET)
在上述代码中, 内核为什么要把用户空间和内核空间的前几个目录项映射到相同的页表中去呢,虽然在head.S中内核已经进入保护模式,但是内核现在是处于保护模式的段式寻址方式下,因为内核还没有启用分页映射机制,现在都是以物理地址来取指令, 如果代码中遇到了符号地址,只能减去0xc0000000才行, 当开启了映射机制后就不用了现在cpu中的取指令指针eip仍指向低区,如果只建立内核空间中的映射, 那么当内核开启映射机制后, 低区中的地址就没办法寻址了,应为没有对应的页表, 除非遇到某个符号地址作为绝对转移或调用子程序为止。因此要尽快开启CPU的页式映射机制.
movl $swapper_pg_dir-__PAGE_OFFSET,%eax
movl %eax,%cr3 /* cr3控制寄存器保存的是目录表地址 */
movl %cr0,%eax /* 向cr0的最高位置1来开启映射机制 */
orl $0x80000000,%eax
movl %eax,%cr0
ljmp $__BOOT_CS,$1f /* Clear prefetch and normalize %eip */
1:
lss stack_start,%esp
通过ljmp $__BOOT_CS,$1f这条指令使CPU进入了系统空间继续执行 因为__BOOT_CS是个符号地址,地址在0xc0000000以上。
在head.S完成了内核临时页表的建立后,它继续进行初始化,包括初始化INIT_TASK,也就是系统开启后的第一个进程;建立完整的中断处理程序,然后重新加载GDT描述符,最后跳转到init/main.c中的start_kernel函数继续初始化.
3.3内核页表的完整建立
内核在start_kernel()中继续做第二阶段的初始化,因为在这个阶段中, 内核已经处于保护模式下,前面只是简单的设置了内核页表, 内核必须首先要建立一个完整的页表才能继续运行,因为内存寻址是内核继续运行的前提。
pagetable_init()的代码在mm/init.c中:
[start_kernel()>setup_arch()>paging_init()>pagetable_init()]
为了简单起见, 我忽略了对PAE选项的支持。
static void __init pagetable_init (void)
{
……
pgd_t *pgd_base = swapper_pg_dir;
……
kernel_physical_mapping_init(pgd_base);
……
}
在这个函数中pgd_base变量指向了swapper_pg_dir, 这正是内核目录表的开始地址,pagetable_init()函数在通过
kernel_physical_mapping_init()函数完成内核页表的完整建立。
kernel_physical_mapping_init函数同样在mm/init.c中, 我略去了与PAE模式相关的代码:
static void __init kernel_physical_mapping_init(pgd_t *pgd_base)
{
unsigned long pfn;
pgd_t *pgd;
pmd_t *pmd;
pte_t *pte;
int pgd_idx, pmd_idx, pte_ofs;
pgd_idx = pgd_index(PAGE_OFFSET);
pgd = pgd_base pgd_idx;
pfn = 0;
for (; pgd_idx < PTRS_PER_PGD; pgd , pgd_idx ) {
pmd = one_md_table_init(pgd);
if (pfn >= max_low_pfn)
continue;
for (pmd_idx = 0; pmd_idx < PTRS_PER_PMD && pfn < max_low_pfn; pmd , pmd_idx ) {
unsigned int address = pfn * PAGE_SIZE PAGE_OFFSET;
……
pte = one_page_table_init(pmd);
for (pte_ofs = 0; pte_ofs < PTRS_PER_PTE && pfn < max_low_pfn; pte , pfn , pte_ofs ) {
if (is_kernel_text(address))
set_pte(pte, pfn_pte(pfn, PAGE_KERNEL_EXEC));
else
set_pte(pte, pfn_pte(pfn, PAGE_KERNEL));
……
}
}
通过作者的注释, 可以了解到这个函数的作用是把整个物理内存地址都映射到从内核空间的开始地址,即从0xc0000000的整个内核空间中,直到物理内存映射完毕为止。这个函数比较长, 而且用到很多关于内存管理方面的宏定义,理解了这个函数, 就能大概理解内核是如何建立页表的,将这个抽象的模型完全的理解。 下面将详细分析这个函数:
函数开始定义了4个变量pgd_t *pgd, pmd_t *pmd, pte_t *pte, pfn;
pgd指向一个目录项开始的地址,pmd指向一个中间目录开始的地址,pte指向一个页表开始的地址pfn是页框号被初始为0. pgd_idx根据pgd_index宏计算结果为768,也是内核要从目录表中第768个表项开始进行设置。 从768到1024这个256个表项被linux内核设置成内核目录项,低768个目录项被用户空间使用. pgd = pgd_base pgd_idx; pgd便指
亿恩科技地址(ADD):郑州市黄河路129号天一大厦608室 邮编(ZIP):450008 传真(FAX):0371-60123888
联系:亿恩小凡
QQ:89317007
电话:0371-63322206 本文出自:亿恩科技【www.enkj.com】
服务器租用/服务器托管中国五强!虚拟主机域名注册顶级提供商!15年品质保障!--亿恩科技[ENKJ.COM]
|