linux内核版本:linux-4.4.y 基于arm64分析
推荐文章:http://www.voidcn.com/article/p-qtnltdel-up.html (Linux内存管理中的SPARSEMEM)
start_kernel -> setup_arch -> paging_init -> bootmem_init -> arm64_memory_present
arm64_memory_present,对于memblock管理的每一个memory region,为其创建mem_section结构体的内存空间,分配内存通过memblock模块进行,然后分配的mem_section类型的指针,指向静态定义的全局的mem_section指针数组,在分配mem_section类型的指针时,分配了一个PAGE的空间,SECTION_PER_ROOT * sizeof(struct mem_section),其中SECTION_PER_ROOT表示每个root中有多少个SECTION,sparse memory中使用1个PAGE能容纳多少个SECTION表示一个ROOT,即1个PAGE
/* Record a memory area against a node. */
//start 为memory region的物理起始地址的pfn,end为物理结束地址的pfn
void __init memory_present(int nid, unsigned long start, unsigned long end)
{
unsigned long pfn;
start &= PAGE_SECTION_MASK; // 保留起始pfn对应的 section num,section内的偏移mask为0
mminit_validate_memmodel_limits(&start, &end); // 验证 start 是否超过最大物理的地址,或着end超过最大物理地址
for (pfn = start; pfn < end; pfn += PAGES_PER_SECTION) { //以section为单位,建立 mem_section结构体的内存空间,并不是分配 struct page 所占内存空间
unsigned long section = pfn_to_section_nr(pfn); // 取pfn对应的section num
struct mem_section *ms;
sparse_index_init(section, nid);
// 根据section num,首先判断此section属于哪个root section,然后判断此root section是否存在
// 如果存在,那么返回 -EEXIST,如果不存在,那么调用sparse_index_alloc,分配一个 mem_section
// 大小为1个page,分配成功,则将此结构体指针指向 静态定义的 mem_section指针数组,按照root section num存放
// 因为1个page中存放固定数量的mem_section结构体,个数为:PAGE_SIZE/sizeof(struct mem_section) = SECTION_PER_ROOT
// 假设SECTION_PER_ROOT = 4,即一个ROOT中存放4个mem_section的结构体,那么假设section num为13,那么,此section
// 便在 13 / 4 = 3 ,即mem_section[3]指针数组中(注:此处的mem_section不是结构体,而是静态全局变量)
// 所以此函数的作用为,根据section num,找出在哪个 mem_section的root中,如果mem_section[root]中的指针为空,那么
// 需要分配此 root mem_section的内存空间(大小为一个page),分配方式为 memblock模块,此时 slab is not available,
// 通过sparse_index_alloc函数操作,分配内存通过 memblock_virt_alloc_node
set_section_nid(section, nid);
// 另外还有一个全局静态数组,section_to_node_table,数组大小为系统中 SECTION大小,以此section num为索引,
// 设置此 mem_section 的 node id,因为 arm64中使用的是UMA,未使用NUMA,所以,nid都为0
ms = __nr_to_section(section);
// 如果section对应的mem_section的ROOT不存在,返回NULL,如果存在,则取此ROOT内的偏移
// 例如 section为 13,SECTION_PER_ROOT = 4,则 13 在 mem_section[3] 中,取mem_section[3][1]
// 即 ROOT内的偏移为多少,即可取得 此section对应的mem_section的地址
// 第一次初始化时,section_mem_map为空,所以执行下面的
if (!ms->section_mem_map)
ms->section_mem_map = sparse_encode_early_nid(nid) |
SECTION_MARKED_PRESENT;
// sparse_encode_early_nid(nid) 此函数 nid << SECTION_NID_SHIFT
// SECTION_MARKED_PRESENT 1<<0
// 将 nid存放在 ms->section_mem_map 中,且将mem_section marke 为 present,此时并未分配 struct page结构体的内存空间
}
}
综上分析,memory_present根据memblock的memory region的start pfn,end pfn,来分配全局静态变量mem_section数组对应的指针,根据 start pfn,将start pfn中 mem_section位保留,低于mem_section的位设置位0,然后取得 mem_section的section number,根据此number,计算此number对应的root,然后以root为索引,在 全局 mem_section数组中找到root mem_section,然后再根据number,找到在 root mem_section中的具体的mem_section
分配好了 memory region 对应的 mem_section数据结构后,初始化mem_section对应的数据成员,这里将 mem_section中的section_mem_map 进行初始化,即将nid放在section_mem_map中,置位present位,表示此mem_section有对应的物理内存,可以为其分配 struct page结构体的内存空间,那么接下来的工具就是初始化 ms->section_mem_map,分配struct page结构体对应的内存空间,然后指向ms->section_mem_map
另外,此函数中将nid放在了两个地方,一个是ms->section_mem_map中,一个是全局静态数组section_to_node_table,具体为啥要设置这两处,通过搜索 section_to_node_table 的引用,发现只有 page_to_nid 这个函数有使用到此全局静态数组,所以此全局静态数组的设置的意义为,通过struct page结构体指针找到对应的nid,仅此而已