为什么要使用bootmem分配器,内存管理不是有buddy系统和slab分配器吗?由于在系统初始化的时候需要执行一些内存管理,内存分配的任务,这个时候buddy系统,slab分配器等并没有被初始化好,此时就引入了一种内存管理器bootmem分配器在系统初始化的时候进行内存管理与分配,当buddy系统和slab分配器初始化好后,在mem_init()中对bootmem分配器进行释放,内存管理与分配由buddy系统,slab分配器等进行接管。
bootmem分配器使用一个bitmap来标记物理页是否被占用,分配的时候按照第一适应的原则,从bitmap中进行查找,如果这位为1,表示已经被占用,否则表示未被占用。为什么系统运行的时候不使用bootmem分配器呢?bootmem分配器每次在bitmap中进行线性搜索,效率非常低,而且在内存的起始端留下许多小的空闲碎片,在需要非常大的内存块的时候,检查位图这一过程就显得代价很高。bootmem分配器是用于在启动阶段分配内存的,对该分配器的需求集中于简单性方面,而不是性能和通用性。
一、bootmem分配器核心数据结构
bootmem核心数据结构bootmem_data:
typedef struct bootmem_data {
unsigned long node_boot_start;
unsigned long node_low_pfn;
void *node_bootmem_map;
unsigned long last_offset;
unsigned long last_pos;
unsigned long last_success;
} bootmem_data_t;
系统内存的中每一个结点(如果是内存是UMA,就只有一个这样的结构)都有一个bootmem_data_t结构,它含有bootmem分配器给结点分配内存时所需的信息。
1、node_boot_start是这个结点内存的起始地址(如果内存是UMA的话,为0);
2、node_low_pfn是低端内存最后一个page的页帧号;
3、node_bootmem_map指向内存中bitmap所在的位置;
4、last_offset是分配的最后一个页内的偏移,如果该页完全使用,则offset为0;
5、last_pos是分配最后一个页帧号;
6、last_success是最后一次成功分配的位置。
二、bootmem分配器初始化
是在init_bootmem_core函数对bootmem分配器进行初始化的。
static unsigned long __init init_bootmem_core (pg_data_t *pgdat,
unsigned long mapstart, unsigned long start, unsigned long end)
{
bootmem_data_t *bdata = pgdat->bdata;
unsigned long mapsize = ((end - start)+7)/8; /*映射的字节数*/
pgdat->pgdat_next = pgdat_list;
pgdat_list = pgdat;
mapsize = (mapsize + (sizeof(long) - 1UL)) & ~(sizeof(long) - 1UL);
bdata->node_bootmem_map = phys_to_virt(mapstart << PAGE_SHIFT);
bdata->node_boot_start = (start << PAGE_SHIFT);
bdata->node_low_pfn = end;
memset(bdata->node_bootmem_map, 0xff, mapsize); //初始化所有内存都保留
return mapsize;
}
linux系统初始化过程中通过init_bootmem_core(NODE_DATA(0), min_low_pfn, 0, max_low_pfn)来调用这个函数初始化bootmem分配器。其中min_low_pfn是临时页表后的第一个可用页框,max_low_pfn是低端内存结束的页框。
前面说的bootmem分配器核心数据结构bootmem_data_t是内嵌在pg_data_t数据结构中(也就是节点的描述符中)。内存中的节点描述符是通过链表pgdat_list链起来的,如果