arm64内存-sparsemem-【转载】linux内存-sparse内存模型初始化

 

版权声明:本文为CSDN博主「Huo的藏经阁」的原创文章,遵循CC 4.0 BY-SA版权协议,转载请附上原文出处链接及本声明。
原文链接:https://blog.csdn.net/weixin_42730667/article/details/117840820

 

由于现在运行的设备中大都采用sparse内存模型,而《understanding the linux virtual memory manager》书中主要以2.4和2.6内核源码基础上进行讲解当时并没有sparse模型,故重新以5.8.10代码为基础梳理下内存相关代码。

memblocks_present()

memblocks_present()函数是sparse内存模型初始化接口,意思是有一个新的内存块上线,不管是初始化还是热插拔内存都会进入到该函数里面,代码如下:

{
 struct memblock_region *reg;
 
     for_each_memblock(memory, reg) {
         memory_present(memblock_get_region_node(reg),
          memblock_region_memory_base_pfn(reg),
          memblock_region_memory_end_pfn(reg));
      }
 }

 

该函数代码比较简单:

  • for_each_memblock(),遍历所有的memblock, memblock为在启动阶段早期的内存管理模块,主要记录已经上线的所有内存块,此时sparse内存模型数据还未初始化,memblock主要记录每个内存block的物理内存大小,pfn等信息。此时memblocks_present()函数会遍历每个已经上线的内存,并进行内存管理初始化(memblock详细信息暂不涉及)。
  • memblock_get_region_node(reg):获取memblock所属的node -id,即位于NUMA系统中的哪个node-id中。
  • memblock_region_memory_base_pfn(reg):获取memblock在全局内存中负责起始的pfn
  • memblock_region_memory_end_pfn(reg): 获取memblock的结束pfn
  • memory_present():是sparse内存模型的一个memblock的初始化函数。

memory_present()

根据获取到的memblock初始化对应的mem_section中的信息:

  1.   
  2. /* Record a memory area against a node. */
  3.  void __init memory_present(int nid, unsigned long start, unsigned long end)
  4.  {
  5.          unsigned long pfn;
  6.  
  7.          #ifdef CONFIG_SPARSEMEM_EXTREME
  8.          if (unlikely(!mem_section)) {
  9.                   unsigned long size, align;
  10.  
  11.                    size = sizeof(struct mem_section*) * NR_SECTION_ROOTS;
  12.                    align = 1 << (INTERNODE_CACHE_SHIFT);
  13.                    mem_section = memblock_alloc(size, align);
  14.                    if (!mem_section)
  15.                           panic("%s: Failed to allocate %lu bytes align=0x%lx\n",
  16.                                   __func__, size, align);
  17.         }
  18.        #endif
  19.  
  20.         start &= PAGE_SECTION_MASK;
  21.         mminit_validate_memmodel_limits(&start, &end);
  22.         for (pfn = start; pfn < end; pfn += PAGES_PER_SECTION) {
  23.                   unsigned long section = pfn_to_section_nr(pfn);
  24.                   struct mem_section *ms;
  25.  
  26.                  sparse_index_init(section, nid);
  27.                  set_section_nid(section, nid);
  28.  
  29.                  ms = __nr_to_section(section);
  30.                  if (!ms->section_mem_map) {
  31.                          ms->section_mem_map = sparse_encode_early_nid(nid) |
  32.                                            SECTION_IS_ONLINE;
  33.                          section_mark_present(ms);
  34.                   }
  35.          }
  36.  }
  • CONFIG_SPARSEMEM_EXTREME特性 主要是将mem_section数组不再是静态初始化好,为了节省mem_section内存将mem_section修改未动态申请内存,注意由于此时内存管理还没有初始化完成,所以kmalloc/vmalloc等之类函数都无法使用,只能使用memblock_alloc申请一个mem_section节点。,如果没有开启CONFIG_SPARSEMEM_EXTREME功能则意味着mem_section使用的是一个静态二维数组,如下:
  1.  #ifdef CONFIG_SPARSEMEM_EXTREME
     
  2.        struct mem_section **mem_section;
     
  3.  #else
     
  4.         struct mem_section mem_section[NR_SECTION_ROOTS][SECTIONS_PER_ROOT]
     
  5.  ____cacheline_internodealigned_in_smp;
     
  6.  #endif
     

