您的位置:首页 > 其它

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

2012-08-17 15:10 423 查看
在系统初始化进行到伙伴系统分配器能够承担内存管理的责任后,必须停用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+32
			order = 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;//既然存放页帧位码表的空间已经不存在了,我们就是这个指针变量指向null

	return 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,否则返回  false
		if (order == 0)
			free_hot_page(page);//把该页释放到该页所属内存node的内存页区的当前处理器的“热区”高数缓存内存中,后面的文章会详细讨论
		else
			__free_pages_ok(page, order);//否者就调用伙伴系统内存释放操作函数,后面的文章会详细讨论
	}
}
内容来自用户分享和网络整理,不保证内容的准确性,如有侵权内容,可联系管理员处理 点击这里给我发消息
标签: 
相关文章推荐