linux vi 内存,Vi Linux内存 之 bootmem分配器(二)

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;

}

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值