1. 启动过程中的内存初始化
首先我们来看看start_kernel是如何初始化系统的, start_kerne定义在init/main.c?v=4.7, line 479
其代码很复杂, 我们只截取出其中与内存管理初始化相关的部分, 如下所示
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();
}
函数 | 功能 |
---|---|
setup_arch | 是一个特定于体系结构的设置函数, 其中一项任务是负责初始化自举分配器 |
mm_init_cpumask | 初始化CPU屏蔽字 |
setup_per_cpu_areas | 函数(查看定义)给每个CPU分配内存,并拷贝.data.percpu段的数据. 为系统中的每个CPU的per_cpu变量申请空间. 在SMP系统中, setup_per_cpu_areas初始化源代码中(使用per_cpu宏)定义的静态per-cpu变量, 这种变量对系统中每个CPU都有一个独立的副本. 此类变量保存在内核二进制影像的一个独立的段中, setup_per_cpu_areas的目的就是为系统中各个CPU分别创建一份这些数据的副本 在非SMP系统中这是一个空操作 |
build_all_zonelists | 建立并初始化结点和内存域的数据结构 |
mm_init | 建立了内核的内存分配器, 其中通过mem_init停用bootmem分配器并迁移到实际的内存管理器(比如伙伴系统) 然后调用kmem_cache_init函数初始化内核内部用于小块内存区的分配器 |
kmem_cache_init_late | 在kmem_cache_init之后, 完善分配器的缓存机制, 当前3个可用的内核内存分配器slab, slob, slub都会定义此函数 |
kmemleak_init | Kmemleak工作于内核态,Kmemleak 提供了一种可选的内核泄漏检测,其方法类似于跟踪内存收集器。当独立的对象没有被释放时,其报告记录在 /sys/kernel/debug/kmemleak中, Kmemcheck能够帮助定位大多数内存错误的上下文 |
setup_per_cpu_pageset | 初始化CPU高速缓存行, 为pagesets的第一个数组元素分配内存, 换句话说, 其实就是第一个系统处理器分配 由于在分页情况下,每次存储器访问都要存取多级页表,这就大大降低了访问速度。所以,为了提高速度,在CPU中设置一个最近存取页面的高速缓存硬件机制,当进行存储器访问时,先检查要访问的页面是否在高速缓存中. |
1.1 setup_arch函数初始化内存流程
前面我们的内核从start_kernel开始, 进入setup_arch(), 并完成了早期内存分配器的初始化和设置工作.
void __init setup_arch(char **cmdline_p)
{
/* 初始化memblock */
arm64_memblock_init( );
/* 分页机制初始化 */
paging_init();
bootmem_init();
}
流程 | 描述 |
---|---|
arm64_memblock_init | 初始化memblock内存分配器 |
paging_init | 初始化分页机制 |
bootmem_init | 初始化内存管理 |
该函数主要执行了如下操作
- 使用arm64_memblock_init来完成memblock机制的初始化工作, 至此memblock分配器接受系统中系统中内存的分配工作
- 调用paging_init来完成系统分页机制的初始化工作, 建立页表, 从而内核可以完成虚拟内存的映射和转换工作
- 最后调用bootmem_init来完成实现buddy内存管理所需要的工作
1.2 (第一阶段)启动过程中的内存分配器
在初始化过程中, 还必须建立内存管理的数据结构, 以及很多事务. 因为内核在内存管理完全初始化之前就需要使用内存. 在系统启动过程期间, 使用了额外的简化悉尼股市的内存管理模块, 然后在初始化完成后, 将旧的模块丢弃掉.
这个阶段的内存分配其实很简单, 因此我们往往称之为内存分配器(而不是内存管理器), 早期的内核中内存分配器使用的bootmem引导分配器, 它基于一个内存位图bitmap, 使用最优适配算法来查找内存, 但是这个分配器有很大的缺陷, 最严重的就是内存碎片的问题, 因此在后来的内核中将其舍弃《而使用了新的memblock机制. memblock机制的初始化在arm64上是通过arm64_memblock_init函数来实现的
start_kernel()
|---->page_address_init()
| 考虑支持高端内存
| 业务:初始化page_address_pool链表;
| 将page_address_maps数组元素按索引降序插入
| page_address_pool链表;
| 初始化page_address_htable数组.
|
|---->setup_arch(&command_line);
| 初始化特定体系结构的内容
|
|---->arm64_memblock_init( );
| 初始化引导阶段的内存分配器memblock
|
|---->paging_init();
| 分页机制初始化
|
|---->bootmem_init(); [当前位置]
| 始化内存数据结构包括内存节点, 内存域和页帧page
|
|---->arm64_numa_init();
| 支持numa架构
|
|---->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-