为了加快mem_section访问速度,要求mem_section为cacheline对齐

  • start &= PAGE_SECTION_MASK,保证start为 PAGE_SECTION_MASK对齐
  • mminit_validate_memmodel_limits():对start和end pfn做检查,是否超过了运行范围.
  • for (pfn = start; pfn < end; pfn += PAGES_PER_SECTION): 将该memblock 按照section处理,每个section可以容纳 PAGES_PER_SECTION处理,接下来初始化mem_section数据
  • unsigned long section = pfn_to_section_nr(pfn): 根据pfn 获取到被划分到的mem_section id
  • sparse_index_init(section, nid):初始化sparse mem_section如果开启CONFIG_SPARSEMEM_EXTREME 则需要 申请  mem_section (为mem_section 对象分配内存 ), 如果非开启则什么都不处理
  • set_section_nid(): 只有NODE_NOT_IN_PAGE_FLAGS开启才有效,section_to_node_table[section_nr] 主要存储这nr与node id对应管理表,方便查找
  • __nr_to_section():根据section id获取到对应的mem_section为后面初始化mem_section.
  1.  static inline struct mem_section *__nr_to_section(unsigned long nr)
     
  2.  {
     
  3.        #ifdef CONFIG_SPARSEMEM_EXTREME
     
  4.        if (!mem_section)
     
  5.                return NULL;
     
  6.        #endif
     
  7.         if (!mem_section[SECTION_NR_TO_ROOT(nr)])
     
  8.                return NULL;
     
  9.          return &mem_section[SECTION_NR_TO_ROOT(nr)][nr & SECTION_ROOT_MASK];
     
  10.  }
     
  • 如果ms->section_mem_map 还没有建立则先设置该section 所属的node id,注意mem_map是个多功能变量,除了记录mem_map 之外还记录了所属node id已经状态标记
  1.  static inline unsigned long sparse_encode_early_nid(int nid)
     
  2.  {
     
  3.       return (nid << SECTION_NID_SHIFT);
     
  4.  }
     
  • section_mark_present 设置该section状态为上线状态,同时更新__highest_present_section_nr,如果当前present的section nr超过__highest_present_section_nr,则更新_highest_present_section_nr,方便后续变量使用
  1.  static void section_mark_present(struct mem_section *ms)
     
  2.  {
     
  3.          unsigned long section_nr = __section_nr(ms);
     
  4.  
  5.         if (section_nr > __highest_present_section_nr)
     
  6.                    __highest_present_section_nr = section_nr;
     
  7.  
  8.        ms->section_mem_map |= SECTION_MARKED_PRESENT;
     
  9.  }
     

 section_mem_map划分

       section_mem_map处于不同的阶段其划分不同,在处于early早期阶段,其划分如下

这是因为在early阶段 还没有为mem_map分配物理内存,为了节省空间将后面的字段用于记录node_id,后面调用sparse_init()之后,将会为mem_map分配物理内存,early阶段将结束,section_mem_map将会变成下面分配结果:

前4个为section状态标记:

  • marked_present:表明该mem_section 的物理内存是否出现过,有可能之前上过线 但是又下线
  • has_mem_map:mem_section中的mem_map是否已经设置
  • on_line:该内存mem_section是否在线
  • early:是否在早期初始化阶段
  • map_base:应该的mem_map base 

各个宏定义如下:

  1.  #define SECTION_MARKED_PRESENT (1UL<<0)
     
  2.  #define SECTION_HAS_MEM_MAP (1UL<<1)
     
  3.  #define SECTION_IS_ONLINE (1UL<<2)
     
  4.  #define SECTION_IS_EARLY (1UL<<3)
     
  5.  #define SECTION_MAP_LAST_BIT (1UL<<4)
     
  6.  #define SECTION_MAP_MASK (~(SECTION_MAP_LAST_BIT-1))
     
  7.  #define SECTION_NID_SHIFT 3
     

注意:使用section_mem_map时一定要注意该section在处于什么阶段 。

sparse_init()

