linux 内存分配 函数,Linux中的内存分配和释放之__alloc_boot函数分析

对于这个函数,其实是很多宏定义调用的函数,其中alloc_bootmem_low_pages(x)是其中一个调用它的宏,大家可以认为这些宏只是把这个__alloc_bootmem实质性的函数进行了封装。#define alloc_bootmem_low_pages(x)相当于__alloc_bootmem((x), PAGE_SIZE, 0),就是从0地址开始的低端内存分配按页大小对齐的内存。好了,为了了解这个函数是怎么运行的,我们就从它的源头开始讲起吧。

我们回到paging_init()这个函数,在上诉的文章的bootmem_init()函数返回之后,就接着回到paging_init()。

memcpy(&meminfo, mi, sizeof(meminfo));//这里起到更新的作用。

/*

* allocate the zero page.  Note that we count on this going ok.

*/

zero_page = alloc_bootmem_low_pages(PAGE_SIZE);//从0地址低端内存开始分配1页空闲内存,将该页的页帧位码表置1,并将这也得得内容全部清0.我们还是仔细点来分析这段代码吧。

void * __init __alloc_bootmem (unsigned long size, unsigned long align, unsigned long goal)//size是申请的内存大小,align是决定的起始地址开始的内存对齐方式,goal是分配内存的起始地址,如果不行的话就从0地址开始。

{

pg_data_t *pgdat = pgdat_list;//这个就是那个指向discontig_node_data[0]的链表。

void *ptr;

for_each_pgdat(pgdat)//这是一个宏定义:#define for_each_pgdat(pgdat)  for (pgdat = pgdat_list; pgdat; pgdat = pgdat->pgdat_next)。其实很简单,就是通过这个链表从0号内存node开始遍历所有的discontig_node_data[n]。

if ((ptr = __alloc_bootmem_core(pgdat->bdata, size,align, goal)))//调用分配内存核心函数,我们在下面紧接着的位置介绍。

return(ptr);

/*

* Whoops, we cannot satisfy the allocation request.

*/

printk(KERN_ALERT "bootmem alloc of %lu bytes failed!/n", size);

panic("Out of memory");

return NULL;

}

static void * __init __alloc_bootmem_core(struct bootmem_data *bdata, unsigned long size,unsigned long align, unsigned long goal)//这里和前面的__alloc_bootmem()不同的地方就是在第一项,对于这一项我们是比较熟悉的。

