最近看了一份博客介绍memblock的算法实现,整理下温故而知新。
引:Linux-3.14.12内存管理笔记【系统启动阶段的memblock算法(1)】-Jean_Leo-ChinaUnix博客
memblock算法是linux内核初始化阶段的一个内存分配器(它取代了原来的bootmem算法),实现较为简单。负责page allocator初始化之前的内存管理和分配请求。
分析memblock算法,可以从几点入手:
1、 memblock算法初始化;
2、 memblock算法管理内存的申请和释放;
memblock算法前的准备:
前面已经分析了linux系统在初始化的过程中,使用int 15中断探知了机器的内存分布图(e820图),其数据是存储在boot_params.e820_map里面,这里面是没有经过整理的数据,杂乱无章,毕竟BIOS没有责任做整理内存的事情,所以这部分事情由系统来实现。那么看一下linux如何实现这部分功能的,这部分功能是在setup_memory_map里面实现的。主要就是为了把通过BIOS中断探测到的内存布局信息boot_params.e820_map做整合处理,完了转存到变量e820中。
先来看打印出所有e820探测的物理内存:
start_kernel()->setup_arch->setup_memory_map(),打印信息如下:
这里的地址范围是物理地址空间的范围,并不是实际上的内存条上的物理地址,这里是把物理内存条上的地址映射到这个地址空间,也就是我们访问的物理地址 physical addrsss。
设备启动后除了一些特殊的地址范围和一些reserverd的内存,其余的usable是我们linux系统可直接使用的,也就是start_kernel()可使用的内存大小是usable的内存。
这个是设备时16G物理内存大小,以上usable计算出来的物理内存大小大概为15.902G,也就是有100M内存被其他占用,但是占用不多。
以上是memblock初始化的准备工作。
#define INIT_MEMBLOCK_REGIONS 128
它将所有状态都保存在一个全局变量memblock中,算法的初始化以及内存的申请释放都是在将内存块的状态做变更。
先介绍结构体 struct memblock
bottom_up:用来表示分配器分配内存是自低地址(低地址指的是内核映像尾部,下同)向高地址还是自高地址向低地址来分配的;
current_limit:用来表示用来限制memblock_alloc()和memblock_alloc_base(..., MEMBLOCK_ALLOC_ACCESSIBLE)的内存申请;
memory:表示可用可分配的内存;
reserved:表示已经分配出去了的内存;
memory和reserved是很关键的一个数据结构,memblock算法的内存初始化和申请释放都是围绕着它们转。
往下看看memory和reserved的结构体struct memblock_type定义:
cnt和max分别表示当前状态(memory/reserved)的内存块可用数和可支持的最大数,total_size则表示当前状态(memory/reserved)的空间大小(也就是可用的内存块信息大小总和),而regions则是用于保存内存块信息的结构(包括基址、大小和标记等):
总的来说以上的关系图:
第一个图片可以看出全局变量memblock已经对部分数据进行了初始化:它初始化了部分成员,表示内存申请自高地址向低地址,且current_limit设为~0,即0xFFFFFFFF,同时通过全局变量定义为memblock的算法管理中的memory和reserved准备了内存空间。
接下来分析一下memblock算法初始化,其初始化函数为memblock_x86_fill(),初始化调用栈位置:
start_kernel() #/init/main.c
└->setup_arch() #/arch/x86/kernel/setup.c
└->memblock_x86_fill()
遍历e820中的每一个内存entry块,将E820_RAM即usable的内存调用memblock_add添加。
接下来看memblock_add()函数:
【file:/mm/memblock.c】
/**
* memblock_add_region - 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.
*/
static int __init_memblock memblock_add_region(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;
phys_addr_t end = base + memblock_cap_size(base, &size);
int i, nr_new;
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 (i = 0; i < type->cnt; i++) {
struct memblock_region *rgn = &type->regions[i];
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) {
nr_new++;
if (insert)
memblock_insert_region(type, i++, 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, i, 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_region()函数的行为流程:
1、 如果memblock算法管理内存为空的时候,则将当前空间添加进去;
2、 不为空的情况下,则先检查是否存在内存重叠的情况,如果有的话,则剔除重叠部分,然后将其余非重叠的部分添加进去;
3、 如果出现region[]数组空间不够的情况,则通过memblock_double_array()添加新的region[]空间;
4、 最后通过memblock_merge_regions()把紧挨着的内存合并了。
现在很明了,可以看到其功能作用是把e820图里面的内存布局转换到memblock管理算法当中的memblock.memory进行管理,表示该内存可用,e820探测到多少个usable内存块,就对应多少个region,最大128个,region地址由低到高排序,各region没有重叠。
memblock_trim_memory()对各region的start和end修建成PAGE_SIZE对齐 。
memblock_dump_all()打印memblock信息。
memblock_alloc函数:
这里两个重要的函数显现出来,一个是memblock_find_in_range_node()一个memblock_reserve()。
memblock_find_in_range_node()->__memblock_find_range_top_down()函数:
__memblock_find_range_top_down()通过使用for_each_free_mem_range_reverse宏封装调用__next_free_mem_range_rev()函数,此函数逐一将memblock.memory里面的内存块信息提取出来与memblock.reserved的各项信息进行检验,确保返回的this_start和this_end不会与reserved的内存存在交叉重叠的情况。然后通过clamp取中间值,判断大小是否满足,满足的情况下,将自末端向前(因为这是top-down申请方式)的size大小的空间的起始地址(前提该地址不会超出this_start)返回回去。至此满足要求的内存块算是找到了。
接下来是另一重要好函数memblock_reserve():
可以看到memblock_reserve_region()是通过memblock_add_region()函数往memblock.reserved里面添加内存块信息。
三、释放
memblock_free()实现:
其主要功能是将指定下标索引的内存项从memblock.reserved管理结构中移除。
memblock管理算法将可用可分配的内存在memblock.memory进行管理起来,已分配的内存在memblock.reserved进行管理,只要内存块加入到memblock.reserved里面就表示该内存已经被申请占用了。所以有个关键点需要注意,内存申请的时候,仅是把被申请到的内存加入到memblock.reserved中,并不会在memblock.memory里面有相关的删除或改动的操作,这也就是为什么申请和释放的操作都集中在memblock.reserved的原因了。这个算法效率并不高,但是这是合理的,毕竟在初始化阶段没有那么多复杂的内存操作场景,甚至很多地方都是申请了内存做永久使用的。
也就是说申请memblock.memory永远都是管理设备所有可识别的内存也就是usable对应的内存。尽管添加memblock.reserved中也不会从memblock.memory清除。
for_each_mem_pfn_range,遍历memblock的memory帧的范围。
#define for_each_mem_pfn_range(i, nid, p_start, p_end, p_nid) \
for (i = -1, __next_mem_pfn_range(&i, nid, p_start, p_end, p_nid); \
i >= 0; __next_mem_pfn_range(&i, nid, p_start, p_end, p_nid))
void __init_memblock __next_mem_pfn_range(int *idx, int nid,
unsigned long *out_start_pfn,
unsigned long *out_end_pfn, int *out_nid)
{
struct memblock_type *type = &memblock.memory; //遍历memblock的memory的帧
struct memblock_region *r;
while (++*idx < type->cnt) {
r = &type->regions[*idx];
if (PFN_UP(r->base) >= PFN_DOWN(r->base + r->size))
continue;
if (nid == MAX_NUMNODES || nid == r->nid)
break;
}
if (*idx >= type->cnt) {
*idx = -1;
return;
}
if (out_start_pfn)
*out_start_pfn = PFN_UP(r->base);
if (out_end_pfn)
*out_end_pfn = PFN_DOWN(r->base + r->size);
if (out_nid)
*out_nid = r->nid;
}
for_each_free_mem_range,从注解中可以看到,memory-reserved的帧,即未被分配出去的帧
/* Walks over free (memory && !reserved) areas of memblock. Available as
* soon as memblock is initialized.
*/
#define for_each_free_mem_range(i, nid, flags, p_start, p_end, p_nid) \
for_each_mem_range(i, &memblock.memory, &memblock.reserved, \
nid, flags, p_start, p_end, p_nid)