sparse 内存管理数据初始化,memory_present()只是初始化了section_mem_map的状态信息 以及归属与哪个node id,但是还没有为mem_map数组申请内存,并初始化每个page,代码如下:

  1. /*
  2. * Allocate the accumulated non-linear sections, allocate a mem_map
  3. * for each and record the physical to section mapping.
  4.  */
     
  5.  void __init sparse_init(void)
     
  6.  {
     
  7.       unsigned long pnum_begin = first_present_section_nr();
     
  8.       int nid_begin = sparse_early_nid(__nr_to_section(pnum_begin));
     
  9.       unsigned long pnum_end, map_count = 1;
     
  10.  
  11.        /* Setup pageblock_order for HUGETLB_PAGE_SIZE_VARIABLE */
     
  12.        set_pageblock_order();
     
  13.  
  14.        for_each_present_section_nr(pnum_begin + 1, pnum_end) {
     
  15.               int nid = sparse_early_nid(__nr_to_section(pnum_end));
     
  16.  
  17.               if (nid == nid_begin) {
     
  18.                        map_count++;
     
  19.                        continue;
     
  20.               }
     
  21.              /* Init node with sections in range [pnum_begin, pnum_end) */
     
  22.             sparse_init_nid(nid_begin, pnum_begin, pnum_end, map_count);
     
  23.             nid_begin = nid;
     
  24.             pnum_begin = pnum_end;
     
  25.             map_count = 1;
     
  26.       }
     
  27.        /* cover the last node */
     
  28.        sparse_init_nid(nid_begin, pnum_begin, pnum_end, map_count);
     
  29.         vmemmap_populate_print_last();
     
  30.  }
     
  • 首先调用first_present_section_nr()函数获取到第一个mem_section id即pnum_begin
  • sparse_early_nid(__nr_to_section(pnum_begin),获取到第一个mem_section所属的node id.
  • set_pageblock_order(),如何开启CONFIG_HUGETLB_PAGE_SIZE_VARIABLE功能,则设置pageblock_order,后续为buddy 伙伴算法使用
  • for_each_present_section_nr(pnum_begin + 1, pnum_end)  从pnum_begin开始遍历每个mem_section,准备初始化mem_section
  • sparse_early_nid(__nr_to_section(pnum_end), 在early 阶段,mem_map中存储的时node id, 获取到相应的node id 
  • 如果(nid == nid_begin) 则说明为同一个node内的section ,这是因为在同一个node内的内存section 为连续的,所以一次性初始化node 节点内所以的物理内存section,如果为同一个section,则map_count++,记录该内存有多少个section
  • sparse_init_nid(nid_begin, pnum_begin, pnum_end, map_count) :初始化指定section 范围,范围为[pnum_begin, pnum_end)
  • 更新下一个node 节点 nid_begin, pnum_begin以及map_count,
  • 循环完成之后,初始化最后一个node 节点的section,因为最后一个section没有初始化,需要在后面单独初始化。

sparse_init_nid()

sparse_init_nid()主要功能为初始化一个node 节点上的mem_section结果初始化,该函数一次性将一个节点的所有mem_section进行初始化:

  1.  
  2.  /*
     
  3.  * Initialize sparse on a specific node. The node spans [pnum_begin, pnum_end)
     
  4.  * And number of present sections in this node is map_count.
     
  5.  */
     
  6.  static void __init sparse_init_nid(int nid, unsigned long pnum_begin,
     
  7.  unsigned long pnum_end,
     
  8.  unsigned long map_count)
     
  9.  {
     
  10.      struct mem_section_usage *usage;
     
  11.      unsigned long pnum;
     
  12.      struct page *map;
     
  13.  
  14.      usage = sparse_early_usemaps_alloc_pgdat_section(NODE_DATA(nid),
     
  15.      mem_section_usage_size() * map_count);
     
  16.      if (!usage) {
     
  17.          pr_err("%s: node[%d] usemap allocation failed", __func__, nid);
     
  18.          goto failed;
     
  19.      }
     
  20.      sparse_buffer_init(map_count * section_map_size(), nid);
     
  21.      for_each_present_section_nr(pnum_begin, pnum) {
     
  22.           unsigned long pfn = section_nr_to_pfn(pnum);
     
  23.  
  24.           if (pnum >= pnum_end)
     
  25.                break;
     
  26.  
  27.           map = __populate_section_memmap(pfn, PAGES_PER_SECTION,
     
  28.                                     nid, NULL);
     
  29.          if (!map) {
     
  30.                pr_err("%s: node[%d] memory map backing failed. Some memory will not be available.",
     
  31.                          __func__, nid);
     
  32.                pnum_begin = pnum;
     
  33.               goto failed;
     
  34.          }
     
  35.          check_usemap_section_nr(nid, usage);
     
  36.          sparse_init_one_section(__nr_to_section(pnum), pnum, map, usage,
     
  37.          SECTION_IS_EARLY);
     
  38.          usage = (void *) usage + mem_section_usage_size();
     
  39.      }
     
  40.      sparse_buffer_fini();
     
  41.      return;
     
  42.  failed:
     
  43.       /* We failed to allocate, mark all the following pnums as not present */
     
  44.       for_each_present_section_nr(pnum_begin, pnum) {
     
  45.              struct mem_section *ms;
     
  46.  
  47.             if (pnum >= pnum_end)
     
  48.                     break;
     
  49.            ms = __nr_to_section(pnum);
     
  50.            ms->section_mem_map = 0;
     
  51.       }
     
  52.  }
     
  • 调用sparse_early_usemaps_alloc_pgdat_section()接口为mem_section中的mem_section_usage 申请内存,注意是从mem_block直接获取到物理内存. spase内存模型将物理内存划分成页,同时为每个页设置类型(可迁移,不可迁移 ,可回收等),方便后续进行碎片化管理,为了方便对页类型管理,内核将按照page block记录页类型,即将多个page 组成一个block(一般为pageblock_order管理管理,sparse模型是将页按照(1UL << (PFN_SECTION_SHIFT - pageblock_order))  组织进行管理记录页类型。
  • sparse_buffer_init(map_count * section_map_size(), nid): 该函数提前将一个节点内的物理内存管理结构mem_map申请好,后续每个mem_section的mem_map直接从sparsemap_buf中进行划分。:
  1.  static void __init sparse_buffer_init(unsigned long size, int nid)
     
  2.  {
     
  3.      phys_addr_t addr = __pa(MAX_DMA_ADDRESS);
     
  4.      WARN_ON(sparsemap_buf); /* forgot to call sparse_buffer_fini()? */
     
  5.      /*
     
  6.      * Pre-allocated buffer is mainly used by __populate_section_memmap
     
  7.      * and we want it to be properly aligned to the section size - this is
     
  8.      * especially the case for VMEMMAP which maps memmap to PMDs
     
  9.      */
     
  10.      sparsemap_buf = memblock_alloc_exact_nid_raw(size, section_map_size(),
     
  11.               addr, MEMBLOCK_ALLOC_ACCESSIBLE, nid);
     
  12.      sparsemap_buf_end = sparsemap_buf + size;
     
  13.  }
     

 

  • for_each_present_section_nr(pnum_begin, pnum): 依次遍历该节点内的mem_section,进行后续初始化。
  • map = __populate_section_memmap(pfn, PAGES_PER_SECTION    nid, NULL),直接从sparsemap_buf中获取到该section 对应的mem_map
  •                在 CONFIG_SPARSEMEM_VMEMMAP 下,此函数在 mm/sparse-vmemmap.c 中,将 pfn 对应 struct page 对象所在的 物理 内存,映射到 VMEMMAP 区域
  • check_usemap_section_nr(nid, usage):对usage进行检查是否属于该节点,只有再开启CONFIG_MEMORY_HOTREMOVE才有效
  • sparse_init_one_section:初始化一个mem_section
  • usage = (void *) usage + mem_section_usage_size(): usage进行偏移 为下一个mem_section做准备
  • sparse_buffer_fini: 初始化该node节点所有mem_section之后,检查sparsemap_buf是否还有剩余,如果有剩余则释放掉多余的不使用的内存。

sparse_init_one_section

sparse_init_one_section初始化一个section:

  1.  static void __meminit sparse_init_one_section(struct mem_section *ms,
     
  2.  unsigned long pnum, struct page *mem_map,
     
  3.  struct mem_section_usage *usage, unsigned long flags)
     
  4.  {
     
  5.      ms->section_mem_map &= ~SECTION_MAP_MASK;
     
  6.      ms->section_mem_map |= sparse_encode_mem_map(mem_map, pnum)
     
  7.                  | SECTION_HAS_MEM_MAP | flags;
     
  8.      ms->usage = usage;
     
  9.  }
     

主要是设置section_mem_map以及usage ,并设置该section状态。

 

 

 

posted @ 2022-04-03 17:29  张志伟122  阅读(360)  评论(0编辑  收藏  举报