趣谈Linux操作系统学习笔记-内存管理(26讲)-- 内核的映射机制

问题:

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 extern pgd_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 memory
 3  *  @size:    allocation size
 4  *  Allocate enough pages to cover @size from the page level
 5  *  allocator and map them into contiguous kernel virtual space.
 6  *
 7  *  For tight control over page level allocator and protection flags
 8  *  use __vmalloc() instead.
 9  */
10 void *vmalloc(unsigned long size)
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 long align,
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     return page_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   return kmap_atomic_prot(page, kmap_prot);
17 }
18 
19 
20 static __always_inline void *lowmem_page_address(const struct page *page)
21 {
22   return page_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 area
 5  */
 6 static noinline int vmalloc_fault(unsigned long address)
 7 {
 8   unsigned long pgd_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 < VMALLOC_END))
15     return -1;
16 
17 
18   /*
19    * Synchronize this task's top level page-table
20    * with the 'reference' page table.
21    *
22    * Do _not_ use "current" here. We might be inside
23    * 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
posted @ 2020-02-16 17:53  坚持,每天进步一点点  阅读(814)  评论(0编辑  收藏  举报