• Linux内核源码分析之setup_arch (三)


     

    1. 前言

    Linux内核源码分析之setup_arch (二) 中介绍了当前启动阶段的内存分配函数memblock_alloc,该内存分配函数在本篇将要介绍paging_init中用于页表和内存的分配,paging_init函数大致流程如下图所示。

    2. paging_init

    2.1 build_mem_type_table

    该函数根据具体的CPU架构对静态定义的mem_types数组中定义的属性进行调整。

    2.2 prepare_page_table

    该函数的作用是把页目录项清零,源码大致如下。首先是把虚拟地址范围[0, MODULES_VADDR]的页目录项清零,如果内核是模块区域以XIP方式运行的,则跳过内核部分的页目录,然后继续对区域[addr,PAGE_OFFSET]的页目录项清零,此时用户空间的页目录项已经全部清零;最后,把除了第一块内存条之外的内核空间[__phys_to_virt(end), VMALLOC_START]对应的页目录项清零。

    /* arch/arm/mm/mmu.c */
    static inline void prepare_page_table(void)
    {
     unsigned long addr;
     phys_addr_t end;
      /* <--(1)--> */
     for (addr = 0; addr < MODULES_VADDR; addr += PMD_SIZE)
      pmd_clear(pmd_off_k(addr));
    
    #ifdef CONFIG_XIP_KERNEL
     addr = ((unsigned long)_etext + PMD_SIZE - 1) & PMD_MASK;
    #endif
      /* <--(2)--> */
     for ( ; addr < PAGE_OFFSET; addr += PMD_SIZE)
      pmd_clear(pmd_off_k(addr));
    
     end = memblock.memory.regions[0].base + memblock.memory.regions[0].size;
     if (end >= lowmem_limit)
      end = lowmem_limit;
      /* <--(3)--> */
     for (addr = __phys_to_virt(end);
          addr < VMALLOC_START; addr += PMD_SIZE)
      pmd_clear(pmd_off_k(addr));
    }
    

    2.3 map_lowmem

    该函数将物理内存地址小于lowmem_limit的内存映射到内核空间,实际的内存映射工作在create_mapping中完成。

    /* arch/arm/mm/mmu.c */
    static void __init map_lowmem(void)
    {
     ...
     for_each_memblock(memory, reg) {
      start = reg->base;
      end = start + reg->size;
    
      if (end > lowmem_limit)
       end = lowmem_limit;
      if (start >= end)
       break;
    
      map.pfn = __phys_to_pfn(start);
      map.virtual = __phys_to_virt(start);
      map.length = end - start;
      map.type = MT_MEMORY;
    
      create_mapping(&map, false);
     }
    }
    

    create_mapping函数的大致流程如下图所示,这里需要提一下,linux内核使用的是四级页表,即PGD、PUD、PMD、PTE;而ARM32使用的是二级页表,即PMD、PTE。同时由于内存管理是以页为单位进行的,如果按照ARM硬件MMU的分页机制,一个PMD对应的PTE并不能完全占用完整个页,为了避免内存浪费,会在软件层面上将两个PMD对应的PTE放在一个页内,具体细节可以参考文件arch/arm/include/asm/pgtable-2level.h中的注释部分。最终会调用alloc_init_pte函数对指定范围的内存区域进行映射,其中的early_pte_alloc函数最终也会去调用 Linux内核源码分析之setup_arch (二) 中介绍的memblock_alloc函数来分配内存,最后将PTE所在页写入到PMD中即可完成映射。

    /* arch/arm/mm/mmu.c */
    static void __init alloc_init_pte(...)
    {
     pte_t *start_pte = early_pte_alloc(pmd);
     pte_t *pte = start_pte + pte_index(addr);
    
     do {
      set_pte_ext(pte, pfn_pte(pfn, __pgprot(type->prot_pte)), 0);
      pfn++;
     } while (pte++, addr += PAGE_SIZE, addr != end);
     early_pte_install(pmd, start_pte, type->prot_l1);
    }
    

    2.4 devicemaps_init

    该函数大致流程如下图所示,首先调用early_alloc分配一个页,然后调用early_trap_init将向量表复制到新的页内,最后调用create_mapping将这个页映射到0xffff0000处,如果mdesc->map_io存在,还会对设备相关的IO进行映射。

    2.5 kmap_init

    这个函数非常简单,把大小为2MB的区间[PKMAP_BASE,PAGE_OFFSET]映射到内核空间。

    /* arch/arm/mm/mmu.c */
    static void __init kmap_init(void)
    {
    #ifdef CONFIG_HIGHMEM
     pkmap_page_table = early_pte_alloc_and_install(pmd_off_k(PKMAP_BASE),
      PKMAP_BASE, _PAGE_KERNEL_TABLE);
    #endif
    }
    

    3. 总结

    本文主要介绍了内核启动阶段页表初始化部分的内容,其中,build_mem_type_table负责根据不同CPU架构对mem_types进行调整,prepare_page_table负责将待初始化区域的页目录项清零,然后通过map_lowmem建立低端内存区域的页表映射,最后调用devicemaps_init建立对向量表和设备IO的映射。至此,除了bootmem_init函数没有分析之外,paging_init基本算是分析完了,bootmem_init的分析将在下一篇中给出。

  • 相关阅读:
    一个Spring的应用看起来象什么?
    IOC的优点是什么?
    解释对象/关系映射集成模块?
    XMLBeanFactory?
    使用Spring框架的好处是什么?
    什么是Spring MVC框架的控制器?
    什么是Spring的MVC框架?
    在Spring AOP 中,关注点和横切关注的区别是什么?
    你更倾向用那种事务管理类型?
    Spring支持的事务管理类型?
  • 原文地址:https://www.cnblogs.com/wjxzs/p/14241414.html
Copyright © 2020-2023  润新知