linux内核版本:linux4.9.115(arm64)
文章目录
arm64_memblock_init()完成了memblock的初始化然后通过paging_init建立只能用于内核的页表开始初始化分页机制。分页机制完成后内核则需要通过bootmem_init函数开始进行内存基本数据结构的初始化工作且bootmem_init函数就是建立buddy内存管理方案的关键一步。本章主要分析arm 64架构的bootmem_init函数。(在进入分析函数前需要读者需要预先了解PFN,NUMA和uma,linux三种内存模型这3方面的知识:http://www.wowotech.net/memory_management/memory_model.html。)
1.bootmem_init()
void __init bootmem_init(void)
{
unsigned long min, max;
//获取最大,最小页号;此处求min和max的方法貌似舍弃掉了不足一个page大小的物理内存
min = PFN_UP(memblock_start_of_DRAM());
max = PFN_DOWN(memblock_end_of_DRAM());
/*
*可以看到如果使能CONFIG_MEMTEST,并且传递的command line 中保护 memtest 关键字的话,
*则内核会对没有使用的free memory做memtest,通过相关算法检测出存在问题的dram,
*将这些dram过reserve_bad_mem 保留起来不使用,从而保证系统能正常boot
*/
early_memtest(min << PAGE_SHIFT, max << PAGE_SHIFT);
max_pfn = max_low_pfn = max;
//若支持且使能numa则做一些numa相关的初始化工作
arm64_numa_init();
/*
*若配置了CONFIG_SPARSEMEM则通过arm64_memory_present()进行支持sparse memory模型相关数
*据结构的初始化工作,未配置则跳过该函数
*/
arm64_memory_present();
sparse_init();
zone_sizes_init(min, max);
memblock_dump_all();
}
2.arm64_memory_present
若内核配置了CONFIG_SPARSEMEM,调用该函数遍历memblock中所有的memory region,每个region划分为1G大小的section,并设置section对应的结构体struct mem_section的section_mem_map成员 ,主要是设置present位和nid位(section大小可以配置,对于arm64往往section大小为1G)。具体实现细节如下所示
-
1 arm64_memory_present
static void __init arm64_memory_present(void) { struct memblock_region *reg; /*针对每一个memblock中的memory type的region,调用memory_present函数分配memory region中每个 *section的struct *mem_section结构体的物理空间,并将nid放在mem_section结构体的section_mem_map *成员中然后置位section_mem_map的present位.表示该mem_section有对应的物理内存了,可以进行存储该 *section中所有页的struct page结构体物理空间的分配.需要注意的是: * (1)此函数并没进行struct page结构体对应物理空间的分配的操作 * (2)nid除了存储在ms->section_mem_map中,还会存储在全局静态数组section_to_node_table中,目 * 的是通过struct page结构体指针找到对应的nid。 */ for_each_memblock(memory, reg) { int nid = memblock_get_region_node(reg); memory_present(nid, memblock_region_memory_base_pfn(reg), memblock_region_memory_end_pfn(reg)); } }
-
memory_present
void __init memory_present(int nid, unsigned long start, unsigned long end) { unsigned long pfn; //保留起始pfn对应的 section num,section内的偏移mask为0 start &= PAGE_SECTION_MASK; // 对物理地址范围进行检查 mminit_validate_memmodel_limits(&start, &end); //以section为单位建立mem_section结构体的内存空间,并不分配 struct page所占内存空间 for (pfn = start; pfn < end; pfn += PAGES_PER_SECTION) { //获取pfn对应的section编号,高18位 unsigned long section = pfn_to_section_nr(pfn); struct mem_section *ms; /* *根据section编号,找出标号属于全局静态指针数组mem_section中的哪个root section,并判断该 *root section是否存在.如果mem_section[root]对应的指针不存在,需要分配一个大小为1个page的内存空 *间(类型为struct mem_section *)。然后将该地址指向对应的mem_section[root] */ sparse_index_init(section, nid); /* *给全局静态指针数组section_to_node_table赋值,数组的大小为系统中SECTION的数量。该数组元素是 *sectio区域对应的内存节点号node id,索引index是section num.对于arm64架构由 *于不支持NUMA所以nid都为0。作用是后续通过struct page结构体指针找到对应的nid. */ set_section_nid(section, nid); /* *如果section对应的静态指针数组mem_section的root不存在返回NULL,存在则取该root指向内存空间对应 *的偏移例如section=15,SECTION_PER_ROOT=4,则赋值mem_section[3]+3,即是mem_section[3][3] */ ms = __nr_to_section(section); /* *首次初始化时,ms对应的section_mem_map是NULL,此时将对应的nid放在ms->section_mem_map特定的位 *置,同时把ms->section_mem_map的第一位置为1,表示该mem_section num对应的mem_section区域为 *present,但是此时*该pfn对应的struct page结构体内存空间还未分配: *(1)sparse_encode_early_nid(nid) ---> nid << SECTION_NID_SHIFT *(2)SECTION_MARKED_PRESENT 1<<0 */ if (!ms->section_mem_map) ms->section_mem_map = sparse_encode_early_nid(nid) | SECTION_MARKED_PRESENT; } }
-
sparse_index_init
static int __meminit sparse_index_init(unsigned long section_nr, int nid) { //根据section num,首先判断此section属于哪个root section unsigned long root = SECTION_NR_TO_ROOT(section_nr); struct mem_section *section; //判断此root section是否存在,存在则返回-EEXIST if (mem_section[root]) return -EEXIST; /*若root section不存在则分配一个大小为1个page的内存空间,当分配成功则将该内存基地址指 *针指向静态定义的mem_section指针数组(地址类型转化为struct mem_section *)分配内存通 *过memblock_virt_alloc_node函数在对应节点上分配1page大小物理内存并返回虚拟地址 */ section = sparse_index_alloc(nid); if (!section) return -ENOMEM; mem_section[root] = section; return 0; }
3.sparse_init
该函数主要做两件事
-
- 给所有的理论存在的section内存区域分配一个unsigned long **usemap_map指针数组,类似于:unsigned long usemap_map [L1][L2],其中L1表示内存中最大的section 的个数即是NR_MEM_SECTIONS,L2表示每个section中有多少个page block。页块(page block)的的大小为2^(MAX_ORDER-1)个page。usemap_map指向的元素是一个物理内存空间首地址映射的虚拟地址,该内存空间存储着section中每个page block的位图数据该数据大小为4bit,与内存的回收机制相关:4bits=3bits Migrate + 1bit Skip。
- 给所有present的section分配物理空间用于存储section的每个页块位图信息,并将其虚拟地址填充在usemap_map对应的位置,非present section在数组中对应的元素指向NULL。
- 遍历所有present section,然后将其映射到vmemmap区域空间。
Ps:下面代码内核未配置CONFIG_SPARSEMEM_ALLOC_MEM_MAP_TOGETHER且配置了CONFIG_SPARSEMEM_VMEMMAP,若是其它配置分配方式可能出现差异
//mm/spase.c
void __init sparse_init(void)
{
unsigned long pnum;
struct page *map;
unsigned long *usemap;
unsigned long **usemap_map;
int size;
/* see include/linux/mmzone.h 'struct mem_section' definition */
BUILD_BUG_ON(!is_power_of_2(sizeof(struct mem_section)));
/* Setup pageblock_order for HUGETLB_PAGE_SIZE_VARIABLE */
set_pageblock_order();
/*NR_MEM_SECTIONS是理论是最大的section的数量*/
size = sizeof(unsigned long *) * NR_MEM_SECTIONS;
/*
*通过memblock模块分配usemap_map指向的物理内存并返回虚拟地址,实际上这里为所有可能存在的section分配了
*unsigned long * 类型的指针数组
*/
usemap_map = memblock_virt_alloc(size, 0);
if (!usemap_map)
panic("can not allocate usemap_map\n");
/*
*分别为内存各个节点的所有section在usemap_map中对应的位置进行赋值:
*(1)为所有节点中每个present的section分配物理空间用于存储各个section的位图数据,其中同一个节点中所有
* section 位图信息存储的物理内存分配是通过memblock按节点统一分配的,并返回内存空间基地址映射的虚拟地 址。
*(2)循环找出每个节点的所有present section在指针数组usemap_map对的应位置,然后把其虚拟地址赋给它。其中
* sparse_early_usemaps_alloc_node函数用于处理单个节点所有present section相关内存分配和虚拟地址赋值工 * 作,而alloc_usemap_and_memmap是循环调用sparse_early_usemaps_alloc_node处理每个节点中的所有的
* present section
*/
alloc_usemap_and_memmap(sparse_early_usemaps_alloc_node,
(void *)usemap_map);
/*
*(1)先给每个在线的section的struct section结构体中的pageblock_flags元素赋值。
*(2)由于还未对在线的section中所有页的struct page结构体分配存储空间,因此接着给每个在线的section分配了一段 * 物理空间用于存储struct page,同时物理地址映射的虚拟地址位于vmemmap区域中,可以通过pfn进行索引获取。
*(3)最后将section基地址pfn对应的struct page的虚拟地址经过相关编码工作,赋值给struct section结构体中的
* section_mem_map元素
*/
for (pnum = 0; pnum < NR_MEM_SECTIONS; pnum++) {
if (!present_section_nr(pnum))
continue;
usemap = usemap_map[pnum];
if (!usemap)
continue;
/*
*上面已经判定了该section为present,此处为该section的所有page的struct page结构体实例分配实际的虚拟空间
*和对应的物理空间,map为虚拟空间的的基地址
*/
map = sparse_early_mem_map_alloc(pnum);
if (!map)
continue;
//给当前section对应的结构体成员赋值
sparse_init_one_section(__nr_to_section(pnum), pnum, map,
usemap);
}
vmemmap_populate_print_last();
memblock_free_early(__pa(usemap_map), size);
}
-
sparse_early_usemaps_alloc_node:该函数处理单个节点所有在线的section的所有page_block位图存储空间的分配,最后将返回的虚拟地址赋值给usemap_map[pnum],后面具体赋值时可以通过section的编号获取该虚拟地址中:
static void __init sparse_early_usemaps_alloc_node(void *data, unsigned long pnum_begin, unsigned long pnum_end, unsigned long usemap_count, int nodeid) { void *usemap; unsigned long pnum; unsigned long **usemap_map = (unsigned long **)data; //section的pageblock bitmap大小 int size = usemap_size(); //动态分配当前节点上所有section的位图存储的物理空间,返回该空间首地址的虚拟地址 usemap = sparse_early_usemaps_alloc_pgdat_section(NODE_DATA(nodeid), size * usemap_count); if (!usemap) { pr_warn("%s: allocation failed\n", __func__); return; } /* *每个节点中所有section对应的位图信息存储在一个对应节点下的一段内存空间中,下面是把pnum_begin到 *pnum_end-1范围内的所有present section找出来,然后将上面给其分配的位图存储空间的虚拟地址填充在指针 *数组usemap_map对应的位置。pnum_begin到pnum_end-1表示当前节点中所有的section num */ for (pnum = pnum_begin; pnum < pnum_end; pnum++) { //跳过当前节点的非present section if (!present_section_nr(pnum)) continue; usemap_map[pnum] = usemap; //跳转到下一个present section位图信息存储的虚拟地址 usemap += size; check_usemap_section_nr(nodeid, usemap_map[pnum]); } }
-
alloc_usemap_and_memmap:该函数循环中的每一次迭代处理同一个节点中所有present的section。通过调用alloc_fun函数给当前节点的所有在线section分配对应的物理空间,用于存储该对应section的相关信息。最后把物理空间基地址的虚拟地址赋给usemap_map[pnum],通过section的num号进行索引:
static void __init alloc_usemap_and_memmap(void (*alloc_func) (void *, unsigned long, unsigned long, unsigned long, int), void *data) { unsigned long pnum; unsigned long map_count; int nodeid_begin = 0; unsigned long pnum_begin = 0; //遍历所有的section,找到第一个present的section for (pnum = 0; pnum < NR_MEM_SECTIONS; pnum++) { struct mem_section *ms; if (!present_section_nr(pnum)) continue; ms = __nr_to_section(pnum); nodeid_begin = sparse_early_nid(ms); pnum_begin = pnum; break; } //记录当前(nodeid_begin)节点上有多少个presentsection map_count = 1; //一个循环处理一个节点的所有preset section的物理空间分配,和虚拟的赋值操作 for (pnum = pnum_begin + 1; pnum < NR_MEM_SECTIONS; pnum++) { struct mem_section *ms; int nodeid; //跳过非present的section if (!present_section_nr(pnum)) continue; ms = __nr_to_section(pnum); nodeid = sparse_early_nid(ms); /* *此处结合for循环统计在nodeid_begin节点上的present section个数。 */ if (nodeid == nodeid_begin) { map_count++; continue; } /*每个循环到达此处代表该节点的present section的数量统计已经结束,且该节点在线的section的 *section num在范围(pnum_begin和pnum - 1)中(该范围中仍然有非present的section),alloc_func *指向的函数是给当前节点中所有在线的section分配相关物理地址空间,分配后返回物虚拟地址空间的基地 *址。并将基准地址放在数组data对应的位置(data用于存储指针的数组,长度为系统总section个数) */ alloc_func(data, pnum_begin, pnum, map_count, nodeid_begin); //为下一个节点处理初始化数据 nodeid_begin = nodeid; pnum_begin = pnum; map_count = 1; } /* ok, last chunk */ alloc_func(data, pnum_begin, NR_MEM_SECTIONS, map_count, nodeid_begin); }
Ps:上面函数(2)循环调用函数(1)来达到处理每个节点的目的
-
sparse_early_mem_maps_alloc:该函数最终作用是给每个在线的section分配一段物理空间用于存储该section中每个页的struct page,并将该物理空间映的地址与vmemmap区域对应的虚拟地址建立映射关系。最后返回基地址的虚拟地址用于后续给struct section的section_mem_map赋值(就是将section的section_mem_map映射到vmemmap区域):
static struct page __init *sparse_early_mem_map_alloc(unsigned long pnum) { struct page *map; struct mem_section *ms = __nr_to_section(pnum); int nid = sparse_early_nid(ms); map = sparse_mem_map_populate(pnum, nid); if (map) return map; pr_err("%s: sparsemem memory map backing failed some memory will not be available\n", __func__); ms->section_mem_map = 0; return NULL; }
sparse_mem_map_populate:**该函数有两条不同的线由内核配置CONFIG_SPARSEMEM_VMEMMAP控制:
- 若没有配置CONFIG_SPARSEMEM_VMEMMAP,则每个在线的section中所有page的struct page存储的物理地址空间和虚拟地址都由memblock模块分配,先申请物理空间再返回其对应的虚拟地址
- 若配置了CONFIG_SPARSEMEM_VMEMMAP,则先通过过pfn_to_page在vmemmap区域获取存储该section的所有页面对应的“struct page”结构体实例的虚拟地址,然后再通过vmemmap_populate函数分配对应的物理地址空间,并将上面获得的虚拟地址和新分配物理内容空间的物理地址在转换表和页表中建立映射关系。以后内核就可以通过内核虚拟地址空间的Vmemmap区间的虚拟地址来回去到物理页对应的struct page数据(1.先用物理页框号为索引,在Vmemmap区间获取到物理页struct page描述符的虚拟地址(pfn_to_page),2.然后将通过获取到的虚拟地址通过MMU,就能回去到该页的物理地址,也就能访问到struct page描述符对应的数据):
- 由于VMEMMAP的虚拟地址并未在转换页表和页表中建立过与物理地址的映射关系,因此vmemmap_populate先是通过虚拟地址找到其在转换页表中对应的entry的位置,并填写entry,然后通过memblock分配一个页大小的物理内存空间,用新分配页的物理页框号(pfn)来填pte页表中对应的entry。(注意新分配的物理页用来存该section中每个页面描述符struct page,且新页的物理空间在当前节点上分配)
本文分析的是配置了CONFIG_SPARSEMEM_VMEMMAP这种情况代码如下:
struct page * __meminit sparse_mem_map_populate(unsigned long pnum, int nid) { unsigned long start; unsigned long end; struct page *map; /* *分配虚拟地址空间,若定义了CONFIG_SPARSEMEM_VMEMMAP,该虚拟地址空间连续 *#define PAGES_PER_SECTION (1UL << PFN_SECTION_SHIFT) *#define pfn_to_page __pfn_to_page *#define __pfn_to_page(pfn) (vmemmap + (pfn)) *#define vmemmap ((struct page *)VMEMMAP_START - (memstart_addr >> PAGE_SHIFT) *#define VMEMMAP_START (PAGE_OFFSET - VMEMMAP_SIZE) *#define VMEMMAP_SIZE (UL(1) << (VA_BITS - PAGE_SHIFT- 1 + STRUCT_PAGE_MAX_SHIFT)) */ map = pfn_to_page(pnum * PAGES_PER_SECTION); start = (unsigned long)map; end = (unsigned long)(map + PAGES_PER_SECTION); /* *将分配的虚拟地址空间映射到实际的物理内存上 *(1)先将根据虚拟地址把对应的信息填写到所有转换页表(PGD=&init_mm,PUD,PMD)对应的entry上,因为 * VMEMMAP区域虚拟地址页表还未映射所以其虚拟地址对应的页表和转换页表上对应的entry还为0. *(2)对于pte页表,先找到虚拟地址对应的entry,然后通过memblock模块在当前节点上分配一个page的物理页, * 最后用刚分配物理页的pfn和该页的权限信息去填充对应的pte entry。 *在建立虚拟得知和物理地址的映射关系时,pgd页表就是以前初始化创建的swapper_pg_dir页表(物理地址在 *ttbr0寄存器中) */ if (vmemmap_populate(start, end, nid)) return NULL; return map; }
-
sparse_init_one_section:该函数给当前section的struct mem_section结构体的section_mem_map和page_block_flags赋值。其中section_mem_map是里面存储了多个section信息:(a)section中所页的struct page存储空间基地址的虚拟地址。(b)section的present信息。(c)section的map信息 (d)section首个pfn信息。(e)section的nid信息;page_block_flags代表的是section区域中所有pageblock的位图数据存储空间基地址的虚拟地址
static int __meminit sparse_init_one_section(struct mem_section *ms, unsigned long pnum, struct page *mem_map, unsigned long *pageblock_bitmap) { if (!present_section(ms)) return -EINVAL; //~SECTION_MAP_MASK: 11 ms->section_mem_map &= ~SECTION_MAP_MASK; /* * Subtle, we encode the real pfn into the mem_map such that *the identity pfn - section_mem_map will return the actual *physical page frame number. *mem_map指向的是存储ms区域中所有页的struct page实例的物理空间基地址对应的虚拟地址。 *mem_map不是所有位都用来存储虚拟地址。第一位用来表示该section是否present,而第二位表示 *该section是否map过(该map是指从section到page的map),第3位上几位用于保存nid数据: * (1)因为mem_map虚拟地址的低PAGE_SHIFT位全为0,所以mem_map理论上低PAGE_SHIFT位都能用来存储其它信息。 * 若需要使用mem_map作为虚拟地址时,只需将该mem_map与相关MASK做一个与运算即可获得实际的虚拟地址。 * (2)由于mem_map虚拟地址只能访问到存储ms区域第一个pfn对应的struct page实例的物理空间,并不能知道该 * stuct page代表哪一个pfn。于是此处用sparse_encode_mem_map将实际的pfn编码到mem_map中去下面是编 * 码和解码过程: * (a)编码:先通过section_nr_to_pfn(pnum)获取到ms区域的首个pfn。然后mem_map=mem_map-pfn.用 * sparse_encode_mem_map(mem_map,pnum)实现上述操作 * (b)解码:后续需要mem_map获取ms第一个pfn指向的struct page实例,只需通过 * vaddr=sparse_decode_mem_map(mem_map,pnum),vaddr就是struct page实例的对应的虚拟地址,实际 * 的pfn=vaddr-mem_map将mem_map经过位运算和编码操 作后将值赋给ms的section_mem_map成员 */ ms->section_mem_map |= sparse_encode_mem_map(mem_map, pnum) | SECTION_HAS_MEM_MAP; //将ms的每个pageblock位图表的虚拟地址赋值给pageblock_flags成员 ms->pageblock_flags = pageblock_bitmap; return 1; }
经过上述函数的处理,最终内核展现出sparese memory内存模型的示意图如图3所示:
若内核配置了CONFIG_SPARSEMEM_VMEMMAP,这表示内核通过vmemmap能够在pfn和page实例地址之间快速转换,它多用于具有较大vmalloc区域的64位系统。此时多了一层特殊的映射关系,模型示意图可参考图4:
4.zone_sizes_init
在学习该函数前需要需要先学习两个关键结构体struct pglist_data和strcut zone,以及两结构体间关系(前面已经做了详细介绍)。
该函数主要是对各个节点对应的struct pglist_data结构体成员进行初始化,同时也对其所属的所有zone区域对应的结构体stuct zone的成员进行初始化和对zone区域中所有有效页对应的struct page中的成员进行初始化。函数实现细节如图5所示。(图五流程分析针对的系统具有arm64架构,sparse内存模型,UMA和VMEMMAP这4个条件)
下面代码只分析了uma,情况下的实现细节对于numa以后有机会分析分析。下面看具体代码实现细节:
static void __init zone_sizes_init(unsigned long min, unsigned long max)
{
struct memblock_region *reg;
unsigned long zone_size[MAX_NR_ZONES], zhole_size[MAX_NR_ZONES];
unsigned long max_dma = min;
//数组内存区域初始化为0
memset(zone_size, 0, sizeof(zone_size));
/* 4GB maximum for 32-bit only capable devices */
#ifdef CONFIG_ZONE_DMA
max_dma = PFN_DOWN(arm64_dma_phys_limit);
/*计算DMA大小,此处的大小只是内存的跨度,并不是实际大小,因为内存中可能有空洞。从该区域起始位置起将4G的内存分
*配给DMA域(arm64)。当总的内存小于4g,则所有内存空间都用作DMA内存域,此时NORMAL域 0
*/
zone_size[ZONE_DMA] = max_dma - min;
#endif
//同上,其中NORMAL值可能为0
zone_size[ZONE_NORMAL] = max - max_dma;
/*下面主要是遍历memblock中的所有memory region,分别计算出DMA和NORMAL内存区域的空洞大小,存储在zhole_size数
*组中。空洞存在于region和region之间
*/
memcpy(zhole_size, zone_size, sizeof(zhole_size));
for_each_memblock(memory, reg) {
unsigned long start = memblock_region_memory_base_pfn(reg);
unsigned long end = memblock_region_memory_end_pfn(reg);
if (start >= max)
continue;
#ifdef CONFIG_ZONE_DMA
if (start < max_dma) {
unsigned long dma_end = min(end, max_dma);
zhole_size[ZONE_DMA] -= dma_end - start;
}
#endif
if (end > max_dma) {
unsigned long normal_end = min(end, max);
unsigned long normal_start = max(start, max_dma);
zhole_size[ZONE_NORMAL] -= normal_end - normal_start;
}
}
//初始化内存节点,unam系统只有一个内存节点--->contig_page_data
free_area_init_node(0, zone_size, min, zhole_size);
}
free_area_init_node
void __paginginit free_area_init_node(int nid, unsigned long *zones_size,
unsigned long node_start_pfn, unsigned long *zholes_size)
{
//获取内存节点
pg_data_t *pgdat = NODE_DATA(nid);
unsigned long start_pfn = 0;
unsigned long end_pfn = 0;
/* pg_data_t should be reset to zero when it's allocated */
WARN_ON(pgdat->nr_zones || pgdat->kswapd_classzone_idx);
//若为uma系统只有一个节点,nid为0
pgdat->node_id = nid;
pgdat->node_start_pfn = node_start_pfn;
pgdat->per_cpu_nodestats = NULL;
#ifdef CONFIG_HAVE_MEMBLOCK_NODE_MAP
get_pfn_range_for_nid(nid, &start_pfn, &end_pfn);
pr_info("Initmem setup node %d [mem %#018Lx-%#018Lx]\n", nid,
(u64)start_pfn << PAGE_SHIFT,
end_pfn ? ((u64)end_pfn << PAGE_SHIFT) - 1 : 0);
#else
start_pfn = node_start_pfn;
#endif
/*
*(1)给节点中各个内存的zone的zone_start_pfn成员,spanned_pages成员和present_pages成员赋值
*(2)给节点的node_spanned_pages和node_present_pages成员赋值
*/
calculate_node_totalpages(pgdat, start_pfn, end_pfn,
zones_size, zholes_size);
//针对FLAT内存模型
alloc_node_mem_map(pgdat);
#ifdef CONFIG_FLAT_NODE_MEM_MAP
printk(KERN_DEBUG "free_area_init_node: node %d, pgdat %08lx, node_mem_map %08lx\n",
nid, (unsigned long)pgdat,
(unsigned long)pgdat->node_mem_map);
#endif
reset_deferred_meminit(pgdat);
/*初始化pgdata相关成员,主要是初始化该节点中的每个zone对应的struct zone结构体成员和zone中每个页struct page
*结构的的相关成员
*/
free_area_init_core(pgdat);
}
free_area_init_core
该函数内部有很多宏控制的结构体成员初始化,本文此处进行了省略有兴趣的同学可以通过内核源码自己进行分析。
static void __paginginit free_area_init_core(struct pglist_data *pgdat)
{
enum zone_type j;
int nid = pgdat->node_id;
int ret;
/*初始化pg_dat_t结构体的相关成员;前面是初始化其内部的锁和队列*/
//给node_size_lock自旋锁加锁(CONFIG_MEMORY_HOTPLUG配置才有效)
pgdat_resize_init(pgdat);
#ifdef CONFIG_NUMA_BALANCING
spin_lock_init(&pgdat->numabalancing_migrate_lock);
pgdat->numabalancing_migrate_nr_pages = 0;
pgdat->numabalancing_migrate_next_window = jiffies;
#endif
#ifdef CONFIG_TRANSPARENT_HUGEPAGE
spin_lock_init(&pgdat->split_queue_lock);
INIT_LIST_HEAD(&pgdat->split_queue);
pgdat->split_queue_len = 0;
#endif
init_waitqueue_head(&pgdat->kswapd_wait);
init_waitqueue_head(&pgdat->pfmemalloc_wait);
#ifdef CONFIG_COMPACTION
init_waitqueue_head(&pgdat->kcompactd_wait);
#endif
pgdat_page_ext_init(pgdat);
spin_lock_init(&pgdat->lru_lock);
lruvec_init(node_lruvec(pgdat));
//初始化该节点中的每个zone结构体,并对zone管理的每个内存区域进行初始化
for (j = 0; j < MAX_NR_ZONES; j++) {
struct zone *zone = pgdat->node_zones + j;
unsigned long size, realsize, freesize, memmap_pages;
unsigned long zone_start_pfn = zone->zone_start_pfn;
size = zone->spanned_pages;
realsize = freesize = zone->present_pages;
/*
* 通过zone的spanned_pages和present_pages计算出管理该zone所需的struct page
* 结构体所占据的页的个数memmap_pages
*/
memmap_pages = calc_memmap_size(size, realsize);
//非高端内存域,freesize的大小要减去memmap_pages
if (!is_highmem_idx(j)) {
if (freesize >= memmap_pages) {
freesize -= memmap_pages;
if (memmap_pages)
printk(KERN_DEBUG
" %s zone: %lu pages used for memmap\n",
zone_names[j], memmap_pages);
} else
pr_warn(" %s zone: %lu pages exceeds freesize %lu\n",
zone_names[j], memmap_pages, freesize);
}
/*第一个内存域DMA,要预留一段内存给DMA使用。这是某些外设的要求如果系统没有需求可以不用预留*/
if (j == 0 && freesize > dma_reserve) {
freesize -= dma_reserve;
printk(KERN_DEBUG " %s zone: %lu pages reserved\n",
zone_names[0], dma_reserve);
}
/*对于非高端内存域,可用的page数都会计入nr_kernel_pages,即所有zone中可用的page数都会计入其中*/
if (!is_highmem_idx(j))
nr_kernel_pages += freesize;
/*对于高端内存域当nr_kernel_pages大小充足的情况下,会预留一段内存空间用来存储高端内存的page结构体,该段
*内存空间在NORMAL处(猜测),而高端内存本身并不会计入nr_kernel_pages.注意arm64架构不存在高端内存.
*/
else if (nr_kernel_pages > memmap_pages * 2)
nr_kernel_pages -= memmap_pages;
/*所有zone的freesize都会累加计入nr_all_pages.由上可知在没有高端内存域的时候其大小和nr_kernel_pages一致
*/
nr_all_pages += freesize;
/*
*对于非高端内存域zone的managed_pages表示可用的page个数,高端内存域则是其实际page个数
*/
zone->managed_pages = is_highmem_idx(j) ? realsize : freesize;
#ifdef CONFIG_NUMA
zone->node = nid;
#endif
zone->name = zone_names[j];
zone->zone_pgdat = pgdat;
/* &zone->lock:Primarily protects free_area */
spin_lock_init(&zone->lock);
zone_seqlock_init(zone);
zone_pcp_init(zone);
if (!size)
continue;
//初始化页块的order默认是MAX_ORDER - 1
set_pageblock_order();
/*(1)若是SPARSEMEM内存模型该函数不做处理因为sparese_init已经对内存块的位图数据做了相关处理
*(2)若不是稀疏内存模型则同过memblock模块分配内存空间给该zone的内存块的位图数据提供存储空间返回虚拟地
*址,将值赋给zone的pageblock_flags成员
*/
setup_usemap(pgdat, zone, zone_start_pfn, size);
//初始化zone的free_area,伙伴系统的核心
ret = init_currently_empty_zone(zone, zone_start_pfn, size);
BUG_ON(ret);
/*通过PFN页框号找到对应的struct page结构体,然后将该结构体进行初始化,并设置位MIGRATE_MOVABLE标志表示为
*可移动
*/
memmap_init(size, nid, j, zone_start_pfn);
}
}
上述代码步骤:
- 初始化struct pglist_data内部使用的锁和队列
- 遍历zone的各个区域
- 根据zone的spanned_pages和present pages计算出zone存储区struct page结构体需要的物理内存大小memmap_pages(单位页)
- 计算各个zone的freeszie
- 循环统计出全局变量nr_kernel_pages和nr_all_pages。图6表明这两个全局参数同节点页面的关系(为了该图的通用性以arm32系统为例,因为具有HIGEMEM)。
- 初始化zone的各类锁等变量
- 循环遍历zone区域,通过pfn以page大小为单位找出zone区域的每个struct page结构体,对结构体成员进行初始化,并设置MIGRATE_MOVABLE表明可以移动。
到此bootmem_init完成了物理内存的框架的的出示化工作:Node,Zone,page fram和其对应结构体struct page中的各个成员的初始化工作