boot memory allocator——自举内存分配器(五:停用bootmem)

来源:互联网 发布:淘宝店主阿希哥 编辑:程序博客网 时间:2024/05/21 06:40

在系统初始化进行到伙伴系统分配器能够承担内存管理的责任后,必须停用bootmem分配器,毕竟不能同时用两个分配器管理内存。在UMA和NUMA系统上,停用分别由free_all_bootmem和free_all_bootmem_node完成。在伙伴系统建立以后,特定于体系结构的初始化代码需要调用这两个函数。本文还是选择讨论UMA系统。

free_all_bootmem如下:

unsigned long __init free_all_bootmem(void){return free_all_bootmem_core(NODE_DATA(0));}
它实际上将工作委托给free_all_bootmem_core,对free_all_bootmem_core源代码的分析如下:

static unsigned long __init free_all_bootmem_core(pg_data_t *pgdat){struct page *page;unsigned long pfn;bootmem_data_t *bdata = pgdat->bdata;unsigned long i, count, total = 0;unsigned long idx;unsigned long *map; int gofast = 0;//gofast起一个标识的作用,当gofast等于1时就有可能一次性释放掉32个页面,gofast等于1的条件见下面BUG_ON(!bdata->node_bootmem_map);count = 0;/* first extant page of the node */pfn = PFN_DOWN(bdata->node_boot_start);//分配给该内存结点起始页的编号idx = bdata->node_low_pfn - pfn;//计算出该内存结点所占的内存总页数,包含空洞map = bdata->node_bootmem_map;/* Check physaddr is O(LOG2(BITS_PER_LONG)) page aligned */if (bdata->node_boot_start == 0 ||    ffs(bdata->node_boot_start) - PAGE_SHIFT > ffs(BITS_PER_LONG))//ffs()函数可以求出一个数中置1的最低位,PAGE_SHIFT为12,BITS_PER_LONG为32,所以上述条件的语义为:当内存结点的起始地址是0或者至少是按2的17次方对齐的,也就是说页号是按至少是32对齐的,这样对应的页帧位码表也是按一个字对齐的gofast = 1;//上述条件成立时,就可以一次性释放掉32个页面for (i = 0; i < idx; ) {unsigned long v = ~map[i / BITS_PER_LONG];//此处我们得明白map是一个指向unsigned long的指针,那么,举个例子来说,map[0]就表示一个unsigned long类型的变量,在内存中占据32位,又因为map是由bdata->node_bootmem_map赋值过来的,在bdata->node_bootmem_map中一位就可以表示一页的使用情况,所以map[0]是由连续的32个可以表示内存使用情况的位构成的if (gofast && v == ~0UL) {//当gofast=1也就是满足快速释放的基本条件并且原始位图中连续的32位都是0的情况下就可以一次性释放掉32页,注意上面有一个取反的运算int order;page = pfn_to_page(pfn);//#define pfn_to_page(pfn) virt_to_page(pfn_to_virt(pfn)),#define pfn_to_virt(pfn) __va((pfn) << PAGE_SHIFT),#define __va(x) ((void *)((unsigned long)(x) | 0xc0000000)),#define virt_to_page(addr) (mem_map + (((unsigned long)(addr)-PAGE_OFFSET) >> PAGE_SHIFT)),这一连串宏的作用是先通过物理页帧找到物理地址,然后通过物理地址找到虚拟地址,最后通过虚拟地址找到映射的页号count += BITS_PER_LONG;//由于一次释放了32页,所以count+32order = ffs(BITS_PER_LONG) - 1;//order的含义是对需要连续释放的页取以2为底的对数,如现在需要连续释放32页,将32取以2为底的对数得5,正好等于ffs(BITS_PER_LONG)-1__free_pages_bootmem(page, order);//释放页,page为释放的首个页的地址,order表示需要连续释放2的order次方个页,详细讨论见下文i += BITS_PER_LONG;//i往后走,继续完成外层循环page += BITS_PER_LONG;//page也需要往后走} else if (v) {//即使满足快速释放的基本条件(gofast=1),但是原始位图中连续的32位不全为0时,也只能一个页一个页的释放unsigned long m;page = pfn_to_page(pfn);for (m = 1; m && i < idx; m<<=1, page++, i++) {//通过这个循环就能将原始位图中(针对上面的32位)中位为0的页释放掉if (v & m) {count++;__free_pages_bootmem(page, 0);}}} else {//在原始位图中该连续的32位全为1,即都在使用中,没有页可以释放i += BITS_PER_LONG;//i往后走,继续完成外层循环}pfn += BITS_PER_LONG;//往后走,继续完成外层循环}total += count;//total记录释放的总页数,程序运行至此,就将位图中指示的空闲页全部释放了,下面还需释放位图本身占据的内存单元/* * Now free the allocator bitmap itself, it's not * needed anymore: */page = virt_to_page(bdata->node_bootmem_map);//将存储位图的内存区的首地址转换为页号count = 0;idx = (get_mapsize(bdata) + PAGE_SIZE-1) >> PAGE_SHIFT;//get_mapsize()在前面的文章中已经讨论过,此语句就是计算页帧位码表占了多少页for (i = 0; i < idx; i++, page++) {//然后通过此循环,从首页开始一页一页的释放__free_pages_bootmem(page, 0);count++;}total += count;bdata->node_bootmem_map = NULL;//既然存放页帧位码表的空间已经不存在了,我们就是这个指针变量指向nullreturn total;}
对free_pages_bootmem的源代码分析如下:

void fastcall __init __free_pages_bootmem(struct page *page, unsigned int order){if (order == 0) {//order=0,表示只释放一页__ClearPageReserved(page);//将struct page结构flags标志字中的PG_reserved标志位清除set_page_count(page, 0);//将page结构的_count位置0,使其可以被释放set_page_refcounted(page);////////////////////////不明白为什么再释放之前又将该页的_count设置回1__free_page(page);//#define __free_page(page) __free_pages((page), 0),_free_pages的分析见下文} else {//如果order不为0,就一次性释放2的order次方个连续页int loop;prefetchw(page);//由于是释放连续的页,所以可以先预取for (loop = 0; loop < BITS_PER_LONG; loop++) {struct page *p = &page[loop];if (loop + 1 < BITS_PER_LONG)prefetchw(p + 1);__ClearPageReserved(p);set_page_count(p, 0);}set_page_refcounted(page);__free_pages(page, order);}}

问题1:上面第6行,为什么在释放该页之前又将该页的_count设置回1,设置回1之后该页还能被释放吗?那么前面做的工作岂不都是白费!

问题2:当order=0时,每释放一个页之前都将该页的_count设置回1,当order非0时,仅仅将要释放的一连串的页的最后一页的_count设置回1,为什么?

希望谁可以指点一下,不甚感激。下面附上我没有弄明白的那个函数,set_page_refcounted:

static inline void set_page_refcounted(struct page *page){VM_BUG_ON(PageCompound(page) && PageTail(page));VM_BUG_ON(atomic_read(&page->_count));set_page_count(page, 1);}
对_free_pages源代码的分析如下:

fastcall void __free_pages(struct page *page, unsigned int order){if (put_page_testzero(page)) {//把  page 的计数原子性的减 1 ,并测试是否为 0 ,如果为 0 ,返回  true,否则返回  falseif (order == 0)free_hot_page(page);//把该页释放到该页所属内存node的内存页区的当前处理器的“热区”高数缓存内存中,后面的文章会详细讨论else__free_pages_ok(page, order);//否者就调用伙伴系统内存释放操作函数,后面的文章会详细讨论}}