问题:
1. 内核态内存映射函数 vmalloc、kmap_atomic 是如何工作的;
2. 内核态页表是放在哪里的,如何工作的?
3. swapper_pg_dir 是怎么回事;
4. 出现了内核态缺页异常应该怎么办?
内核页表
注意:和用户态页表不同,在系统初始化的时候,我们就要创建内核页表了
从内核页表的根 swapper_pg_dir 开始找线索,在 arch/x86/include/asm/pgtable_64.h 中就能找到它的定义
1 extern pud_t level3_kernel_pgt[512];2 extern pud_t level3_ident_pgt[512];3 extern pmd_t level2_kernel_pgt[512];4 extern pmd_t level2_fixmap_pgt[512];5 extern pmd_t level2_ident_pgt[512];6 extern pte_t level1_fixmap_pgt[512];7 externpgd_t init_top_pgt[];8
9
10 #define swapper_pg_dir init_top_pgt
swapper_pg_dir 指向内核最顶级的目录 pgd,同时出现的还有几个页表目录。
64 位系统的虚拟地址空间的布局:
其中 XXX_ident_pgt 对应的是直接映射区
XXX_kernel_pgt 对应的是内核代码区
XXX_fixmap_pgt 对应的是固定映射区
内核页表的顶级目录 init_top_pgt,
init_top_pgt 有三项:
第一项:
指向的是 level3_ident_pgt,也即直接映射区页表的三级目录
为什么要减去 __START_KERNEL_map 呢?
因为 level3_ident_pgt 是定义在内核代码里的,写代码的时候,写的都是虚拟地址,谁写代码的时候也不知道将来加载的物理地址是多少
因为 level3_ident_pgt 是在虚拟地址的内核代码段里的,而 __START_KERNEL_map 正是虚拟地址空间的内核代码段的起始地址,level3_ident_pgt 减去 __START_KERNEL_map 才是物理地址
第二项:PGD_PAGE_OFFSET
__PAGE_OFFSET_BASE 是虚拟地址空间里面内核的起始地址。第二项也指向 level3_ident_pgt,直接映射区
第三项:PGD_START_KERNEL
__START_KERNEL_map 是虚拟地址空间里面内核代码段的起始地址。第三项指向 level3_kernel_pgt,内核代码区
vmalloc 和 kmap_atomic 原理
在虚拟地址空间里面,有个 vmalloc 区域,从 VMALLOC_START 开始到 VMALLOC_END,可以用于映射一段物理内存。
1 /**2 * vmalloc - allocate virtually contiguous memory3 * @size: allocation size4 * Allocate enough pages to cover @size from the page level5 * allocator and map them into contiguous kernel virtual space.6 *7 * For tight control over page level allocator and protection flags8 * use __vmalloc() instead.9 */
10 void *vmalloc(unsigned longsize)11 {12 return__vmalloc_node_flags(size, NUMA_NO_NODE,13 GFP_KERNEL);14 }15
16
17 static void *__vmalloc_node(unsigned long size, unsigned longalign,18 gfp_t gfp_mask, pgprot_t prot,19 int node, const void *caller)20 {21 return__vmalloc_node_range(size, align, VMALLOC_START, VMALLOC_END,22 gfp_mask, prot, 0, node, caller);23 }
kmap_atomic 的实现:
1 void *kmap_atomic_prot(struct page *page, pgprot_t prot)2 {3 ......4 if (!PageHighMem(page))5 returnpage_address(page);6 ......7 vaddr = __fix_to_virt(FIX_KMAP_BEGIN +idx);8 set_pte(kmap_pte-idx, mk_pte(page, prot));9 ......10 return (void *)vaddr;11 }12
13
14 void *kmap_atomic(struct page *page)15 {16 returnkmap_atomic_prot(page, kmap_prot);17 }18
19
20 static __always_inline void *lowmem_page_address(const struct page *page)21 {22 returnpage_to_virt(page);23 }24
25
26 #define page_to_virt(x) __va(PFN_PHYS(page_to_pfn(x)
1. 如果是 32 位有高端地址的,就需要调用 set_pte 通过内核页表进行临时映射;
2. 如果是 64 位没有高端地址的,就调用 page_address,里面会调用 lowmem_page_address。
3. 其实低端内存的映射,会直接使用 __va 进行临时映射。
内核态缺页异常
kmap_atomic 发现,没有页表的时候,就直接创建页表进行映射了。而 vmalloc 没有,它只分配了内核的虚拟地址。所以,访问它的时候,会产生缺页异常
内核态的缺页异常还是会调用 do_page_fault,这个函数前面分析了已经,
说一下vmalloc_fault:
1 /*
2 * 32-bit:3 *4 * Handle a fault on the vmalloc or module mapping area5 */
6 static noinline int vmalloc_fault(unsigned longaddress)7 {8 unsigned longpgd_paddr;9 pmd_t *pmd_k;10 pte_t *pte_k;11
12
13 /*Make sure we are in vmalloc area:*/
14 if (!(address >= VMALLOC_START && address
17
18 /*
19 * Synchronize this task's top level page-table20 * with the 'reference' page table.21 *22 * Do _not_ use "current" here. We might be inside23 * an interrupt in the middle of a task switch..24 */
25 pgd_paddr =read_cr3_pa();26 pmd_k =vmalloc_sync_one(__va(pgd_paddr), address);27 if (!pmd_k)28 return -1;29
30
31 pte_k =pte_offset_kernel(pmd_k, address);32 if (!pte_present(*pte_k))33 return -1;34
35
36 return 0
总结:
1、物理内存管理
物理内存根据 NUMA 架构分节点。每个节点里面再分区域。每个区域里面再分页。
物理页面通过伙伴系统进行分配。分配的物理页面要变成虚拟地址让上层可以访问,kswapd 可以根据物理页面的使用情况对页面进行换入换出。
2、内存分配内核态
对于内核态,kmalloc 在分配大内存的时候,以及 vmalloc 分配不连续物理页的时候,直接使用伙伴系统,分配后转换为虚拟地址,访问的时候需要通过内核页表进行映射
对于 kmem_cache 以及 kmalloc 分配小内存,则使用 slub 分配器,将伙伴系统分配出来的大块内存切成一小块一小块进行分配。
kemem_cache和kmalloc的部分不会被换出、因为用这两个函数分配的内存多用于保存内核关键的数据结构,内核中vmalloc分配的部分会被换出,因而当访问的时候、发现不再
就会调用do_page_fault
3、内存分配用户态
对于用户态的内存分配,或者直接调用 mmap 系统调用分配,或者调用 malloc。调用 malloc 的时候,如果分配小的内存,就用 sys_brk 系统调用;如果分配大的内存,还是用 sys_mmap 系统调用。
正常情况下,用户态的内存都是可以换出的,因而一旦发现内存中不存在,就会调用 do_page_fault
4、内存分配体系总图
PGD_START_KERNEL