浅析linux内核内存管理之最终内核页表
在系统初始化的时候进行了最终内核映射,主要在paging_init函数中:
499void __init paging_init(void)
500{
501#ifdef CONFIG_X86_PAE
502 set_nx();
503 if (nx_enabled)
504 printk("NX (Execute Disable) protection: active\n");
505#endif
506
507 pagetable_init();
508
509 load_cr3(swapper_pg_dir);
510
511#ifdef CONFIG_X86_PAE
512 /*
513 * We will bail out later - printk doesn't work right now so
514 * the user would just see a hanging kernel.
515 */
516 if (cpu_has_pae)
517 set_in_cr4(X86_CR4_PAE);
518#endif
519 __flush_tlb_all();
520
521 kmap_init();
522 zone_sizes_init();
523}
其中pagetable_init函数主要对直接映射区,永久映射区,固定映射区的页表进行了设置。当设置临时页表的时候,swapper_pg_dir是存放临时页全局目录,范围是0~4G。在建立最终内核页表的时候,仍然使用了swapper_pg_dir这个变量,在pagetable_init函数中对其进行了重新的设置,使其保存3G~4G的页全局目录部分的地址
把swapper_pg_dir的物理地址存放在cr3中
如果CONFIG_X86_PAE,则设置CR4寄存器的第5位,即PAE标志位
使tlb无效
临时内核映射占用的空间是固定内核映射的一部分,从FIX_KMAP_BEGIN~FIX_KMAP_END,由于之前已经设置了固定映射的页中间目录,页表等,所以临时内核映射的页表是存在的。在kmap_init函数中主要就是获得临时内核映射区的第一个页表,即FIX_KMAP_BEGIN对应的页表,将地址存放在kmap_pte变量中
下面分析pagetable_init是怎样建立内核地址空间与直接映射区物理内存的映射的:
310static void __init pagetable_init (void)
311{
312 unsigned long vaddr;
313 pgd_t *pgd_base=swapper_pg_dir;
314
315#ifdef CONFIG_X86_PAE
316 int i;
317 /* Init entries of the first-level page table to the zero page */
318 for (i=0; 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);
335 remap_numa_kva();
336
337 /*
338 * Fixed mappings, only the page table structure has to be
339 * created - mappings will be set by set_fixmap():
340 */
341 vaddr=__fix_to_virt(__end_of_fixed_addresses - 1) & PMD_MASK;
342 page_table_range_init(vaddr, 0, pgd_base);
343
344 permanent_kmaps_init(pgd_base);
345
346#ifdef CONFIG_X86_PAE
347 /*
348 * Add low memory identity-mappings - SMP needs it when
349 * starting up on an AP from real-mode. In the non-PAE
350 * case we already have these mappings through head.S.
351 * All user-space mappings are explicitly cleared after
352 * SMP startup.
353 */
354 pgd_base[0] = pgd_base[USER_PTRS_PER_PGD];
355#endif
356}
如果是PAE的,那么使用页目录指针表(PDPT),此时pgd只有4项,将其填充为零页的地址
如果设置了PSE,则置位CR4寄存器的第4位
如果设置了PGE,则置位CR4寄存器的第7位
设置直接映射区的页表结构
获得固定映射区起始的线性地址,调用page_table_range_init函数,设置固定映射部分的master kernel page directory,pmd,pte。
调用permanent_kmaps_init函数设置永久映射部分。建立从PKMAP_BASE~PKMAP_BASE+PAGE_SIZE*LASTKMAP的映射,永久映射只使用内核中的一个页表,所以在开启PAE的时候为2MB,否则为4MB,并将页表的地址放入pkmap_page_table变量中
直接映射区的页表建立由kernel_physical_mapping_init函数完成:
143static void __init kernel_physical_mapping_init(pgd_t *pgd_base)
144{
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);
152 pgd=pgd_base+ pgd_idx;
153 pfn=0;
154
155 for (; pgd_idx
156 pmd=one_md_table_init(pgd);
157 if (pfn >= max_low_pfn)
158 continue;
159 for (pmd_idx=0; pmd_idx
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);
173
174 for (pte_ofs=0; pte_ofs
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 }
183}
通过pgd_index计算PAGE_OFFSET在临时页全局目录中的索引值
由于要映射的是直接映射区,所以物理页从物理页0开始
one_md_table_init函数和one_page_table_init函数会调用alloc_bootmem_low_pages分配pmd,pt占用的page,并填充pgd,pmd相应的相应的表项
建立直接映射区物理页与内核线性空间的映射,如果是内核代码段设置为可读,写,执行
注意如果开启了PAE,则设置512项的pmd,否则1项的pmd;当开启PAE,则pte为512项,否则1024项
这样,最终内核页表初始化完成。总结一下:在系统初始化的时候,将直接映射区,永久映射区和固定映射区进行了pgd,pmd,pte的填充。其中直接映射区建立了物理地址到线性地址的映射,而其他的在需要的时候通过kmap,kmap_atomic,set_fixmap进行物理地址到线性地址的映射
再多说一部分,也是系统初始化,页表相关的最后一部分。看一下mem_init函数,除了主要任务bootmem allocator与buddy system的交接之外,还设置了高端内存的页(调用set_highmem_pages_init函数),并调用zap_low_mappings函数清low_memory的映射,内核线程只访问内核空间是不能访问用户空间的,其实low_memory的映射被设置的部分也就是当初为8MB建立的恒等映射填充了临时内核页全局目录的第0项,第1项
376void zap_low_mappings (void)
377{
378 int i;
379
380 save_pg_dir();
381
382 /*
383 * Zap initial low-memory mappings.
384 *
385 * Note that "pgd_clear()" doesn't do it for
386 * us, because pgd_clear() is a no-op on i386.
387 */
388 for (i=0; i
389#ifdef CONFIG_X86_PAE
390 set_pgd(swapper_pg_dir+i, __pgd(1 + __pa(empty_zero_page)));
391#else
392 set_pgd(swapper_pg_dir+i, __pgd(0));
393#endif
394 flush_tlb_all();
395}