所谓bootmem分配器,是内核页表创建后的临时使用的内存分配器,它将在后面的mem_init函数初始化伙伴系统时被伙伴系统取代,即它是一个过渡手段。理解bootmem,一方面有利于对内存管理尤其内核启动阶段阶段内存管理的理解,另一方面也有利于伙伴系统的理解。
在函数bootmem_init中,对每一个node执行bootmem_init_node,bootmem_init_node又对该node的每一个bank执行map_memory_bank即创建内存映射后,接下来的内容是创建bootmem分配器,如下:
static void __init bootmem_init_node(int node, struct meminfo *mi,
unsigned longstart_pfn, unsigned long end_pfn)
{
unsigned longboot_pfn;
unsigned intboot_pages;
pg_data_t *pgdat;
int i;
/*
* Map the memory banks for this node.
*/
for_each_nodebank(i,mi, node) {
structmembank *bank = &mi->bank[i];
if(!bank->highmem)
map_memory_bank(bank);
}
boot_pages = bootmem_bootmap_pages(end_pfn - start_pfn);
boot_pfn = find_bootmap_pfn(node, mi, boot_pages);
node_set_online(node);
pgdat = NODE_DATA(node);
init_bootmem_node(pgdat, boot_pfn, start_pfn, end_pfn);
for_each_nodebank(i, mi, node) {
struct membank*bank = &mi->bank[i];
if(!bank->highmem)
free_bootmem_node(pgdat,bank_phys_start(bank), bank_phys_size(bank));
}
reserve_bootmem_node(pgdat, boot_pfn << PAGE_SHIFT,
boot_pages << PAGE_SHIFT,BOOTMEM_DEFAULT);
}
下面是详述:
1、 boot_pages = bootmem_bootmap_pages(end_pfn - start_pfn);
bootmem分配器是用bitmap即比特位图的方式标识每一个物理页的占用未占用的情况,这里就是计算这么些物理页需要多少个字节的bitmap,如128M物理内存,相当于32768个物理页,需要32768/8 = 4096个字节的bitmap,即4K的空间(一页)用于bitmap;
2、 boot_pfn = find_bootmap_pfn(node, mi, boot_pages);
找出内核镜像之后(即_end之后)的第一个物理页地址,找它是为了确定这个bitmap应该放在哪,它应该放在内核镜像之后;
3、 node_set_online(node);
设置这个node为online,这是个软件的伎俩,知道意思即可;
4、 pgdat = NODE_DATA(node);
取得该node的pg_data_t数据结构变量,每个node都有一个pg_data_t变量描述;今后会经常用到;
5、 init_bootmem_node(pgdat, boot_pfn, start_pfn, end_pfn);
它将直接调用init_bootmem_core (pgdat->bdata, freepfn, startpfn, endpfn);
这是直接创建bootmem分配器了,第一参数pgdat->bdata是bootmem_data_t类型,这个就是bootmem分配器成员:
下面是确定bootmem分配器的实体:bitmap的虚拟地址在哪里,参数mapstart就是前面第2步得到的boot_pfn即bitmap所在物理页地址,把它转换成虚拟地址;
bdata->node_bootmem_map = phys_to_virt(PFN_PHYS(mapstart));
下面是确定bootmem分配器的起始物理页地址、结尾物理地址
bdata->node_min_pfn = start;
bdata->node_low_pfn = end;
把这个bootmem分配器加入到链表bdata_list中,链表头bdata_list就在本文件定义(这步无需重视);
link_bootmem(bdata);
下面是计算出该bootmem分配器的bitmap占用多少字节mapsize;然后根据字节数长度把bitmap全都置成1,意为当前所有页都是不可用(bootmem分配器,原理是用每一比特标识每一页的使用情况,置1为已占用,置0为可用)
mapsize = bootmap_bytes(end - start);
memset(bdata->node_bootmem_map, 0xff, mapsize);
6、 for_each_nodebank(i, mi, node) {
structmembank *bank = &mi->bank[i];
if (!bank->highmem)
free_bootmem_node(pgdat,bank_phys_start(bank), bank_phys_size(bank));
}
对该node的每个bank,只要是低端内存,调用函数free_bootmem_node;
void__init free_bootmem_node(pg_data_t *pgdat, unsigned long physaddr,
unsigned long size)
{
unsigned long start, end;
kmemleak_free_part(__va(physaddr), size);
start = PFN_UP(physaddr);
end = PFN_DOWN(physaddr + size);
mark_bootmem_node(pgdat->bdata, start, end,0, 0);
}
首先由start和end获取物理起始、结尾地址start和end;然后调用函数mark_bootmem_node:
mark_bootmem_node(pgdat->bdata, start, end, 0, 0);
这个函数的用途是把该bootmem分配器标识的相关内存(由start和end标识是哪些内存),进行标记,标记为可用或已用(通过标记node的boomtown的node_bootmem_map成员为1或0,为1为已用,为0为可用)
staticint __init mark_bootmem_node(bootmem_data_t *bdata,
unsigned longstart, unsigned long end,
int reserve, intflags)
{
unsigned long sidx, eidx;
bdebug("nid=%td start=%lx end=%lxreserve=%d flags=%x\n",
bdata - bootmem_node_data, start,end, reserve, flags);
BUG_ON(start < bdata->node_min_pfn);
BUG_ON(end > bdata->node_low_pfn);
sidx = start - bdata->node_min_pfn;
eidx = end - bdata->node_min_pfn;
if (reserve)
return __reserve(bdata, sidx, eidx,flags);
else
__free(bdata, sidx, eidx);
return 0;
}
由物理页地址和该node的bootmem分配器的首尾物理页地址的差值,获取索引sidx和eidx,最后通过__reserve或__free设置某块内存区域为已占用(reserve)或已可用(free);无需再细看__reserve和__free的实现,它们都是bitmap相应的位操作;
要注意这里的意义:执行到这里,该node的低端物理内存均已在bootmem分配器中标识为可用!
7、reserve_bootmem_node(pgdat,boot_pfn << PAGE_SHIFT,
boot_pages << PAGE_SHIFT,BOOTMEM_DEFAULT);
reserve_bootmem_node和上面的free_bootmem_node功能相反,它是设置某块内存为已占用,这里把内核代码段之后(_end之后)的第一个物理页地址boot_pfn之后的一页(4K)物理内存设置为不可用,实际上这一页内容就是bootmem的bitmap表本身,所以不能被占用;
至此,除了bitmap本身所在的物理页为已占用,其他内存还是可用的;
8、从bootmem_init_node还是返回,执行到:
if (node == 0)
reserve_node_zero(NODE_DATA(node));
这里是把内核页表(0xc0004000到0xc0008000)以及内核镜像([_stext:_end])部分所处的物理页面也在bootmem分配器的bitmap中标识为已占用;
至此,bitmap本身、内存页表本身、内核镜像本身所处的物理页已被标识为已占用,其他物理内存可用;到这里,bootmem分配器已创建好并且把不应被占用的物理页面保护起来了,接下来看看bootmem分配器的使用:
对于bootmem分配器的使用,重在知道原理和大概记住都用在了哪些地方,具体细节无需细究,因为伙伴系统初始化后bootmem就不用了,没意义:
主要会用到alloc_bootmem_node函数和alloc_bootmem_low_pages函数等,它们最终使用的都是同一个函数alloc_bootmem_core(释放函数仍是free_bootmem_node,不过初始阶段都是在分配,基本没有用到释放),这个函数是从bootmem分配器的bitmap位图中申请内存,过程非常繁琐,网上也有不少关于它的注释和讲解,我个人认为无需仔细分析,因为bootmem分配器本身效率就比较低,又是临时使用的分配手段,重点看看都有哪些地方需要申请内存,至少有如下几处:
devicemaps_init中的为中断向量创建映射时需要映射在高端地址,中断向量本身需申请物理内存(1页),又因为所映射中断向量长度不足1M,不足一个段所以需要二级映射,创建二级页表又需申请1页物理内存;
此外其他IO寄存器映射中,不足1M的映射均需创建二级映射的,都需申请相应数量物理内存;
kmap_init中高端内存映射(当前这里marvell不存在高端内存),需要申请相关创建二级页表所需物理内存;
paging_init中最后需1页物理内存专门用作一个全0页empty_zero_page;
最早的初始化后面伙伴系统函数free_area_init_node中,调用函数alloc_node_mem_map为每一个物理内存页创建其struct page管理结构,所需申请的内存;
在最终start_kernel调用函数mm_init--->mem_init时,将把该node中所有可用的内存转交给伙伴系统:
for_each_online_node(node) {
pg_data_t*pgdat = NODE_DATA(node);
free_unused_memmap_node(node, &meminfo);
if(pgdat->node_spanned_pages != 0)
totalram_pages+= free_all_bootmem_node(pgdat);
}
free_unused_memmap_node函数意思是,逐个bank遍历,/*释放每个bank之间的间隙(它们之间的间隙是下一个bank的描述部分),对于这里的marvell,只有1个bank,不存在此情况;
free_all_bootmem_node是把整个bootmem分配器掌管的内存包括bitmap本身统统都释放到伙伴系统中去。下面是该函数的注释片段:
while (start < end) {
unsignedlong *map, idx, vec;
map =bdata->node_bootmem_map;
idx = start- bdata->node_min_pfn;
vec =~map[idx / BITS_PER_LONG];
/*end比start大32页时,释放内存到伙伴系统*/
if (aligned&& vec == ~0UL && start + BITS_PER_LONG < end) {
intorder = ilog2(BITS_PER_LONG);
__free_pages_bootmem(pfn_to_page(start),order);
count+= BITS_PER_LONG;
}
/*end比start大不足32页时,释放内存到冷热页框*/
else {
unsignedlong off = 0;
while(vec && off < BITS_PER_LONG) {
if(vec & 1) {
page= pfn_to_page(start + off);
__free_pages_bootmem(page,0);
count++;
}
vec>>= 1;
off++;
}
}
start +=BITS_PER_LONG;
}
/*最后把bootmem分配器的位图本身所占页也释放掉,释放给冷热页框*/
page =virt_to_page(bdata->node_bootmem_map);
pages =bdata->node_low_pfn - bdata->node_min_pfn;
pages =bootmem_bootmap_pages(pages);
count += pages;
while (pages--)
__free_pages_bootmem(page++,0);