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中的信息:
-
-
/* Record a memory area against a node. */
-
void __init memory_present(int nid, unsigned long start, unsigned long end)
-
{
-
unsigned long pfn;
-
-
-
if (unlikely(!mem_section)) {
-
unsigned long size, align;
-
-
size = sizeof(struct mem_section*) * NR_SECTION_ROOTS;
-
align = 1 << (INTERNODE_CACHE_SHIFT);
-
mem_section = memblock_alloc(size, align);
-
if (!mem_section)
-
panic("%s: Failed to allocate %lu bytes align=0x%lx\n",
-
__func__, size, align);
-
}
-
-
-
start &= PAGE_SECTION_MASK;
-
mminit_validate_memmodel_limits(&start, &end);
-
for (pfn = start; pfn < end; pfn += PAGES_PER_SECTION) {
-
unsigned long section = pfn_to_section_nr(pfn);
-
struct mem_section *ms;
-
-
sparse_index_init(section, nid);
-
set_section_nid(section, nid);
-
-
ms = __nr_to_section(section);
-
if (!ms->section_mem_map) {
-
ms->section_mem_map = sparse_encode_early_nid(nid) |
-
SECTION_IS_ONLINE;
-
section_mark_present(ms);
-
}
-
}
-
}
- CONFIG_SPARSEMEM_EXTREME特性 主要是将mem_section数组不再是静态初始化好,为了节省mem_section内存将mem_section修改未动态申请内存,注意由于此时内存管理还没有初始化完成,所以kmalloc/vmalloc等之类函数都无法使用,只能使用memblock_alloc申请一个mem_section节点。,如果没有开启CONFIG_SPARSEMEM_EXTREME功能则意味着mem_section使用的是一个静态二维数组,如下:
-
-
struct mem_section **mem_section;
-
-
struct mem_section mem_section[NR_SECTION_ROOTS][SECTIONS_PER_ROOT]
-
____cacheline_internodealigned_in_smp;
-
为了加快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.
-
static inline struct mem_section *__nr_to_section(unsigned long nr)
-
{
-
-
if (!mem_section)
-
return NULL;
-
-
if (!mem_section[SECTION_NR_TO_ROOT(nr)])
-
return NULL;
-
return &mem_section[SECTION_NR_TO_ROOT(nr)][nr & SECTION_ROOT_MASK];
-
}
- 如果ms->section_mem_map 还没有建立则先设置该section 所属的node id,注意mem_map是个多功能变量,除了记录mem_map 之外还记录了所属node id已经状态标记
-
static inline unsigned long sparse_encode_early_nid(int nid)
-
{
-
return (nid << SECTION_NID_SHIFT);
-
}
- section_mark_present 设置该section状态为上线状态,同时更新__highest_present_section_nr,如果当前present的section nr超过__highest_present_section_nr,则更新_highest_present_section_nr,方便后续变量使用
-
static void section_mark_present(struct mem_section *ms)
-
{
-
unsigned long section_nr = __section_nr(ms);
-
-
if (section_nr > __highest_present_section_nr)
-
__highest_present_section_nr = section_nr;
-
-
ms->section_mem_map |= SECTION_MARKED_PRESENT;
-
}
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
各个宏定义如下:
注意:使用section_mem_map时一定要注意该section在处于什么阶段 。
sparse_init()
sparse 内存管理数据初始化,memory_present()只是初始化了section_mem_map的状态信息 以及归属与哪个node id,但是还没有为mem_map数组申请内存,并初始化每个page,代码如下:
-
/*
-
* Allocate the accumulated non-linear sections, allocate a mem_map
-
* for each and record the physical to section mapping.
-
*/
-
void __init sparse_init(void)
-
{
-
unsigned long pnum_begin = first_present_section_nr();
-
int nid_begin = sparse_early_nid(__nr_to_section(pnum_begin));
-
unsigned long pnum_end, map_count = 1;
-
-
/* Setup pageblock_order for HUGETLB_PAGE_SIZE_VARIABLE */
-
set_pageblock_order();
-
-
for_each_present_section_nr(pnum_begin + 1, pnum_end) {
-
int nid = sparse_early_nid(__nr_to_section(pnum_end));
-
-
if (nid == nid_begin) {
-
map_count++;
-
continue;
-
}
-
/* Init node with sections in range [pnum_begin, pnum_end) */
-
sparse_init_nid(nid_begin, pnum_begin, pnum_end, map_count);
-
nid_begin = nid;
-
pnum_begin = pnum_end;
-
map_count = 1;
-
}
-
/* cover the last node */
-
sparse_init_nid(nid_begin, pnum_begin, pnum_end, map_count);
-
vmemmap_populate_print_last();
-
}
- 首先调用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进行初始化:
-
-
/*
-
* Initialize sparse on a specific node. The node spans [pnum_begin, pnum_end)
-
* And number of present sections in this node is map_count.
-
*/
-
static void __init sparse_init_nid(int nid, unsigned long pnum_begin,
-
unsigned long pnum_end,
-
unsigned long map_count)
-
{
-
struct mem_section_usage *usage;
-
unsigned long pnum;
-
struct page *map;
-
-
usage = sparse_early_usemaps_alloc_pgdat_section(NODE_DATA(nid),
-
mem_section_usage_size() * map_count);
-
if (!usage) {
-
pr_err("%s: node[%d] usemap allocation failed", __func__, nid);
-
goto failed;
-
}
-
sparse_buffer_init(map_count * section_map_size(), nid);
-
for_each_present_section_nr(pnum_begin, pnum) {
-
unsigned long pfn = section_nr_to_pfn(pnum);
-
-
if (pnum >= pnum_end)
-
break;
-
-
map = __populate_section_memmap(pfn, PAGES_PER_SECTION,
-
nid, NULL);
-
if (!map) {
-
pr_err("%s: node[%d] memory map backing failed. Some memory will not be available.",
-
__func__, nid);
-
pnum_begin = pnum;
-
goto failed;
-
}
-
check_usemap_section_nr(nid, usage);
-
sparse_init_one_section(__nr_to_section(pnum), pnum, map, usage,
-
SECTION_IS_EARLY);
-
usage = (void *) usage + mem_section_usage_size();
-
}
-
sparse_buffer_fini();
-
return;
-
failed:
-
/* We failed to allocate, mark all the following pnums as not present */
-
for_each_present_section_nr(pnum_begin, pnum) {
-
struct mem_section *ms;
-
-
if (pnum >= pnum_end)
-
break;
-
ms = __nr_to_section(pnum);
-
ms->section_mem_map = 0;
-
}
-
}
- 调用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中进行划分。:
-
static void __init sparse_buffer_init(unsigned long size, int nid)
-
{
-
phys_addr_t addr = __pa(MAX_DMA_ADDRESS);
-
WARN_ON(sparsemap_buf); /* forgot to call sparse_buffer_fini()? */
-
/*
-
* Pre-allocated buffer is mainly used by __populate_section_memmap
-
* and we want it to be properly aligned to the section size - this is
-
* especially the case for VMEMMAP which maps memmap to PMDs
-
*/
-
sparsemap_buf = memblock_alloc_exact_nid_raw(size, section_map_size(),
-
addr, MEMBLOCK_ALLOC_ACCESSIBLE, nid);
-
sparsemap_buf_end = sparsemap_buf + size;
-
}
- 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:
-
static void __meminit sparse_init_one_section(struct mem_section *ms,
-
unsigned long pnum, struct page *mem_map,
-
struct mem_section_usage *usage, unsigned long flags)
-
{
-
ms->section_mem_map &= ~SECTION_MAP_MASK;
-
ms->section_mem_map |= sparse_encode_mem_map(mem_map, pnum)
-
| SECTION_HAS_MEM_MAP | flags;
-
ms->usage = usage;
-
}
主要是设置section_mem_map以及usage ,并设置该section状态。