页目录表及页表在定义如下:在 http://lxr.linux.no/#linux-bk+v2.6.11.5/include/asm-i386/pgtable.h#L34
extern pgd_t swapper_pg_dir[1024];
extern unsigned long pg0[1024];
其中swapper_pg_dir 为页目录,pg0 为一临时页表。
何以见得swapper_pg_dir是内核的页目录?
在内核的启动过程中,有如下代码:在http://lxr.linux.no/#linux-bk+v2.6.11.5/arch/i386/kernel/head.S#L182
1 2 3 4 5 6 7 8 9 |
183/* 184 * Enable paging 185 */ 186 movl $swapper_pg_dir-__PAGE_OFFSET,%eax 187 movl %eax,%cr3 /* set the page table pointer.. */ 188 movl %cr0,%eax 189 orl $0x80000000,%eax 190 movl %eax,%cr0 /* ..and set paging (PG) bit */ 191 ljmp $__BOOT_CS,$1f /* Clear prefetch and normalize %eip */ |
从这里我们看出,内核启动时,内核把swapper_pg_dir的地址存入CR3寄存器中,并设置CR0中的PG位,打开分页机制。
函数paging_init()通过调用pagetable_init()对swapper_pg_dir进行填充、建立页表。我们来看 pagetable_init()的代码:在http://lxr.linux.no/#linux-bk+v2.6.11.5/arch/i386 /mm/init.c#L310
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 26 27 |
310 static void __init pagetable_init (void) 311 { 312 unsigned long vaddr; 313 pgd_t *pgd_base = swapper_pg_dir; 314 315#ifdef CONFIG_X86_PAE //PAE用于内存大于4G的情况,我们不讨论 316 int i; 317 /* Init entries of the first-level page table to the zero page */ 318 for (i = 0; i < PTRS_PER_PGD; i++) 319 set_pgd(pgd_base + i, __pgd(__pa(empty_zero_page) | _PAGE_PRESENT)); 320#endif 321 322 /* Enable PSE if available */ 323 if (cpu_has_pse) { 324 set_in_cr4(X86_CR4_PSE); 325 } 326 327 /* Enable PGE if available */ 328 if (cpu_has_pge) { 329 set_in_cr4(X86_CR4_PGE); 330 __PAGE_KERNEL |= _PAGE_GLOBAL; 331 __PAGE_KERNEL_EXEC |= _PAGE_GLOBAL; 332 } 333 334 kernel_physical_mapping_init(pgd_base);//这个函数进行映射的建立 ...... ...... |
在继续深究之前,我们有必要了解几个相关的函数和宏。为了避免使本文臃肿、打乱本文的主线,我把相关内容写在了另一篇文章里
我们看kernel_physical_mapping_init()做了什么:在http://lxr.linux.no/#linux-bk+v2.6.11.5/arch/i386/mm/init.c#L143
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 26 27 28 29 30 31 32 33 34 35 36 37 |
145 unsigned long pfn; 146 pgd_t *pgd; 147 pmd_t *pmd; 148 pte_t *pte; 149 int pgd_idx, pmd_idx, pte_ofs; 150 151 pgd_idx = pgd_index(PAGE_OFFSET);//只建立第769~1024项 152 pgd = pgd_base + pgd_idx; 153 pfn = 0; 154 155 for (; pgd_idx < PTRS_PER_PGD; pgd++, pgd_idx++) { 156 pmd = one_md_table_init(pgd);//其实pmd被赋值为pgd,这是为了忽略PMD。 157 if (pfn >= max_low_pfn) 158 continue; 159 for (pmd_idx = 0; pmd_idx < PTRS_PER_PMD && pfn < max_low_pfn; pmd++, pmd_idx++) {//PTRS_PER_PMD为1,所以这个循环只执行一次,达到忽略PMD的目的。见http://lxr.linux.no/#linux-bk+v2.6.11.5/include/asm-generic/pgtable-nopmd.h#L16 160 unsigned int address = pfn * PAGE_SIZE + PAGE_OFFSET; 161 162 /* Map with big pages if possible, otherwise create normal page tables. */ 163 if (cpu_has_pse) { 164 unsigned int address2 = (pfn + PTRS_PER_PTE - 1) * PAGE_SIZE + PAGE_OFFSET + PAGE_SIZE-1; 165 166 if (is_kernel_text(address) || is_kernel_text(address2)) 167 set_pmd(pmd, pfn_pmd(pfn, PAGE_KERNEL_LARGE_EXEC)); 168 else 169 set_pmd(pmd, pfn_pmd(pfn, PAGE_KERNEL_LARGE)); 170 pfn += PTRS_PER_PTE; 171 } else { 172 pte = one_page_table_init(pmd);//这个函数新建一页表,并把该页表基址设置在该pgd(pmd与pgd相等)所指向的页目录中。 173 174 for (pte_ofs = 0; pte_ofs < PTRS_PER_PTE && pfn < max_low_pfn; pte++, pfn++, pte_ofs++) {设置pte指向的页表项为pfn(即Page Frame Number)所指示的页基址。 175 if (is_kernel_text(address)) 176 set_pte(pte, pfn_pte(pfn, PAGE_KERNEL_EXEC)); 177 else 178 set_pte(pte, pfn_pte(pfn, PAGE_KERNEL));// 179 } 180 } 181 } 182 } |
多次循环后,第769~1024项页目录项及相应页表便建立起来了,这些页表建立了PAGE_OFFSET以上的896MB低端内核空间与物理内存的前max_low_pfn页的的映射。
内核空间的页表swapper_pg_dir建立了,由于每个进程公享内核空间,所以每个进程也就有了内核空间到物理空间的映射了。
进程还有各自的虚拟空间(也叫用户空间),进程的虚拟空间与物理空间之间的映射又是怎样建立的呢?让我开始踏上新的旅程吧:进程的虚拟空间
发表评论
要发表评论,您必须先登录。