释放比较简单,先描述释放
free_bootmem 和free_bootmem_node是提供的两个API接口
free_bootmem_node 提供了内存节点,另一需要遍历所有节点
void __init free_bootmem_node(pg_data_t *pgdat, unsigned long physaddr,
unsigned long size)
{
free_bootmem_core(pgdat->bdata, physaddr, size);
}
void __init free_bootmem(unsigned long addr, unsigned long size)
{
bootmem_data_t *bdata;
list_for_each_entry(bdata, &bdata_list, list)
free_bootmem_core(bdata, addr, size);
}
free_bootmem_core 是真正干活的函数
释放内存,需要两个参数,释放内存的起始地址 addr 和要释放的内存的大小size。
实际情况也是如此。
static void __init free_bootmem_core(bootmem_data_t *bdata, unsigned long addr,
unsigned long size)
bootmem分配器是最先适配的算法,用位图来描述某个页面是否使用。这个函数的核心部分是
for (i = sidx; i < eidx; i++) {
if (unlikely(!test_and_clear_bit(i, bdata->node_bootmem_map)))
BUG();
}
简单的说,就是计算出sidx的含义是 以node_boot_start 为起始页面(第0页),addr所在的页面是
第几个页面。 eidx同样道理,(addr+size)所在页面相对于起始页面,是第几页。
将sidx和eidx之间的页面对应的位置,在位图中清零。
----------------------------------------------------------------------------------------------
其次是分配内存。稍微复杂一些。
下面的API接口
alloc_bootmem
alloc_bootmem_pages
alloc_bootmem_low
alloc_bootmem_low_pages
这些API函数只需要一个参数,size 即大小。需要分配多大的空间。
这个_pages 后缀的意思是指数据的对齐方式是按页对齐。
_low后缀的意思是数据从ZONE_DMA内存域开始分配内存。需要DMA内存时才使用带low的分配函数。
这些函数是__alloc_bootmem前端函数
alloc_bootmem
=======> __alloc_bootmem ====> __alloc_bootmem_nopanic====> __alloc_bootmem_core
alloc_bootmem_pages
alloc_bootmem_low
=====>__alloc_bootmem_low =================> __alloc_bootmem_core
alloc_bootmem_low_pages
__alloc_bootmem_core 比较命苦,实际干活的函数都是它。这个函数比较长。
void * __init
__alloc_bootmem_core(struct bootmem_data *bdata, unsigned long size,
unsigned long align, unsigned long goal, unsigned long limit)
一共五个参数
第一个参数比较简单,是老朋友了。
第二个参数比较简单,是API 接口的唯一一个参数,由接口调用者指定
第三个参数是align,对齐方式。 对齐方式有两种 PAGE_SIZE 和SMP_CACHE_BYTES.
SMP_CACHE_BYTES 在大多数的体系结构中使数据理想的置于L1高速缓存
PAGE_SIZE 按照页对齐。API带后缀pages的都是按照PAGE_SiZE对齐。如alloc_bootmem_pages。
第四个参数 goal ,目的地。一般是ZONE_NORMAL内存,API后缀带low的表示分配到ZONE_DMA.
第五个参数 分配的上限,不能超过这个地址。
areasize = (size + PAGE_SIZE-1) / PAGE_SIZE;
areasize表示实际要分配几个页面。
preferred 表示从哪个起始位置开始查找 areasize个可用的页面。
preferred = bdata->last_success - node_boot_start;
preferred = PFN_DOWN(ALIGN(preferred, align));
这个preferred的存在是为了加快查找。bootmem_data数据结构中有个
成员变量为last_success。
eidx = end_pfn - PFN_DOWN(node_boot_start);
end_pfn 如limit为0,则为bdata->node_low_pfn,
否则,选择 limit 和bdata->node_low_pfn 小的哪个。、
然后就到了很复杂的循环体了。
循环体的工作就是找areasize 个连续页面即位图对应位置为0,页面可用,
从preferred 开始找。如果找到最后了,即大于eidx了,还是没找到。并且
preferred大于0,那么从零再次开始找。
如果找到了,记录下其实位置。
start = i;
bdata->last_success = PFN_PHYS(start) + node_boot_start;//记录下成功的物理地址,用于下次查找。
if (align < PAGE_SIZE &&
bdata->last_offset && bdata->last_pos+1 == start)
如果不是按页分配,就是说分配的单位比较小,并且,上次分配的没有分配完整页面,而本次选的start页面
是上次选择的下一页面,可以考虑将上次分配页面的剩余空间利用起来。
offset = ALIGN(bdata->last_offset, align);
remaining_size = PAGE_SIZE - offset ;//上次分配页面的剩余可用空间。
if (size < remaining_size) 如果本次分配的size比较小,小于remaining_size,
OK,不要浪费,就用上次剩下的边角料。
bdata->last_offset = offset + size;
ret = phys_to_virt(bdata->last_pos * PAGE_SIZE +
offset + node_boot_start);
else {
remaining_size = size - remaining_size;
areasize = (remaining_size + PAGE_SIZE-1) / PAGE_SIZE;
ret = phys_to_virt(bdata->last_pos * PAGE_SIZE +
offset + node_boot_start);
bdata->last_pos = start + areasize - 1;
bdata->last_offset = remaining_size;
}
size大于remaining_size,还是要充分利用边角料。注意维护ret bdata->last_pos bdata->last_offset。