分类: LINUX
2013-07-05 15:52:35
内核维持着一组自己使用的页表,驻留在主内核全局目录中,主内核页全局目录的最高目录项部分作为参考模型,为系统中每个普通进程对应的页全局目录项提供参考模型(进程的内核态从oxc0000000-----oxffffffff的线性地址!)
内核页表的初始化化分为2个阶段:
第一阶段:在还未启动分页机制下初始化化一个寻址范围在0---8M的内核页表,这个最小限度的地址空间仅能内核装载到RAM和对其初始化核心数据结构。该部分是由startup_32()汇编语言函数实现的(arch/i386/kernel/head.s)以下是startup_32中初始化内核页表的代码片段及其解析:
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) /* 此时的edi中存放pg0+0x2000,将此值存入init_pg_tables_end中,表示页表初始化结束 */
.......................................................
/*
* Enable paging 启用分页机制
*/
movl $swapper_pg_dir-__PAGE_OFFSET,%eax
movl %eax,%cr3 /* set the page table pointer.. */
movl %cr0,%eax
orl $0x80000000,%eax
movl %eax,%cr0 /* ..and set paging (PG) bit */
注意代码中的子针变量都减去一个线性偏移_PAGE_OFFSET。这是因为在编译内核时。这些变量引用的是启用分页后线性空间的地址。所以为得到实际的物理地址应减去这个线性偏移量。
第二阶段:初始化最终内核页表,注意此时分页机制已启动。初始化使得从oxc0000000开始的线性地址转化为从0开始的物理地址。
初始化发生在paging_init()函数中,而该函数位于setup_arch()中,而setup_arch()在start_kernel函数中被调用。
paging_init()函数执行步骤:
1:调用pagetable_init建立内核页表;
2:把swapper_pg_dir的物理地址写道cr3寄存器中;
3:根据CPU能力及编译内核时的配置,正确的设置cr4的PAE标志位;
4:调用__flush_tlb_all使得TLB(转换后援缓冲器)的所用项无效。
重点来看pagetable_init函数:
该函数首先获得页全局目录的线性地址,然后将其传入kernel_physical_mapping_init函数,该函数最终建立线性地址到物理地址的线性映射关系。kernel_physical_mapping_init代码及解析如下:
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;
/* Map with big pages if possible, otherwise create normal page tables. */
if (cpu_has_pse) {
unsigned int address2 = (pfn + PTRS_PER_PTE - 1) * PAGE_SIZE + PAGE_OFFSET + PAGE_SIZE-1;
if (is_kernel_text(address) || is_kernel_text(address2))
set_pmd(pmd, pfn_pmd(pfn, PAGE_KERNEL_LARGE_EXEC));
else
set_pmd(pmd, pfn_pmd(pfn, PAGE_KERNEL_LARGE));
pfn += PTRS_PER_PTE;
} else {
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));
}
}
}
}
}
调用结束后还要初始化固定地址映射的线性地址页表代码如下:
vaddr = __fix_to_virt(__end_of_fixed_addresses - 1) & PMD_MASK; //将固定地址映射页表放在页全局目录的1023项(最后一项)
page_table_range_init(vaddr, 0, pgd_base);
最后为了初始化高地址内存而调用permanent_kmaps_init方法。