memblock是linux用于管理内存的一种机制。
接下来说一些这个机制的初始化,以及其操作方法。
先看一下memblock中使用的结构体:
//memblock中的flag
enum {
MEMBLOCK_NONE = 0x0, /* No special request */
MEMBLOCK_HOTPLUG = 0x1, /* hotpluggable region */
MEMBLOCK_MIRROR = 0x2, /* mirrored region */
};
struct memblock_region {
phys_addr_t base; //region的基地址
phys_addr_t size; //region的大小
unsigned long flags; //region的标志
#ifdef CONFIG_HAVE_MEMBLOCK_NODE_MAP
int nid; //属于哪个noid
#endif
};
struct memblock_type {
unsigned long cnt; /*当前regions的数量 */
unsigned long max; /*当前集合中可记录的n内存区域的最大大小*/
phys_addr_t total_size; /*region的总大小*/
struct memblock_region *regions; //指向内存区域
};
struct memblock {
bool bottom_up; /*表示分配器的分配方式,true从低地址向高地址分配,false则相反*/
phys_addr_t current_limit; //指出n内存块的大小限制,
struct memblock_type memory;//可用内存
struct memblock_type reserved;//已分配内存
};
memblock的初始化:
#define INIT_MEMBLOCK_REGIONS 128
/*可分配内存区域*/
static struct memblock_region memblock_memory_init_regions[INIT_MEMBLOCK_REGIONS] __initdata_memblock;
/*已分配内存区域*/
static struct memblock_region memblock_reserved_init_regions[INIT_MEMBLOCK_REGIONS] __initdata_memblock;
struct memblock memblock __initdata_memblock = {
.memory.regions = memblock_memory_init_regions,
.memory.cnt = 1, /* empty dummy entry */
.memory.max = INIT_MEMBLOCK_REGIONS,
.reserved.regions = memblock_reserved_init_regions,
.reserved.cnt = 1, /* empty dummy entry */
.reserved.max = INIT_MEMBLOCK_REGIONS,
.bottom_up = false,
.current_limit = MEMBLOCK_ALLOC_ANYWHERE,
};
memblock的操作函数:
加入操作
//把基址为base,大小为size的内存区域加入memblock中,并标记node号为nid
int __init_memblock memblock_add_node(phys_addr_t base, phys_addr_t size,int nid)
{
return memblock_add_range(&memblock.memory, base, size, nid, 0);
}
移除操作
//从memblock中去除基址为base,大小为size的区域
int __init_memblock memblock_remove(phys_addr_t base, phys_addr_t size)
{
return memblock_remove_range(&memblock.memory, base, size);
}
释放操作:
//释放一段基址为base,大小为size的区域
int __init_memblock memblock_free(phys_addr_t base, phys_addr_t size)
{
//调用memblock_remove_range把这个区域删除
return memblock_remove_range(&memblock.reserved, base, size);
}
标记已经使用方法:
//把基址为base,大小为size的区域设置为已分配
int __init_memblock memblock_reserve(phys_addr_t base, phys_addr_t size)
{
return memblock_reserve_region(base, size, MAX_NUMNODES, 0);
}
内存分配函数
phys_addr_t __init memblock_alloc(phys_addr_t size, phys_addr_t align)
{
return memblock_alloc_base(size, align, MEMBLOCK_ALLOC_ACCESSIBLE);
}
一: add函数的实现
/*
* 把一块内存区域放入到memblock中(memory类型)
*这个函数执行分为两步:
*第一步:判断原来的记录数加上要插入的区域数后是否大于最大可记录数,如果大于就把type翻倍
*第二步:type翻倍后,再进行插入操作
*/
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;
phys_addr_t end = base + memblock_cap_size(base, &size);
int i, nr_new;
//如果大小为0,则直接返回
if (!size)
return 0;
//type为空时,就直接插入到regions[0]中。也就是插入的第一块
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:
base = obase;
nr_new = 0;
//对type中所有的region进行查询,看看有没有和要插入区域重叠的部分
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;
//运行到这里,说明rbase<end,而rbase>base,有重叠部分
//nr_new记录要插入的区域的数
if (rbase > base) {
nr_new++;
if (insert)
//把这个区域插入
memblock_insert_region(type, i++, base,
rbase - base, nid,
flags);
}
//更新base为新插入区域的末端
base = min(rend, end);
}
/*如果base<end,则说明还有部分没有插入*/
if (base < end) {
nr_new++;
if (insert)
memblock_insert_region(type, i, base, end - base,
nid, flags);
}
//插入操作分为两步,这里是第一部分
/*
*这里是当要插入的额区域加上原有的区域大于最大可记录数时,则把type翻倍,然后再插入
*/
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;
}
}
下面这个函数就是为参数中的type设置为原来的两倍大小,并在memblock重新为其分配了一块内存
static int __init_memblock memblock_double_array(struct memblock_type *type,phys_addr_t new_area_start,phys_addr_t new_area_size)
{
struct memblock_region *new_array, *old_array;
phys_addr_t old_alloc_size, new_alloc_size;
phys_addr_t old_size, new_size, addr;
int use_slab = slab_is_available();
int *in_slab;
//判断memblock是否可以resize
if (!memblock_can_resize)
return -1;
/*计算新的大小,使得新大小为原来的两倍*/
old_size = type->max * sizeof(struct memblock_region);
new_size = old_size << 1;
//设置新大小的对齐格式
old_alloc_size = PAGE_ALIGN(old_size);
new_alloc_size = PAGE_ALIGN(new_size);
//设置在哪个内存中memory,还是reserve
if (type == &memblock.memory)
in_slab = &memblock_memory_in_slab;
else
in_slab = &memblock_reserved_in_slab;
//目前还没有使用slab,是走else分支
if (use_slab) {
new_array = kmalloc(new_size, GFP_KERNEL);
addr = new_array ? __pa(new_array) : 0;
} else {
//如果不是reserved类型的memblock,就设置new_area_start和new_area_size为0
if (type != &memblock.reserved)
new_area_start = new_area_size = 0;
//在memblock中为查找一个大小为new_alloc_size的内存区域
addr = memblock_find_in_range(new_area_start + new_area_size,memblock.current_limit,new_alloc_size, PAGE_SIZE);
if (!addr && new_area_size)
addr = memblock_find_in_range(0,min(new_area_start, memblock.current_limit),new_alloc_size, PAGE_SIZE);
//用new_array记录新块的物理地址
new_array = addr ? __va(addr) : NULL;
}
//把参数中的type->regions拷贝到new_array中
memcpy(new_array, type->regions, old_size);
//把new_array中从type->max地址后old_size长度的内存清零
memset(new_array + type->max, 0, old_size);
//使用old_array记录原来的type->regions
old_array = type->regions;
//把new_array赋值给type->regions
type->regions = new_array;
//把最大能记录的数值乘以2
type->max <<= 1;
if (*in_slab)
kfree(old_array);
//把原来的old_array释放掉
else if (old_array != memblock_memory_init_regions &&
old_array != memblock_reserved_init_regions)
memblock_free(__pa(old_array), old_alloc_size);
return 0;
}
具体插入操作的函数
static void __init_memblock memblock_insert_region(struct memblock_type *type,int idx, phys_addr_t base,phys_addr_t size,int nid, unsigned long flags)
{
//获取要插入的内存区域
struct memblock_region *rgn = &type->regions[idx];
//验证是否大于最大可记录的值
BUG_ON(type->cnt >= type->max);
//把rgn拷贝到rgn+1的地方
memmove(rgn + 1, rgn, (type->cnt - idx) * sizeof(*rgn));
//重新设置rgn的基址,大小,flag信息
rgn->base = base;
rgn->size = size;
rgn->flags = flags;
//设置rgn的对应的node为nid
memblock_set_region_node(rgn, nid);
//记录的内存区域数量加1
type->cnt++;
//记录的内存区域的总大小更新
type->total_size += size;
}
合并邻居区域的操作:
//合并相邻的内存区域
static void __init_memblock memblock_merge_regions(struct memblock_type *type)
{
int i = 0;
//循环遍历这个类型的内存区域(memory,reserve两种)中的所有region
while (i < type->cnt - 1) {
struct memblock_region *this = &type->regions[i];
struct memblock_region *next = &type->regions[i + 1];
//如果不是相邻的,或者不是同一个node上的,或者flag不相同,则i+1,进行下一个
if (this->base + this->size != next->base ||
memblock_get_region_node(this) !=
memblock_get_region_node(next) ||
this->flags != next->flags) {
BUG_ON(this->base + this->size > next->base);
i++;
continue;
}
//运行到这里,说明可以合并
//把this的大小扩展为this->size+next->size
//并把next+1和区域赋值到next上,然后把记录的区域数减1
this->size += next->size;
memmove(next, next + 1, (type->cnt - (i + 2)) * sizeof(*next));
type->cnt--;
}
}
二, remove操作:
int __init_memblock memblock_remove_range(struct memblock_type *type,phys_addr_t base, phys_addr_t size)
{
int start_rgn, end_rgn;
int i, ret;
ret = memblock_isolate_range(type,base,size,&start_rgn, &end_rgn);
if (ret)
return ret;
//调用memblock_remove_region进行删除操作
for (i = end_rgn - 1; i >= start_rgn; i--)
memblock_remove_region(type, i);
return 0;
}
static void __init_memblock memblock_remove_region(struct memblock_type *type, unsigned long r)
{
/*
*把type管理的大小减少,并把后一个区域的内容拷贝到当前的区域
* 然后把cnt减1
*/
type->total_size -= type->regions[r].size;
memmove(&type->regions[r], &type->regions[r + 1],
(type->cnt - (r + 1)) * sizeof(type->regions[r]));
type->cnt--;
//如果type为空,则把regions的基址,大小,flags清零
/* Special case for empty arrays */
if (type->cnt == 0) {
WARN_ON(type->total_size != 0);
type->cnt = 1;
type->regions[0].base = 0;
type->regions[0].size = 0;
type->regions[0].flags = 0;
memblock_set_region_node(&type->regions[0], MAX_NUMNODES);
}
}
三,free操作
free就是调用memblock_remove_region把区域进行删除
四, reserve操作
调用memblock_add_range把这个区域加入到reserve区域的内存中。
五,alloc操作
phys_addr_t __init memblock_alloc(phys_addr_t size, phys_addr_t align)
{
return memblock_alloc_base(size, align, MEMBLOCK_ALLOC_ACCESSIBLE);
}
phys_addr_t __init memblock_alloc_base(phys_addr_t size, phys_addr_t align, phys_addr_t max_addr)
{
phys_addr_t alloc;
alloc = __memblock_alloc_base(size, align, max_addr);
return alloc;
}
phys_addr_t __init __memblock_alloc_base(phys_addr_t size, phys_addr_t align, phys_addr_t max_addr)
{
return memblock_alloc_base_nid(size, align, max_addr, NUMA_NO_NODE,
MEMBLOCK_NONE);
}
static phys_addr_t __init memblock_alloc_base_nid(phys_addr_t size,
phys_addr_t align, phys_addr_t max_addr,
int nid, ulong flags)
{
phys_addr_t found;
if (WARN_ON(!align))
align = __alignof__(long long);
//设置对齐方式
size = round_up(size, align);
//在memblock中查找一个指定大小的区域
found = memblock_find_in_range_node(size, align, 0, max_addr, nid,
flags);
//如果找到了,把这个区域放入到reserve内存中。
if (found && !memblock_reserve(found, size))
return found;
return 0;
}
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, ulong flags)
{
phys_addr_t kernel_end, ret;
//如果end是MEMBLOCK_ALLOC_ACCESSIBLE,表示在所有区域中查找
if (end == MEMBLOCK_ALLOC_ACCESSIBLE)
end = memblock.current_limit;
start = max_t(phys_addr_t, start, PAGE_SIZE);
end = max(start, end);
//记录kernel加载的末地址
kernel_end = __pa_symbol(_end);
//如果botton_upe设置为true,则从上往下进行分配
//在这里是从下往上分配
if (memblock_bottom_up() && end > kernel_end) {
phys_addr_t bottom_up_start;
/* make sure we will allocate above the kernel */
bottom_up_start = max(start, kernel_end);
/* ok, try bottom-up allocation first */
ret = __memblock_find_range_bottom_up(bottom_up_start, end,
size, align, nid, flags);
if (ret)
return ret;
WARN_ONCE(1, "memblock: bottom-up allocation failed, "
"memory hotunplug may be affected\n");
}
return __memblock_find_range_top_down(start, end, size, align, nid,
flags);
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,
ulong flags)
{
phys_addr_t this_start, this_end, cand;
u64 i;
//从memblock中倒着往前查找
for_each_free_mem_range_reverse(i, nid, flags, &this_start, &this_end,NULL) {
//clamp判断this_start是否在start到end之间
this_start = clamp(this_start, start, end);
this_end = clamp(this_end, start, end);
if (this_end < size)
continue;
//page对齐
cand = round_down(this_end - size, align);
if (cand >= this_start)
return cand;
}
return 0;
}
从上往下分配
static phys_addr_t __init_memblock
__memblock_find_range_bottom_up(phys_addr_t start, phys_addr_t end,
phys_addr_t size, phys_addr_t align, int nid,
ulong flags)
{
phys_addr_t this_start, this_end, cand;
u64 i;
//正着从memblock中查找
for_each_free_mem_range(i, nid, flags, &this_start, &this_end, NULL) {
this_start = clamp(this_start, start, end);
this_end = clamp(this_end, start, end);
cand = round_up(this_start, align);
if (cand < this_end && this_end - cand >= size)
return cand;
}
return 0;
}
memblock的插入是按照地址从大到小进行插入的,所以从上往下查找就是正着查找memblock,而从下往上分配,则是从memblock尾端开始查找。