您的位置:首页 > 运维架构 > Linux

linux 内核 内存管理 初始化 页表

2011-11-09 21:17 736 查看
linux内核在启动分页机制时就已经有了一个可用的页表,这个最初的页表是手工创建了,而且仅有为数不多的几个页面,进入start_kernel()以后需要把原来的页表完善一下,具体工作在start_kernel() --> setup_arch() --> paging_init()函数中。paing_init()函数定义在arch/x86/mm/init_32.c中。

void __init paging_init(void)

{

#ifdef CONFIG_X86_PAE

set_nx();

if (nx_enabled)

printk("NX (Execute Disable) protection: active\n");

#endif

pagetable_init();

load_cr3(swapper_pg_dir);

#ifdef CONFIG_X86_PAE

if (cpu_has_pae)

set_in_cr4(X86_CR4_PAE);

#endif

__flush_tlb_all();

kmap_init();

}

页表初始化的工作由pagetable_init()函数完成,load_cr3()的功能是装载页表。

static void __init pagetable_init (void)

{

unsigned long vaddr, end;

pgd_t *pgd_base = swapper_pg_dir;

paravirt_pagetable_setup_start(pgd_base); //?什么用?

/* Enable PSE if available */

if (cpu_has_pse)

set_in_cr4(X86_CR4_PSE);

/* Enable PGE if available */

if (cpu_has_pge) {

set_in_cr4(X86_CR4_PGE);

__PAGE_KERNEL |= _PAGE_GLOBAL;

__PAGE_KERNEL_EXEC |= _PAGE_GLOBAL;

}

/* 把0~896M的物理页框映射到3G~3G+896M,在这个过程中会通过

* bootmem_allocator申请新的内存页,用以存放页表。 */

kernel_physical_mapping_init(pgd_base);

remap_numa_kva();
// null function

/* Fixed mappings, only the page table structure has to be

* created - mappings will be set by set_fixmap() */

vaddr = __fix_to_virt(__end_of_fixed_addresses - 1) & PMD_MASK;

end = (FIXADDR_TOP + PMD_SIZE - 1) & PMD_MASK;

page_table_range_init(vaddr, end, pgd_base);

/* 把固定映射内存区映射到页目录中 */

permanent_kmaps_init(pgd_base);

paravirt_pagetable_setup_done(pgd_base);

}

kernel_physical_mapping_init()函数把0~896M的物理地址映射到3G以上的虚拟空间。在这个过程中回向bootmem allocator申请需要的内存页(参考bootmem的初始化)。该函数定义在:arch/x86/mm/init_32.c,定义内容如下。

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); // get the index of pgd

pgd = pgd_base + pgd_idx; // get the pgd entry

pfn = 0;

for (; pgd_idx < PTRS_PER_PGD; pgd++, pgd_idx++) {

/* pmd is just pgd in x86 architecture's default config*/

pmd = one_md_table_init(pgd);

if (pfn >= max_low_pfn)//这个判断可以保证不建立高端内存对应的页表

continue; /* why not break ? 当where pfn >= max_low_pfn时,

* 虽然不用建立相应的页表,但是pmd这一层次的目录是需要建立的,

* 所以使用continue而不是break */

/* 下面这个for语句循环的次数取决于 PTRS_PER_PMD宏.

* PTRS_PER_PMD的定义在include/asm-generic/pgtable-nopmd.h中,被定义为1*/

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 {

/* 如果没有与该pmd entry相对应的page table,

* 则为该pmd entry申请一个新的页面作为page table,

* one_page_table_init()的返回值即是page table的首地址 */

pte = one_page_table_init(pmd);

/* 初始化每个pte的值*/

for (pte_ofs = 0; pte_ofs < PTRS_PER_PTE && pfn < max_low_pfn;

pte++, pfn++, pte_ofs++, address += PAGE_SIZE) {

if (is_kernel_text(address))

set_pte(pte, pfn_pte(pfn, PAGE_KERNEL_EXEC));

else

set_pte(pte, pfn_pte(pfn, PAGE_KERNEL));

}

}

}

}

}

函数中使用pmd作为过渡,是为了能够支持3级页表,而linux的x86默认配置是2级页表,所以pmd的存在代码的兼容性。linux在2.6.24中的页表是4级的,从顶到底分别是pgd, pud, pmd和pte,内核在默认配置下屏蔽了pud和pmd。pud的全称是page upper directory, pmd的全程是page middle directory。
内容来自用户分享和网络整理,不保证内容的准确性,如有侵权内容,可联系管理员处理 点击这里给我发消息
标签: