Linux内存管理9(基于6.1内核)---内存启动-memblock分配器
一、memblock内存管理概述
引导内核的过程中, 需要使用内存, 而这个时候内核的内存管理并没有被创建, 因此也就需要一种精简的内存管理系统先接受这个工作, 而在初始化完成后, 再将旧的接口废弃, 转而使用强大的buddy系统来进行内存管理。
在 Linux 6.1内核中,memblock
是一个低级别的内存管理机制,它主要用于内核启动阶段的内存管理,特别是在内核的早期初始化过程中。它是为了处理内核启动时的内存分配和管理而设计的,提供了一种有效的方式来跟踪和分配物理内存,尤其是在内核的初始化阶段。memblock
是在内核正式启用常规的内存管理(如页框分配器 buddy allocator
)之前使用的。
二、memblock的数据结构
2.1 struct memblock结构
include/linux/memblock.h
/**
* struct memblock - memblock allocator metadata
* @bottom_up: is bottom up direction?
* @current_limit: physical address of the current allocation limit
* @memory: usable memory regions
* @reserved: reserved memory regions
*/
struct memblock {
bool bottom_up; /* is bottom up direction? */
phys_addr_t current_limit;
struct memblock_type memory;
struct memblock_type reserved;
};
字段 | 描述 |
---|---|
bottom_up | 表示分配器分配内存的方式 true:从低地址(内核映像的尾部)向高地址分配 false:也就是top-down,从高地址向地址分配内存. |
current_limit | 指出了内存块的大小限制, 用于限制通过memblock_alloc的内存申请 |
memory | 是可用内存的集合 |
reserved | 已分配内存的集合 |
接下来的三个域描述了内存块的类型
-
预留型
-
内存型
-
物理内存型(需要配置宏CONFIG_HAVE_MEMBLOCK_PHYS_MAP)
2.2 struct memblock_type
include/linux/memblock.h
/**
* struct memblock_type - collection of memory regions of certain type
* @cnt: number of regions
* @max: size of the allocated array
* @total_size: size of all regions
* @regions: array of regions
* @name: the memory type symbolic name
*/
struct memblock_type {
unsigned long cnt;
unsigned long max;
phys_addr_t total_size;
struct memblock_region *regions;
char *name;
};
该结构体存储的是内存类型信息:
字段 | 描述 |
---|---|
cnt | 当前集合(memory或者reserved)中记录的内存区域个数 |
max | 当前集合(memory或者reserved)中可记录的内存区域的最大个数 |
total_size | 集合记录区域信息大小 |
regions | 内存区域结构指针 |
它包含的域分别描述了当前内存块含有的内存区域数量,所有内存区域的总共大小,已经分配的内存区域大小和一个指向memblock_region结构体的数组指针。
2.3 内存区域memblock_region
include/linux/memblock.h
/**
* struct memblock_region - represents a memory region
* @base: base address of the region
* @size: size of the region
* @flags: memory region attributes
* @nid: NUMA node id
*/
struct memblock_region {
phys_addr_t base;
phys_addr_t size;
enum memblock_flags flags;
#ifdef CONFIG_NUMA
int nid;
#endif
};
字段 | 描述 |
---|---|
base | 内存区域起始地址 |
size | 内存区域大小 |
flags | 标记 |
nid | node号 |
2.4 内存区域标识
memblock_region的flags字段存储了当期那内存域的标识信息, 标识用enum变量来定义include/linux/memblock.h
/* Definition of memblock flags. */
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 */
MEMBLOCK_DRIVER_MANAGED = 0x8, /* always detected via a driver */
};
2.5 结构总体布局
图示法可以用来展示以上结构体之间的关系:
+---------------------------+ +---------------------------+
| memblock | | |
| _______________________ | | |
| | memory | | | Array of the |
| | memblock_type |---|-->| membock_region |
| |_______________________| | | |
| | +---------------------------+
| __________________________ | +---------------------------+
| | reserved | | | |
| | memblock_type |---|-->| Array of the |
| |_______________________| | | memblock_region |
| | | |
+---------------------------+ +---------------------------+
Memblock主要包含三个结构体:memblock, memblock_type和memblock_region。memblock的初始化过程。
2.6 初始化memblock静态变量
在编译时,会分配好memblock结构所需要的内存空间。结构体memblock的初始化变量名和结构体名相同memblock。mm/memblock.c
static struct memblock_region memblock_memory_init_regions[INIT_MEMBLOCK_MEMORY_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_MEMORY_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,
};
__initdata_memblock宏指定存储位置
我们可以注意到初始化使用了__initdata_memblock宏,include/linux/memblock.h
#ifndef CONFIG_ARCH_KEEP_MEMBLOCK
#define __init_memblock __meminit
#define __initdata_memblock __meminitdata
void memblock_discard(void);
#else
#define __init_memblock
#define __initdata_memblock
static inline void memblock_discard(void) {}
#endif
如果启用CONFIG_ARCH_DISCARD_MEMBLOCK
宏配置选项,memblock代码会被放到.init代码段, 在内核启动完成后 memblock代码会从.init代码段释放。
memblock结构体memblock_type类型数据 memory, reserved初始化
它们的memblock_typecnt域(当前集合中区域个数)被初始化为1。
memblock_typemax域(当前集合中最大区域个数)被初始化为INIT_MEMBLOCK_REGIONS
和INIT_PHYSMEM_REGIONS。
其中INIT_MEMBLOCK_REGIONS
为128。 mm/memblock.c
#define INIT_MEMBLOCK_REGIONS 128
#define INIT_PHYSMEM_REGIONS 4
而memblock_type.regions域都是通过memblock_region数组初始化的, 所有的数组定义都带有__initdata_memblock宏
memblock结构体中最后两个域bottom_up内存分配模式被禁用(bottom_up = false, 因此内存分配方式为top-down.), 当前 Memblock的大小限制是MEMBLOCK_ALLOC_ANYWHERE
为~(phys_addr_t)0即为0xffffffff. include/linux/memblock.h
/* Flags for memblock allocation APIs */
#define MEMBLOCK_ALLOC_ANYWHERE (~(phys_addr_t)0)
#define MEMBLOCK_ALLOC_ACCESSIBLE 0
#define MEMBLOCK_ALLOC_NOLEAKTRACE 1
三、Memblock-API函数接口
3.1 Memblock-API函数接口
既然内核静态创建并初始化了__initdata_memblock这个变量, 那么memblock又是怎么运作的呢?
/
// 基本接口
/
// 向memory区中添加内存区域.
memblock_add(phys_addr_t base, phys_addr_t size)
// 向memory区中删除区域.
memblock_remove(phys_addr_t base, phys_addr_t size)
// 申请内存
phys_addr_t __init memblock_phys_alloc_range(phys_addr_t size,
phys_addr_t align,
phys_addr_t start,
phys_addr_t end)
// 释放内存
memblock_free(phys_addr_t base, phys_addr_t size)
/
// 查找 & 遍历
/
// 在给定的范围内找到未使用的内存
static phys_addr_t __init_memblock memblock_find_in_range(phys_addr_t start,
phys_addr_t end, phys_addr_t size,
phys_addr_t align)
// 反复迭代 memblock
for_each_mem_range(i, type_a, type_b, nid, flags, p_start, p_end, p_nid)
/
// 获取信息
/
// 获取内存区域信息
phys_addr_t get_allocated_memblock_memory_regions_info(phys_addr_t *addr);
// 获取预留内存区域信息
phys_addr_t get_allocated_memblock_reserved_regions_info(phys_addr_t *addr);
/
// 获取信息
/
#define memblock_dbg(fmt, ...) \
do { \
if (memblock_debug) \
pr_info(fmt, ##__VA_ARGS__); \
} while (0)
3.2 memblock_add将内存区域加入到memblock中
memblock_add函数负责向memory区中添加内存区域, 有两个参数:物理基址和内存区域大小,并且把该内存区域添加到memblock。mm/memblock.c
/**
* memblock_add - add new memblock region
* @base: base address of the new region
* @size: size of the new region
*
* Add new memblock region [@base, @base + @size) to the "memory"
* type. See memblock_add_range() description for mode details
*
* Return:
* 0 on success, -errno on failure.
*/
int __init_memblock memblock_add(phys_addr_t base, phys_addr_t size)
{
phys_addr_t end = base + size - 1;
memblock_dbg("%s: [%pa-%pa] %pS\n", __func__,
&base, &end, (void *)_RET_IP_);
return memblock_add_range(&memblock.memory, base, size, MAX_NUMNODES, 0);
}
memblock_add传递的参数依次是: 内存块类型(memory), 物理基址, 内存区域大小, 最大节点数(0如果CONFIG_NODES_SHIFT没有在配置文件中设置,不然就是CONFIG_NODES_SHIFT)和标志。
memblock_add_range函数添加新的内存区域到内存块中, mm/memblock.c
-
首先,该函数检查给定的内存区域大小, 如果是0就返回。
-
在这之后, memblock_add_range用给定的memblock_type检查memblock结构体中是否存在内存区域。
-
如果没有,我们就用给定的值填充新的memory_region然后返回。
-
如果memblock_type不为空,我们就把新的内存区域添加到memblock_type类型的memblock中。
/**
* memblock_add_range - add new memblock region
* @type: memblock type to add new region into
* @base: base address of the new region
* @size: size of the new region
* @nid: nid of the new region
* @flags: flags of the new region
*
* Add new memblock region [@base,@base+@size) into @type. The new region
* is allowed to overlap with existing ones - overlaps don't affect already
* existing regions. @type is guaranteed to be minimal (all neighbouring
* compatible regions are merged) after the addition.
*
* RETURNS:
* 0 on success, -errno on failure.
*/
int __init_memblock memblock_add_range(struct memblock_type *type,
phys_addr_t base, phys_addr_t size,
int nid, unsigned long flags)
{
bool insert = false;
phys_addr_t obase = base;
/* 获取内存区域的结束位置,
* memblock_cap_size函数会设置size大小确保base + size不会溢出 */
phys_addr_t end = base + memblock_cap_size(base, &size);
int idx, nr_new;
struct memblock_region *rgn;
if (!size)
return 0;
/* special case for empty array */
if (type->regions[0].size == 0) {
WARN_ON(type->cnt != 1 || type->total_size);
type->regions[0].base = base;
type->regions[0].size = size;
type->regions[0].flags = flags;
memblock_set_region_node(&type->regions[0], nid);
type->total_size = size;
return 0;
}
repeat:
/*
* The following is executed twice. Once with %false @insert and
* then with %true. The first counts the number of regions needed
* to accomodate the new area. The second actually inserts them.
*/
base = obase;
nr_new = 0;
for_each_memblock_type(type, rgn) {
phys_addr_t rbase = rgn->base;
phys_addr_t rend = rbase + rgn->size;
if (rbase >= end)
break;
if (rend <= base)
continue;
/*
* @rgn overlaps. If it separates the lower part of new
* area, insert that portion.
*/
if (rbase > base) {
#ifdef CONFIG_HAVE_MEMBLOCK_NODE_MAP
WARN_ON(nid != memblock_get_region_node(rgn));
#endif
WARN_ON(flags != rgn->flags);
nr_new++;
if (insert)
memblock_insert_region(type, idx++, base,
rbase - base, nid,
flags);
}
/* area below @rend is dealt with, forget about it */
base = min(rend, end);
}
/* insert the remaining portion */
if (base < end) {
nr_new++;
if (insert)
memblock_insert_region(type, idx, base, end - base,
nid, flags);
}
/*
* If this was the first round, resize array and repeat for actual
* insertions; otherwise, merge and return.
*/
if (!insert) {
while (type->cnt + nr_new > type->max)
if (memblock_double_array(type, obase, size) < 0)
return -ENOMEM;
insert = true;
goto repeat;
} else {
memblock_merge_regions(type);
return 0;
}
}
memblock_add_range函数流程解析
获得内存区域的结束位置:
phys_addr_t end = base + memblock_cap_size(base, &size);
memblock_cap_size函数会设置size大小确保base + size不会溢出。mm/memblock.c
/* adjust *@size so that (@base + *@size) doesn't overflow, return new size */
static inline phys_addr_t memblock_cap_size(phys_addr_t base, phys_addr_t *size)
{
return *size = min(*size, (phys_addr_t)ULLONG_MAX - base);
}
memblock_cap_size返回size和ULLONG_MAX - base中的最小值。
得到了新的内存区域的结束地址, 然后:
-
查内存区域是否重叠。
-
将新的添加到memblock, 并且看是否能和已经添加到memblock中的内存区域进行合并。
首先遍历所有已经存储的内存区域并检查有没有和新的内存区域重叠:
for_each_memblock_type(type, rgn) {
phys_addr_t rbase = rgn->base;
phys_addr_t rend = rbase + rgn->size;
if (rbase >= end)
break;
if (rend <= base)
continue;
/* ...... */
/* area below @rend is dealt with, forget about it */
base = min(rend, end);
}
如果新内存区域没有和已经存储在memblock的内存区域重叠, 把该新内存区域插入到memblock中. 如果有重叠通通过一个小巧的来完成冲突处理。
base = min(rend, end);
重叠检查完毕后, 新的内存区域已经是一块干净的不包含重叠区域的内存, 把新的内存区域插入到memblock中包含两步:
-
把新的内存区域中非重叠的部分作为独立的区域加入到memblock。
-
合并所有相邻的内存区域。
这个过程分为两次循环来完成, 由一个标识变量insert和report代码跳转标签控制:
+--------------------------+
| 第一次循环 |
+--------------------------+
|
v
+--------------------------+ +----------------------------+
| 检查新内存区域是否可以放入 |--否->| 设置insert = true |
| 内存块中? | +----------------------------+
+--------------------------+ |
| v
v +--------------------------+
+--------------------------+ | 执行memblock_double_array|
| 执行!insert条件语句 |<-----| 执行其他相关操作 |
+--------------------------+ +--------------------------+
|
v
+-------------------+
| 跳转到report |
+-------------------+
|
v
+--------------------------+
| 第二次循环 |
+--------------------------+
|
v
+--------------------------+
| insert = true? |
+--------------------------+
|
是 | 否
v
+----------------------------+
| 执行insert == true的代码块 |
| 调用memblock_insert_region |
+----------------------------+
|
v
+----------------------------+
| 执行memblock_merge_regions |
| 合并内存区域 |
+----------------------------+
第一次循环需要检查新内存区域是否可以放入内存块中并调用memblock_double_array:
/*
* If this was the first round, resize array and repeat for actual
* insertions; otherwise, merge and return.
*/
if (!insert) { /* 第一次执行的的时候insert == false */
while (type->cnt + nr_new > type->max)
if (memblock_double_array(type, obase, size) < 0)
return -ENOMEM;
insert = true;
goto repeat;
} else {
/* ...... */
}
memblock_double_array函数加倍给定的内存区域大小,然后把insert设为true再转到repeat标签.
第二次循环,从repeat标签开始经过同样的循环然后用memblock_insert_region函数把当前内存区域插入到内存块:
/* insert the remaining portion */
if (base < end) {
nr_new++;
if (insert)
memblock_insert_region(type, idx, base, end - base,
nid, flags);
}
第一次循环中把insert设为true, 现在memblock_insert_region函数将会被调用。memblock_insert_region几乎和把新内存区域插入到空的memblock_type代码块有同样的实现, mm/memblock.c该函数获得最后一个内存区域:
struct memblock_region *rgn = &type->regions[idx];
然后调用memmove函数移动该内存区域:
memmove(rgn + 1, rgn, (type->cnt - idx) * sizeof(*rgn));
紧接着填充新内存区域memblock_region的base域,size域等等, 然后增大memblock_type的大小。最后memblock_add_range函数调用memblock_merge_regions合并所有相邻且兼容的内存区域mm/memblock.c
/*
* If this was the first round, resize array and repeat for actual
* insertions; otherwise, merge and return.
*/
if (!insert) {
/* ...... */
} else {
memblock_merge_regions(type);
return 0;
}
3.3 memblock_remove删除内存区域
memblock_remove用来完成删除内存区域的工作, mm/memblock.c
int __init_memblock memblock_remove(phys_addr_t base, phys_addr_t size)
{
phys_addr_t end = base + size - 1;
memblock_dbg("%s: [%pa-%pa] %pS\n", __func__,
&base, &end, (void *)_RET_IP_);
return memblock_remove_range(&memblock.memory, base, size);
}
3.4 memblock_alloc申请内存
memblock_phys_alloc_range函数代码
/**
* memblock_phys_alloc_range - allocate a memory block inside specified range
* @size: size of memory block to be allocated in bytes
* @align: alignment of the region and block's size
* @start: the lower bound of the memory region to allocate (physical address)
* @end: the upper bound of the memory region to allocate (physical address)
*
* Allocate @size bytes in the between @start and @end.
*
* Return: physical address of the allocated memory block on success,
* %0 on failure.
*/
phys_addr_t __init memblock_phys_alloc_range(phys_addr_t size,
phys_addr_t align,
phys_addr_t start,
phys_addr_t end)
{
memblock_dbg("%s: %llu bytes align=0x%llx from=%pa max_addr=%pa %pS\n",
__func__, (u64)size, (u64)align, &start, &end,
(void *)_RET_IP_);
return memblock_alloc_range_nid(size, align, start, end, NUMA_NO_NODE,
false);
}
memblock_phys_alloc_range()很粗暴的从能用的内存里分配, 而有些情况下需要从特定的内存范围内分配内存. 解决方法就是通过memblock_alloc_range_nid函数或者实现类似机制的函数
最终memblock_phys_alloc_range的也是通过memblock_alloc_range_nid函数来完成内存分配的
memblock_alloc_range_nid函数的实现, 该函数定义mm/memblock.c
phys_addr_t __init memblock_alloc_range_nid(phys_addr_t size,
phys_addr_t align, phys_addr_t start,
phys_addr_t end, int nid,
bool exact_nid)
{
enum memblock_flags flags = choose_memblock_flags();
phys_addr_t found;
if (WARN_ONCE(nid == MAX_NUMNODES, "Usage of MAX_NUMNODES is deprecated. Use NUMA_NO_NODE instead\n"))
nid = NUMA_NO_NODE;
if (!align) {
/* Can't use WARNs this early in boot on powerpc */
dump_stack();
align = SMP_CACHE_BYTES;
}
again:
found = memblock_find_in_range_node(size, align, start, end, nid,
flags);
if (found && !memblock_reserve(found, size))
goto done;
if (nid != NUMA_NO_NODE && !exact_nid) {
found = memblock_find_in_range_node(size, align, start,
end, NUMA_NO_NODE,
flags);
if (found && !memblock_reserve(found, size))
goto done;
}
if (flags & MEMBLOCK_MIRROR) {
flags &= ~MEMBLOCK_MIRROR;
pr_warn_ratelimited("Could not allocate %pap bytes of mirrored memory\n",
&size);
goto again;
}
return 0;
done:
/*
* Skip kmemleak for those places like kasan_init() and
* early_pgtable_alloc() due to high volume.
*/
if (end != MEMBLOCK_ALLOC_NOLEAKTRACE)
/*
* Memblock allocated blocks are never reported as
* leaks. This is because many of these blocks are
* only referred via the physical address which is
* not looked up by kmemleak.
*/
kmemleak_alloc_phys(found, size, 0);
/*
* Some Virtual Machine platforms, such as Intel TDX or AMD SEV-SNP,
* require memory to be accepted before it can be used by the
* guest.
*
* Accept the memory of the allocated buffer.
*/
accept_memory(found, found + size);
return found;
}
memblock_alloc_range_nid函数的主要工作如下:
-
首先使用memblock_find_in_range_node指定内存区域和大小查找内存区域。
-
memblock_reserve后将其标为已经分配。
该函数定义mm/memblock.c
static phys_addr_t __init_memblock memblock_find_in_range_node(phys_addr_t size,
phys_addr_t align, phys_addr_t start,
phys_addr_t end, int nid,
enum memblock_flags flags)
{
/* pump up @end */
if (end == MEMBLOCK_ALLOC_ACCESSIBLE ||
end == MEMBLOCK_ALLOC_NOLEAKTRACE)
end = memblock.current_limit;
/* avoid allocating the first page */
start = max_t(phys_addr_t, start, PAGE_SIZE);
end = max(start, end);
if (memblock_bottom_up())
return __memblock_find_range_bottom_up(start, end, size, align,
nid, flags);
else
return __memblock_find_range_top_down(start, end, size, align,
nid, flags);
}
-
如果从memblock_alloc过来, end就是MEMBLOCK_ALLOC_ACCESSIBLE,这个时候会设置为current_limit。
-
如果不通过memblock_alloc分配, 内存范围就是指定的范围。紧接着对start做调整,为的是避免申请到第一个页面。
memblock_bottom_up返回的是memblock.bottom_up,前面初始化的时候也知道这个值是false(在numa初始化时会设置为true),所以初始化前期应该调用的是__memblock_find_range_top_down函数去查找内存:
__memblock_find_range_top_down查找内存区域
mm/memblock.c
static phys_addr_t __init_memblock
__memblock_find_range_top_down(phys_addr_t start, phys_addr_t end,
phys_addr_t size, phys_addr_t align, int nid,
enum memblock_flags flags)
{
phys_addr_t this_start, this_end, cand;
u64 i;
for_each_free_mem_range_reverse(i, nid, flags, &this_start, &this_end,
NULL) {
this_start = clamp(this_start, start, end);
this_end = clamp(this_end, start, end);
if (this_end < size)
continue;
cand = round_down(this_end - size, align);
if (cand >= this_start)
return cand;
}
return 0;
}
-
函数通过使用for_each_free_mem_range_reverse宏封装调用__next_free_mem_range_rev()函数,此函数逐一将memblock.memory里面的内存块信息提取出来与memblock.reserved的各项信息进行检验,确保返回的this_start和this_end不会是分配过的内存块。
-
然后通过clamp取中间值,判断大小是否满足,满足的情况下,将自末端向前(因为这是top-down申请方式)的size大小的空间的起始地址(前提该地址不会超出this_start)返回回去。
满足要求的内存块算是找到。
memblock_alloc_range_nid()该函数完成了两项工作:
-
首先通过memblock_find_in_range_node指定内存区域和大小查找内存区域。
-
找到内存区域后, 调用memblock_reserve后将其标为已经分配。
现在已经找到了内存区域了, 那么memblock_reserve函数是如何堆内存进行标记的, 该函数定义mm/memblock.c
int __init_memblock memblock_reserve(phys_addr_t base, phys_addr_t size)
{
phys_addr_t end = base + size - 1;
memblock_dbg("%s: [%pa-%pa] %pS\n", __func__,
&base, &end, (void *)_RET_IP_);
return memblock_add_range(&memblock.reserved, base, size, MAX_NUMNODES, 0);
}
首先memblock_reserve函数也是通过memblock_add_range来实现的, 与memblock_add的实现进行对比:
memblock_reserve
流程图
+----------------------------------+
| memblock_reserve() |
| (开始处理预留内存区域) |
+----------------------------------+
|
v
+-----------------------------+ +-------------------------------+
| 检查传入的内存区域是否有效? |----->| 传入区域无效:返回错误 |
| (地址、大小合法性检查) | | 处理失败 |
+-----------------------------+ +-------------------------------+
|
v
+--------------------------------------------+
| 将内存区域标记为已预留 |
| memblock_reserve 标记区域为已预留状态 |
+--------------------------------------------+
|
v
+--------------------------------------------+
| 更新 memblock 数据结构 |
| 防止内存分配器使用该区域 |
+--------------------------------------------+
|
v
+-----------------------------+
| 完成预留操作 |
+-----------------------------+
memblock_add
流程图
+----------------------------------+
| memblock_add() |
| (开始添加新的内存区域) |
+----------------------------------+
|
v
+-----------------------------+ +-------------------------------+
| 检查传入的内存区域是否有效? |----->| 传入区域无效:返回错误 |
| (地址、大小合法性检查) | | 处理失败 |
+-----------------------------+ +-------------------------------+
|
v
+----------------------------------+
| 将新内存区域添加到 memblock |
| 更新 memblock 数据结构 |
+----------------------------------+
|
v
+--------------------------------------------+
| 检查并合并相邻的内存区域 |
| 调用memblock_merge_regions合并内存块 |
+--------------------------------------------+
|
v
+-----------------------------+
| 完成内存区域添加 |
+-----------------------------+
3.5 memblock_free释放内存区域
mm/memblock.c
/**
* memblock_free - free boot memory allocation
* @ptr: starting address of the boot memory allocation
* @size: size of the boot memory block in bytes
*
* Free boot memory block previously allocated by memblock_alloc_xx() API.
* The freeing memory will not be released to the buddy allocator.
*/
void __init_memblock memblock_free(void *ptr, size_t size)
{
if (ptr)
memblock_phys_free(__pa(ptr), size);
}
/**
* memblock_phys_free - free boot memory block
* @base: phys starting address of the boot memory block
* @size: size of the boot memory block in bytes
*
* Free boot memory block previously allocated by memblock_phys_alloc_xx() API.
* The freeing memory will not be released to the buddy allocator.
*/
int __init_memblock memblock_phys_free(phys_addr_t base, phys_addr_t size)
{
phys_addr_t end = base + size - 1;
memblock_dbg("%s: [%pa-%pa] %pS\n", __func__,
&base, &end, (void *)_RET_IP_);
kmemleak_free_part_phys(base, size);
return memblock_remove_range(&memblock.reserved, base, size);
}
四、memblock初始化
arm64下的memblock初始化,从start_kernel开始, 进入setup_arch(), 并完成了早期内存分配器的初始化和设置工作。
arch/arm64/kernel/setup.c
void __init setup_arch(char **cmdline_p)
{
/* 初始化memblock */
arm64_memblock_init( );
/* 分页机制初始化 */
paging_init();
/* 初始化内存管理 */
bootmem_init();
}
其中arm64_memblock_init就完成了arm64架构下的memblock的初始化。
五、总结
memblock内存管理是将所有的物理内存放到memblock.memory
中作为可用内存来管理, 分配过的内存只加入到memblock.reserved
中, 并不从memory
中移出。
memblock
是 Linux 内核中的一个内存管理机制,主要用于内核启动阶段的物理内存分配和管理。它在内核的早期阶段(尤其是在初始化阶段)负责管理和预留物理内存,确保内核能有效地控制物理内存的布局。
memblock
的设计目标是提供一种简单且高效的方式来管理物理内存,而不依赖于传统的内存分配机制(如 kmalloc
和 slab
),因为这些机制需要依赖于页表等更复杂的内存管理设施,而这些设施在内核启动初期并不可用。
memblock
分配器的工作原理
memblock
的主要任务是管理物理内存,它使用一个简单的数据结构来记录可用内存区域,并提供操作接口来分配和保留内存。它通常在内核启动时进行初始化,并用于以下几个任务:
- 内存区域管理:管理物理内存的可用区域,包括内存的添加、删除、保留等操作。
- 内存区域分配:为内核提供内存分配和预留机制,特别是在内核初始化阶段。
- 内存的合并与拆分:管理内存的碎片化问题,在内存区域合并或拆分时进行优化。
同理释放内存也会加入到memory
中. 也就是说, memory
在fill
过后基本就是不动的了. 申请和分配内存仅仅修改reserved
就达到目的. 在初始化阶段没有那么多复杂的内存操作场景, 甚至很多地方都是申请了内存做永久使用的, 所以这样的内存管理方式已经足够凑合着用了, 毕竟内核也不指望用它一辈子. 在系统完成初始化之后所有的工作会移交给强大的buddy
系统来进行内存管理。