分类: LINUX
2014-07-11 16:02:51
原文地址:Vi Linux内存 之 bootmem分配器(二) 作者:palals
bootmem内存的分配
__alloc_bootmem
通过bootmem分配器分配内存。
参数:
1) size:分配内存的大小
2) align:对齐大小
3) goal:限定在某个范围内分配,goal是限定范围的起始地址
void * __init __alloc_bootmem(unsigned long size, unsigned long align,
unsigned long goal)
{
/* limit为限定范围的截止地址,为0表示没有限制。 */
unsigned long limit = 0;
#ifdef CONFIG_NO_BOOTMEM
limit = -1UL;
#endif
/* 在某个限定范围内分配内存 */
return ___alloc_bootmem(size, align, goal, limit);
}
___alloc_bootmem
在goal与limit之间分配内存。
static void * __init ___alloc_bootmem(unsigned long size, unsigned long align,
unsigned long goal, unsigned long limit)
{
/* 直接调用___alloc_bootmem_nopanic */
void *mem = ___alloc_bootmem_nopanic(size, align, goal, limit);
/* 分配成功返回 */
if (mem)
return mem;
/*
* Whoops, we cannot satisfy the allocation request.
*/
printk(KERN_ALERT "bootmem alloc of %lu bytes failed!\n", size);
panic("Out of memory");
return NULL;
}
___alloc_bootmem_nopanic
参数:
1) size:分配内存的大小
2) align:对齐大小
3) goal:限定范围的起始地址
4) limit:限定范围的截止地址
static void * __init ___alloc_bootmem_nopanic(unsigned long size,
unsigned long align,
unsigned long goal,
unsigned long limit)
{
#ifdef CONFIG_NO_BOOTMEM
/* 如果没有bootmem,通过其他手段模拟bootmem,应该是针对x86架构的,不去管它 */
void *ptr;
if (WARN_ON_ONCE(slab_is_available()))
return kzalloc(size, GFP_NOWAIT);
restart:
ptr = __alloc_memory_core_early(MAX_NUMNODES, size, align, goal, limit);
if (ptr)
return ptr;
if (goal != 0) {
goal = 0;
goto restart;
}
return NULL;
#else
/* 从bootmem中分配 */
bootmem_data_t *bdata;
void *region;
restart:
/* 从体系结构优先选择的节点分配bootmem */
region = alloc_arch_preferred_bootmem(NULL, size, align, goal, limit);
if (region)
return region;
/* 遍历bdata_list中的bootmem,UMA只有一个节点 */
list_for_each_entry(bdata, &bdata_list, list) {
/* 此节点bootmem的截止pfn在参数指定的起始pfn之下,不符合要求,跳过 */
if (goal && bdata->node_low_pfn <= PFN_DOWN(goal))
continue;
/* 此节点bootmem的起始pfn在参数指定的截止pfn之上,不符合要求,跳过 */
if (limit && bdata->node_min_pfn >= PFN_DOWN(limit))
break;
/* 找到了符合条件的bootmem,从中分配内存 */
region = alloc_bootmem_core(bdata, size, align, goal, limit);
if (region)
return region;
}
/* 未找到符合条件的bootmem,去掉起始地址限制,重新扫描 */
if (goal) {
goal = 0;
goto restart;
}
return NULL;
#endif
}
alloc_arch_preferred_bootmem
体系结构如果定义了优先选择的内存节点,从该节点的bootmem中分配。
static void * __init alloc_arch_preferred_bootmem(bootmem_data_t *bdata,
unsigned long size, unsigned long align,
unsigned long goal, unsigned long limit)
{
/* 如果slab可用,使用slab分配器 */
if (WARN_ON_ONCE(slab_is_available()))
return kzalloc(size, GFP_NOWAIT);
#ifdef CONFIG_HAVE_ARCH_BOOTMEM
{
bootmem_data_t *p_bdata;
/* 如果体系结构定义了优先选择的节点,获得该节点的bootmem */
p_bdata = bootmem_arch_preferred_node(bdata, size, align,
goal, limit);
/* 如果该节点bootmem有效,从中分配内存 */
if (p_bdata)
return alloc_bootmem_core(p_bdata, size, align,
goal, limit);
}
#endif
return NULL;
}
__alloc_bootmem_core
Bootmem分配器的核心函数,从某内存节点的bootmem中分配内存,即将位图相关比特位置1,设为保留的内存区。
参数:
1) bdata:某内存节点的bootmem
2) size:分配内存的大小
5) align:对齐大小
6) goal:限定范围的起始地址
7) limit:限定范围的截止地址
static void * __init alloc_bootmem_core(struct bootmem_data *bdata,
unsigned long size, unsigned long align,
unsigned long goal, unsigned long limit)
{
unsigned long fallback = 0;
unsigned long min, max, start, sidx, midx, step;
bdebug("nid=%td size=%lx [%lu pages] align=%lx goal=%lx limit=%lx\n",
bdata - bootmem_node_data, size, PAGE_ALIGN(size) >> PAGE_SHIFT,
align, goal, limit);
/* 分配的内存大小不能为0 */
BUG_ON(!size);
/* 对齐大小必须是2的幂数 */
BUG_ON(align & (align - 1));
/* 起始地址+申请的内存大小>截止地址,参数错误 */
BUG_ON(limit && goal + size > limit);
/* 未初始化此bootmem的位图,返回 */
if (!bdata->node_bootmem_map)
return NULL;
/* min表示bootmem的起始pfn */
min = bdata->node_min_pfn;
/* max表示bootmem的截止pfn */
max = bdata->node_low_pfn;
/* 限定范围的起止地址转换为pfn */
goal >>= PAGE_SHIFT;
limit >>= PAGE_SHIFT;
/* 参数指定的截止地址更小,以参数指定的截止地址为准 */
if (limit && max > limit)
max = limit;
/* 起止地址错误,返回 */
if (max <= min)
return NULL;
/* step为扫描位图时,每次递增的页数/pfn偏移数。由对齐大小计算而来,不足一页时,step为1,表示每次递增一页 */
step = max(align >> PAGE_SHIFT, 1UL);
if (goal && min < goal && goal < max)
/* 参数指定的起始pfn更大,根据它计算起始pfn,并按申请的页数对齐*/
start = ALIGN(goal, step);
else
/* 取bootmem的起始pfn */
start = ALIGN(min, step);
/* 计算起始pfn与bootmem起始pfn的偏移,后面扫描位图时从这个sidx开始 */
sidx = start - bdata->node_min_pfn;
/* 计算截止pfn与bootmem起始pfn的偏移。扫描的就是sidx至midx之间的这部分bootmem内存区 */
midx = max - bdata->node_min_pfn;
/* hint_idx表示优先扫描的偏移,如果大于sidx,则从hint_idx处开始扫描。
何为优先扫描呢?由于分配是从低地址开始,显然下一次扫描时,从上一次分配的截止地址开始扫描成功率会更高。hint_idx之前的空间可能由于对齐的原因仍是空闲的。释放bootmem时会更新hint_idx的值,指向释放的位置。仅当优先扫描失败,才需要回过头来扫描以前分配过的区域。
*/
if (bdata->hint_idx > sidx) {
/*
* Handle the valid case of sidx being zero and still
* catch the fallback below.
*/
/* fallback表示首次扫描是否优先扫描 */
fallback = sidx + 1;
/* 从优先扫描偏移处开始扫描 */
sidx = align_idx(bdata, bdata->hint_idx, step);
}
while (1) {
int merge;
void *region;
unsigned long eidx, i, start_off, end_off;
/* 从位图中找到满足对齐要求的、为0的、连续的比特位 */
find_block:
/* 从位图中找到下一个为0的比特位 */
sidx = find_next_zero_bit(bdata->node_bootmem_map, midx, sidx);
/* 将sidx按照申请的页数对齐,对齐后的比特位不一定是0 */
sidx = align_idx(bdata, sidx, step);
/* 计算结尾pfn */
eidx = sidx + PFN_UP(size);
/* 如果超出了截止pfn,整个位图扫描完毕,未找到符合条件的空闲内存区,跳出循环 */
if (sidx >= midx || eidx > midx)
break;
/* 检查sidx和eidx之间的所有比特位是否全部为0 */
for (i = sidx; i < eidx; i++)
if (test_bit(i, bdata->node_bootmem_map)) {
/* 某比特位为1,这段区间不符合条件,从下一个对齐偏移处继续 扫描 */
sidx = align_idx(bdata, i, step);
/* 前面对齐采用的是入式对齐,如果是第一个比特位为1,即对齐余数为0,没有入上去,需要加上step */
if (sidx == i)
sidx += step;
/* 继续扫描 */
goto find_block;
}
/* last_end_off表示上一次分配截止地址的偏移。如果其不是页面大小对齐的,说明该页中还有空闲的空间。并且如果其所在的页面就是扫描到的内存区的上一页,则可以利用该页的空闲空间。*/
if (bdata->last_end_off & (PAGE_SIZE - 1) &&
PFN_DOWN(bdata->last_end_off) + 1 == sidx)
/* start_off表示本次分配的内存区起始处的地址偏移,需要按照指定方式对齐 */
start_off = align_off(bdata, bdata->last_end_off, align);
else
/* 否则的话,就是从新的页面开始分配了,直接通过pfn偏移计算地址偏移 */
start_off = PFN_PHYS(sidx);
/* 如果使用了上一页的空闲空间,该页对应的比特位无需置1,已经置过了 */
merge = PFN_DOWN(start_off) < sidx;
/* 计算本次分配的截止地址偏移 */
end_off = start_off + size;
/* 用本次分配的截止地址偏移更新last_end_off */
bdata->last_end_off = end_off;
/* 更新hint_idx为last_end_off之后第一个可用的pfn,即下一次优先扫描的位置 */
bdata->hint_idx = PFN_UP(end_off);
/*
* Reserve the area now:
*/
/* 将要分配出去的内存区对应的位图比特位置1,BOOTMEM_EXCLUSIVE表示此次分配是排它的 */
if (__reserve(bdata, PFN_DOWN(start_off) + merge,
PFN_UP(end_off), BOOTMEM_EXCLUSIVE))
BUG();
/* 将要分配出去的内存区清0 */
region = phys_to_virt(PFN_PHYS(bdata->node_min_pfn) +
start_off);
memset(region, 0, size);
/*
* The min_count is set to 0 so that bootmem allocated blocks
* are never reported as leaks.
*/
/* 内存泄露检测相关,不去管它 */
kmemleak_alloc(region, size, 0, 0);
return region;
}
/* 走到这说明优先扫描没有找到符合条件的空闲内存区,扫描初始sidx(fallback记录)之前的区域 */
if (fallback) {
sidx = align_idx(bdata, fallback - 1, step);
/* 扫描一次即可 */
fallback = 0;
goto find_block;
}
/* 还没有找到,分配失败 */
return NULL;
}
__reserve
设置某块bootmem内存为保留区。
static int __init __reserve(bootmem_data_t *bdata, unsigned long sidx,
unsigned long eidx, int flags)
{
unsigned long idx;
/* BOOTMEM_EXCLUSIVE表示是否要求独占这块bootmem */
int exclusive = flags & BOOTMEM_EXCLUSIVE;
bdebug("nid=%td start=%lx end=%lx flags=%x\n",
bdata - bootmem_node_data,
sidx + bdata->node_min_pfn,
eidx + bdata->node_min_pfn,
flags);
for (idx = sidx; idx < eidx; idx++)
if (test_and_set_bit(idx, bdata->node_bootmem_map)) {
/* 在这之前已经置为1,说明有人正在使用这块内存 */
if (exclusive) {
/* 如果是独占型的,独占失败,将之前已经置1的比特位清0 */
__free(bdata, sidx, idx);
/* 返回错误 */
return -EBUSY;
}
/* 非独占,打印重复置1的提示信息 */
bdebug("silent double reserve of PFN %lx\n",
idx + bdata->node_min_pfn);
}
return 0;
}