说明
- memblock是Linux内核启动早期用于管理物理内存的机制,在伙伴系统(Buddy System)接管内存管理之前为系统提供物理内存分配、释放等功能。
- 相对于伙伴系统,memblock功能和实现较为简单。
- 本文基于:linux_5.10 arm64平台。
历史
- 启动早期的内存管理器实现有bootmem和memblock,bootmem是早期内核采用,4.x以后内核内核采用memblock,配置了NO_BOOTMEM宏。
- memblock取代了bootmem算法。
实现原理
获取物理内存布局
- 不同于伙伴系统以内存页为操作对象,memblock以物理内存段为操作对象,系统从dtb或者uboot传递来的mem信息中解析出总的物理内存信息(核心信息是地址范围),此时的物理内存是一段段的地址空间,再初始化memblock。
fdt方式
* 单段物理内存
memory {
device_type = "memory";
reg = <0x0 0x80000000 0x0 0x10000000>;
};
* 多段物理内存
memory@0 {
device_type = "memory";
reg = <0x00000000 0x00000000 0x00000000 0x05e00000>,
<0x00000000 0x05f00000 0x00000000 0x00001000>,
<0x00000000 0x05f02000 0x00000000 0x00efd000>,
<0x00000000 0x06e00000 0x00000000 0x0060f000>,
<0x00000000 0x07410000 0x00000000 0x1aaf0000>,
<0x00000000 0x22000000 0x00000000 0x1c000000>;
};
uboot bootargs方式
- uboot启动linux时,可以通过linux的启动参数bootargs,传递物理内存信息(基址和size),初始化memblock。
- 格式如下:
mem=size@start
* 函数调用栈
early_mem //file: arch/arm64/mm/init.c,解析出物理内存信息,保存在全局变量中
arm64_memblock_init //file: arch/arm64/mm/init.c
-> memblock_add
* 核心代码
void __init arm64_memblock_init(void)
{
...
if (memory_limit != PHYS_ADDR_MAX) { //全局变量非默认值
memblock_mem_limit_remove_map(memory_limit);
memblock_add(__pa_symbol(_text), (u64)(_end - _text));
}
...
}
可用段查找原理
- memblock内存分配时可用段查找采用first match算法,即占用首先找到的可用内存段。
- 内存分配查找的方向可以是从高到低,也可以是从低到高,通过总context中的成员变量bottom_up决定。
两个阶段
- memblock init之前;主要是静态分配,根据dts配置中预留内存定义(reserved memory),内核本身(code等),dtb等,在物理内存上分配出所需的预留内存。
- memblock init之后,伙伴系统初始化完之前;主要是Linux内核机制产生的动态内存分配。
- 两个阶段以以memblock configuration为分隔。
分配结果
- memblock分配结果都是预留内存,分配结束后固定占用,无法释放和复用。
代码逻辑
include/linux/memblock.h
mm/memblock.c
数据结构和实例
- memblock从大到小定义了三个数据结构,如下:
- 总context定义
struct memblock {
bool bottom_up; //内存分配的方向:从高到低(FALSE)、从低到高(TRUE)
phys_addr_t current_limit; //最大内存地址
struct memblock_type memory; //可管理的内存段
struct memblock_type reserved; //预留内存
};
- 内存类型定义
struct memblock_type {
unsigned long cnt; //内存区域个数(占用数组个数)
unsigned long max; //最大区域个数(数组总个数)
phys_addr_t total_size; //该内存类型总大小
struct memblock_region *regions; //包含的内存区域数组
char *name;
};
- 内存区域(地址段)定义
struct memblock_region {
phys_addr_t base; //基址
phys_addr_t size; //空间大小
enum memblock_flags flags; //flag
#ifdef CONFIG_NUMA
int nid; //物理内存 node id,NUMA可以存在多个物理内存节点(node)
#endif
};
enum memblock_flags {
MEMBLOCK_NONE = 0x0, /* No special request */ //正常
MEMBLOCK_HOTPLUG = 0x1, /* hotpluggable region */ //可插拔区域
MEMBLOCK_MIRROR = 0x2, /* mirrored region */
MEMBLOCK_NOMAP = 0x4, /* don't add to kernel direct mapping */ //no map区域
};
- 总context实例,以全局静态变量(保存在BSS段中)形式定义,区域都是预先定义的全局静态数组,数组个数默认128(INIT_MEMBLOCK_REGIONS)。
static struct memblock_region memblock_memory_init_regions[INIT_MEMBLOCK_REGIONS] __initdata_memblock;
static struct memblock_region memblock_reserved_init_regions[INIT_MEMBLOCK_RESERVED_REGIONS] __initdata_memblock;
#ifdef CONFIG_HAVE_MEMBLOCK_PHYS_MAP
static struct memblock_region memblock_physmem_init_regions[INIT_PHYSMEM_REGIONS];
#endif
struct memblock memblock __initdata_memblock = {
.memory.regions = memblock_memory_init_regions,
.memory.cnt = 1, /* empty dummy entry */
.memory.max = INIT_MEMBLOCK_REGIONS,
.memory.name = "memory",
.reserved.regions = memblock_reserved_init_regions,
.reserved.cnt = 1, /* empty dummy entry */
.reserved.max = INIT_MEMBLOCK_RESERVED_REGIONS,
.reserved.name = "reserved",
.bottom_up = false,
.current_limit = MEMBLOCK_ALLOC_ANYWHERE,
};
API
- memblock_add
- 将内存区域加入memblock可管理的内存区域,即memory的region队列。
- memblock_free
- 将一个物理内存段从预留内存中移除,该内存段重新标记为可用。
int memblock_free(phys_addr_t base, phys_addr_t size)
{
memblock_dbg(" memblock_free: [%#016llx-%#016llx] %pF\n",
(unsigned long long)base,
(unsigned long long)base + size - 1,
(void *)_RET_IP_);
kmemleak_free_part_phys(base, size);
return memblock_remove_range(&memblock.reserved, base, size);
}
- …
调试
- 获取memblock的详细分配log,可以通过在uboot bootargs中加入“memblock=debug”,内核启动后,通过dmesg或者/proc/kmsg查看调试信息。
- linux kernel启动后可以通过debug fs查看内存地址范围和reserved区域,如下:
/sys/kernel/debug/memblock/memory #交由系统管理的内存
/sys/kernel/debug/memblock/reserved #预留的内存
* 需要开启配置
CONFIG_DEBUG_FS
CONFIG_ARCH_KEEP_MEMBLOCK //是否保留memblock分配信息
- 该功能不是很有必要并且会占用一定硬件资源,方法1足以满足调试需求,新版本内核CONFIG_ARCH_KEEP_MEMBLOCK配置默认是关闭的。
交接
- buddy分配器初始化ok后,memblock分配器将内存管理工作交接给buddy(伙伴)分配器。
标志
- memblock和伙伴系统的交接标志:释放init进程内存(free_initmem函数处理),之后系统可用内存(/proc/meminfo中的MemTotal)就固定了。
分配实例分析