{

unsigned long offset, remaining_size, areasize, preferred;

unsigned long i, start = 0, incr, eidx;

void *ret;

if(!size) {

printk("__alloc_bootmem_core(): zero-sized request/n");//如果要申请的内存是0的话,系统就会崩溃。

BUG();

}

BUG_ON(align & (align-1));//如果不按2exp(n)对齐,则系统崩溃。

eidx = bdata->node_low_pfn - (bdata->node_boot_start >> PAGE_SHIFT);//计算包括内存孔洞在内的,内存节点的内存空间的总页数。

offset = 0;//预先设定从本内存node的起始地址开始分配。

if (align &&(bdata->node_boot_start & (align - 1UL)) != 0)//如果align>0并且内存node的物理起始基址又不是按align对齐。

offset = (align - (bdata->node_boot_start & (align - 1UL)));//求出内存node的物理起始基址到首个align的偏移字节数。

offset >>= PAGE_SHIFT;//求出偏移页数。

/*

* We try to allocate bootmem pages above 'goal'

* first, then we try to allocate lower pages.

*/

if (goal && (goal >= bdata->node_boot_start) && ((goal >> PAGE_SHIFT) < bdata->node_low_pfn)){//如果分配的起始地址不再0开头处的话,但是在本内存node的物理地址范围内

preferred = goal - bdata->node_boot_start;//计算这个偏移量。

if (bdata->last_success >= preferred)

preferred = bdata->last_success;//如果上次成功分配的内存起始位置,大于这次申请的地址话,就把上次的地址赋给这次的地址。

} else

preferred = 0;

preferred = ((preferred + align - 1) & ~(align - 1)) >> PAGE_SHIFT;//把分配起始位置相对于本内存node起始位置的偏移量转换成相对于本内存node的偏移页数。

preferred += offset;//offset起始是本内存node按align对齐后的偏移页数,preferred是按align对齐后的分配内存位置相对于本内存node起始位置的偏移页数。

areasize = (size+PAGE_SIZE-1)/PAGE_SIZE;//计算要分配的页数,不足一页按一页计算。

incr = align >> PAGE_SHIFT ? : 1;//对齐页数至少为一页的大小,这里是求出一个对齐所占的页数。

restart_scan:

for (i = preferred; i < eidx; i += incr) {//查看本内存node的所有内存页。

unsigned long j;

i = find_next_zero_bit(bdata->node_bootmem_map, eidx, i);//在本内存node的页帧位码表的第i位开始到第eidx位之间,寻找第一位为0的位号。

i = ALIGN(i, incr);//#define ALIGN(x,a) (((x)+(a)-1)&~((a)-1))由宏定义可以看出,又是把位号进行对齐。是按页数进行对齐,可以看到这位是属于哪个对齐内存页的。其实这一部是非常重要的。我在这里举个例子:假设,我们是从0页开始进行检测的,我们这里分两种情况。第一:当我们一上来就发现0号页是空闲的,我们可以拿来分配,这个是靠上条语句得来的。接着我们在这里对齐的时候,我们的‘i’还是0,这样我们就初步定下来在这个内存页内分配空间。如果我们的incr是4的话,我们就会通过下面的for来为我们对后面的1,2,3页进行测试是否空闲。第二种情况:如果我们等到1,2,3三者其中一页才检测到时空闲的话,我们来到这条语句的时候,会发现'i'会增加一个档位,如果对于incr=4时,i=4,这样我们就发现,如果发现连续4个页的第一个页并非空闲的话,我们就放弃这个档位(4页)。

if (test_bit(i, bdata->node_bootmem_map))continue;//其实这语句只对上面的第二种情况有实际意义的,为什么?自己体会一下吧。

for (j = i + 1; j < i + areasize; ++j) {//这里就是我上面提到的for循环,就是为了确保整个档位(4页)是空闲的,只要有一点瑕疵,就马上进行档位调整,如果顺利的话,我们就可以申请到我们需要的空间。

if (j >= eidx)goto fail_block;

if (test_bit (j, bdata->node_bootmem_map))goto fail_block;

}

start = i;//如果可以找到连续的内存,就把相对的内存页号赋给start。

goto found;

fail_block:

i = ALIGN(j, incr);//修改一下巡查页号,进行下一轮修改。

}

if (preferred > offset) {

preferred = offset;

goto restart_scan;

}//这个if肯定会被执行的,peferred+=offset这条语句可以看出,我们不是从偏移首align开始检测是否有空闲空间的,这里我们从新scan时,是从offset的页号开始的,对于是PAGE_SIZE对齐格式,我们就会从0号开始!

return NULL;

found:

bdata->last_success = start << PAGE_SHIFT;//把这次成功分配的空闲空间的起始虚拟地址,这是地址是按页对齐的。

BUG_ON(start >= eidx);//如果大于得花,说明已经超过了指定的低端内存页数,这样系统会崩溃的。

if (align < PAGE_SIZE &&bdata->last_offset && bdata->last_pos+1 == start) {//先要清楚bdata->last_offset是上次分配成功空间的结束地址相对于其页的页内偏移量,如果结束地址刚好是按页对齐的话,last_offset=0。可以看出if里面的判断是说明对于小于PAGE_SIZE对齐方式的,如果上次成功分配空间的结束位置的所在页正好在start的上一页。

offset = (bdata->last_offset+align-1) & ~(align-1);//按align格式来对齐偏移量,对于对齐后是一页的大小时,就不能用这些剩余空间来为本次分配内存,只能用上面找到的本内存node的第start页开始。

BUG_ON(offset > PAGE_SIZE);//如果大于PAGE_SIZE时,就出现系统崩溃。

remaining_size = PAGE_SIZE-offset;//计算上次分配的内存最后页中能用于本次分配的空闲内存大小。

if (size < remaining_size) {//如果本次分配size小于剩余空间的话,就执行以下语句。

areasize = 0;//由于剩余的空间够本次的分配,则这次分配不用新的一页来分配了。

bdata->last_offset = offset+size;//重新更新最后地址相对于本页的偏移量。

ret = phys_to_virt(bdata->last_pos*PAGE_SIZE + offset +bdata->node_boot_start);//bdata->last_pos是上次内存申请结束地址所在的页号(由于页号是从0号开始的),这样很明显括号里面求得的就是本次申请内存的起始物理地址,最后通过phys_to_virt宏定义求出虚拟地址。

} else {//如果上次分配剩下的空间足以为本次分配所用。

remaining_size = size - remaining_size;//计算还要多大的新的空闲内存空间。

areasize = (remaining_size+PAGE_SIZE-1)/PAGE_SIZE;//看看需要多少页,不够一页也要算用一页。

ret = phys_to_virt(bdata->last_pos*PAGE_SIZE + offset +bdata->node_boot_start);//同上。

bdata->last_pos = start+areasize-1;//从新统计本次申请空闲空间结束位置的所在页号。

bdata->last_offset = remaining_size;//重新更新。

}

bdata->last_offset &= ~PAGE_MASK;//最后把last_offset转换成页内偏移量。

}else{如果对其值大于等于页大小,或者上次分配的内存的物理结束地址是按页对齐的,或是上次分配的内存的结束地址不在本次分配的起始空闲内存页start的前一页内。

bdata->last_pos = start + areasize - 1;

bdata->last_offset = size & ~PAGE_MASK;

ret = phys_to_virt(start * PAGE_SIZE + bdata->node_boot_start);

}//else里面的三条语句就不多说了。

for (i = start; i < start+areasize; i++)

if (unlikely(test_and_set_bit(i, bdata->node_bootmem_map)))BUG();//对本次的申请的空间的每页对应的页帧位码表中的每一位都置1,如果本身就是1的话,系统崩溃。

memset(ret, 0, size);//对申请的这段空间进行清零。

return ret;//返回申请到的虚拟地址。

}

好的,这样就过了一遍linux的内存分配的核心函数__alloc_bootmem_core。我们下一篇文章就会开始介绍有关内存管理的释放函数。

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

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

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值