• 趣谈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
  • 相关阅读:
    数据同步
    闭包的内存泄漏解决办法
    No module named 'MySQLdb'
    pqi 更换pip 国内源
    BZOJ 1934 [Shoi2007]Vote 善意的投票
    BZOJ 2038 [2009国家集训队]小Z的袜子(hose)
    BZOJ 1002 [FJOI2007]轮状病毒
    BZOJ 3442 学习小组
    BZOJ 3261 最大异或和
    BZOJ 4029 [HEOI2015]定价
  • 原文地址:https://www.cnblogs.com/mysky007/p/12317831.html
Copyright © 2020-2023  润新知