boot memory allocator——自举内存分配器(四:内存释放)
在bootmem阶段,内核提供了free_bootmem函数来释放内存。它需要两个参数:需要释放的内存区的起始地址和长度。不出意外,NUMA系统上等价函数的名称为free_bootmem_node,它需要一个额外的参数来指定结点。
static void __init free_bootmem_core(bootmem_data_t *bdata, unsigned long addr,unsigned long size){unsigned long sidx, eidx;unsigned long i;/* * round down end of usable mem, partially free pages are * considered reserved. */BUG_ON(!size);//如果释放空内存,系统崩溃BUG_ON(PFN_DOWN(addr + size) > bdata->node_low_pfn);//需要释放的空间的最后位置的绝对页号不能释放超过本节点范围内的内存空间,否则系统崩溃if (addr < bdata->last_success)bdata->last_success = addr;//如果addr<bdata->last_success,那么作了这一次空间释放以后,就需要更新该结点中上一次成功分配内存的位置/* * Round up the beginning of the address. */sidx = PFN_UP(addr) - PFN_DOWN(bdata->node_boot_start);//此处使用PFN_UP(addr)是为了计算开始地址的全新页,假设addr是在0~4kb的地址范围的话,其实真正在0号页的位置,但是如果通过上面的运算,求出来的就是1号页的位置。为什么这样做呢?因为如果起始地址不是按页对齐的,如果直接释放,就会把前面不属于这个内存node的空间也释放了,而后面为什么是PFN_DOWN(bdata->node_boot_start)而不是PFN_UP(bdata->node_boot_start),因为如果后面是PFN_UP(bdata->node_boot_start)的话,就会把当前内存结点最后一页中的后面那一部分不属于该内存结点的空间给释放掉了。这样就计算出了新页和原来的起始地址所在页的偏移页数。eidx = PFN_DOWN(addr + size - bdata->node_boot_start);//计算出要释放空间的结束位置相对于该内存节点的起始位置的偏移页数for (i = sidx; i < eidx; i++) {//这里是对要释放的页对应的页帧位码表的位清零if (unlikely(!test_and_clear_bit(i, bdata->node_bootmem_map)))//如果发现页帧位码表的那些位已经为0的话,系统就崩溃了BUG();}}该过程隐藏了一些风险,如果也包含在两个不同的内存区,那么连续释放这些内存区,却无法释放该页。包含页的前一半和后一半的内存区在间隔一段时间后分别被释放,分配器无法了解到该页是否不再使用,因此也无法释放。该页的状态就一直保持为“使用中”,尽管事实上不是这样。尽管如此,由于free_bootmem很少使用,这也不是大问题。系统初始化期间分配的大多数内存区都用于基本的数据结构,在内核运行的所有时间都需要使用,因此无需释放。