参考文档:
https://github.com/gatieme/LDD-LinuxDeviceDrivers/tree/master/study/kernel/02-memory/03-initialize/04-bootmem_init
感谢作者的无私分享。
我们住关注 start_kernel 中关于内存管理的部分总览:
asmlinkage __visible void __init start_kernel(void)
{
.....
setup_arch(&command_line);
mm_init_cpumask(&init_mm);
.....
setup_per_cpu_areas();
.....
build_all_zonelists(NULL, NULL);
page_alloc_init();
.....
/*
* These use large bootmem allocations and must precede
* mem_init();
* kmem_cache_init();
*/
mm_init();
.....
kmem_cache_init_late();
.....
kmemleak_init();
setup_per_cpu_pageset();
.....
rest_init();
}
总体的流程如下:
start_kernel()
|---->page_address_init()
| 考虑支持高端内存
| 业务:初始化page_address_pool链表;
| 将page_address_maps数组元素按索引降序插入
| page_address_pool链表;
| 初始化page_address_htable数组.
|
|---->setup_arch(&command_line);
| 初始化特定体系结构的内容
|
|---->arm_memblock_init();
| 初始化引导阶段的内存分配器memblock
|---->paging_init();
| 分页机制初始化
----------|--------------------------------------------------------------------------
| |---->bootmem_init(); <<<<---- [当前位置] |
| | 始化内存数据结构包括内存节点, 内存域和页帧page <<<<---- [当前位置] |
| ------- <<<<---- [当前位置] |
| | <<<<---- [当前位置] |
| |---->zone_sizes_init(min, max); <<<<---- [当前位置] |
| 来初始化节点和管理区的一些数据项 <<<<---- [当前位置] |
| | <<<<---- [当前位置] |
| |---->free_area_init_node <<<<---- [当前位置] |
| | 初始化内存节点 <<<<---- [当前位置] |
| | <<<<---- [当前位置] |
| |---->free_area_init_core <<<<---- [当前位置] |
| | 初始化zone <<<<---- [当前位置] |
| | <<<<---- [当前位置] |
| |---->memmap_init <<<<---- [当前位置] |
| | 初始化page页面 <<<<---- [当前位置] |
| | <<<<---- [当前位置] |
| |---->memblock_dump_all(); <<<<---- [当前位置] |
| | 初始化完成, 显示memblock的保留的所有内存信息 |
|-------------------------------------------------------------------------------------
|---->build_all_zonelist()
| 为系统中的zone建立后备zone的列表.
| 所有zone的后备列表都在
| pglist_data->node_zonelists[0]中;
|
| 期间也对per-CPU变量boot_pageset做了初始化.
|
完成了开始的内存探测,添加到 memblock,paging_init 完成页表的初始化以及页表的映射后,基本的已经建立起来,接下来根据现目前的情况来初始化相关的结构,zone 结构,以便后续使用。
在初始化内存的结点和内存区域之前, 内核先通过 pagging_init 初始化了内核的分页机制, 这样我们的虚拟运行空间就初步建立,并可以完成物理地址到虚拟地址空间的映射工作。
paging_init 负责建立只能用于内核的页表, 用户空间是无法访问的。
在分页机制完成后, 内核通过 bootmem_init() 开始进行内存基本数据结构(内存结点pg_data_t, 内存域zone和页帧)的初始化工作, 就是在这个函数中, 内核开始从体系结构相关的部分逐渐展开到体系结构无关的部分, 在zone_sizes_init->free_area_init_node中开始, 内核开始进行内存基本数据结构的初始化, 也不再依赖于特定体系结构无关的层次。
具体的调用关系为:
bootmem_init()
始化内存数据结构包括内存节点, 内存域和页帧page
|
|---->find_limits(&min, &max_low, &max_high);
| 获取物理页帧号
|
|---->zone_sizes_init(min, max);
来初始化节点和管理区的一些数据项
|
|---->free_area_init_node
| 初始化内存节点
|
|---->free_area_init_core
| 初始化zone
|
|---->memmap_init
| 初始化page页面
|
|---->memblock_dump_all();
| 初始化完成, 显示memblock的保留的所有内存信息
首先通过 find_limits 函数,获取三个值:
static void __init find_limits(unsigned long *min, unsigned long *max_low,
unsigned long *max_high)
{
*max_low = PFN_DOWN(memblock_get_current_limit());
*min = PFN_UP(memblock_start_of_DRAM());
*max_high = PFN_DOWN(memblock_end_of_DRAM());
}
min_low_pfn —— 内存块的起始帧号
max_low_pfn —— normal 结束帧号
max_pfn —— 内存块的结束帧号
在初始化内存结点和内存域之前, 内核首先通过setup_arch()-->bootmem_init()-->zone_sizes_init()来初始化节点和管理区的一些数据项, 其中关键的是初始化了系统中各个内存域的页帧边界,保存在max_zone_pfn数组.
最终调用 free_area_init_node 函数来进行 Zone 的初始化。
static void __init zone_sizes_init(unsigned long min, unsigned long max_low,
unsigned long max_high)
{
unsigned long zone_size[MAX_NR_ZONES], zhole_size[MAX_NR_ZONES];
struct memblock_region *reg;
/*
* initialise the zones.
*/
memset(zone_size, 0, sizeof(zone_size));
/*
* The memory size has already been determined. If we need
* to do anything fancy with the allocation of this memory
* to the zones, now is the time to do it.
*/
zone_size[0] = max_low - min;
#ifdef CONFIG_HIGHMEM
zone_size[ZONE_HIGHMEM] = max_high - max_low;
#endif
/*
* Calculate the size of the holes.
* holes = node_size - sum(bank_sizes)
*/
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_low) {
unsigned long low_end = min(end, max_low);
zhole_size[0] -= low_end - start;
}
#ifdef CONFIG_HIGHMEM
if (end > max_low) {
unsigned long high_start = max(start, max_low);
zhole_size[ZONE_HIGHMEM] -= end - high_start;
}
#endif
}
#ifdef CONFIG_ZONE_DMA
/*
* Adjust the sizes according to any special requirements for
* this machine type.
*/
if (arm_dma_zone_size)
arm_adjust_dma_zone(zone_size, zhole_size,
arm_dma_zone_size >> PAGE_SHIFT);
#endif
free_area_init_node(0, zone_size, min, zhole_size);
}
函数中先使用 NODE_DATA 宏,获取了 pg_data_t 总体的指针,并进行一些数据域的赋值,最后调用 free_area_init_core 来初始化
void __init 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);
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
calculate_node_totalpages(pgdat, start_pfn, end_pfn,
zones_size, zholes_size);
alloc_node_mem_map(pgdat);
pgdat_set_deferred_range(pgdat);
free_area_init_core(pgdat);
}
free_area_init_core 来完成内存域 zone 的初始化
free_area_init_core执行,它会依次遍历结点的所有内存域,在初始化内存管理区zone的过程中, 通过 memmap_init 函数对每个内存管理区zone的page内存进行了初始化.
static void __init free_area_init_core(struct pglist_data *pgdat)
{
enum zone_type j;
int nid = pgdat->node_id;
pgdat_init_internals(pgdat);
pgdat->per_cpu_nodestats = &boot_nodestats;
for (j = 0; j < MAX_NR_ZONES; j++) {
.....
memmap_init(size, nid, j, zone_start_pfn);
.....
}
}
至此,节点和管理区的关键数据已完成初始化,内核在后面为内存管理做得一个准备工作就是将所有节点的管理区都链入到zonelist中,便于后面内存分配工作的进行。内核在start_kernel()-->build_all_zonelist() 中完成zonelist的初始化
注意:free_area_init函数在系统唯一的struct node对象contig_page_data中node_mem_map成员赋值给全局的mem_map变量
mem_map是一个struct page的数组,管理着系统中所有的物理内存页面。