linux内存模型之buddy(伙伴)系统三从bootmem到buddy的过渡

===================================
本文系本站原创,欢迎转载!

转载请注明出处:http://blog.csdn.net/gdt_A20

===================================

 

摘要:

     buddy基本模型建立起来后,bootmem也将要退出历史舞台了,boomem管理的内存都要过渡到buddy中,同样高端部分内存也要加入进去,因为boomem

毕竟也只是管理了低端部分。以前面的释放函数为基础,理解起来过渡阶段的代码也就不难了。


一、mem_init 函数

先来看一下mem_init函数,

arch/arm/mm/init.c

mm_init-->mem_init()

本着去其糟粕,取其精华的原则,挑重点的看吧,

max_mapnr   = pfn_to_page(max_pfn + PHYS_PFN_OFFSET) - mem_map;
//PHYS_PFN_OFFSET被定义成
#define PHYS_PFN_OFFSET (PHYS_OFFSET >> PAGE_SHIFT)
PHYS_PFN_OFFSET是在直接映射中的第一个页,max_pfn是在bootmem分配器时候提到的总内存页数,
这里再给出一下:

max_pfn = max_high - PHYS_PFN_OFFSET;

这样算回来,就得到了最大内存在数组中的标号,
减去数组起始地址得到总共有多少页。
下一个函数

/* this will put all unused low memory onto the freelists */
free_unused_memmap(&meminfo);  
不明思议,放所有的没有使用的内存到空闲链表,
具体到会检查所有的bank,以MAX_ORDER_NR_PAGES为结尾,
调用free_memmap(prev_bank_end, bank_start);释放每个bank的内存到空闲链表,

重点函数

totalram_pages += free_all_bootmem();
释放bootmem中所有空闲页到buddy分配器,完成过渡

free_highpages();
释放所有高端内存页到buddy;

for_each_bank(i, &meminfo) {
		struct membank *bank = &meminfo.bank[i];
		unsigned int pfn1, pfn2;
		struct page *page, *end;

		pfn1 = bank_pfn_start(bank);
		pfn2 = bank_pfn_end(bank);

		page = pfn_to_page(pfn1);
		end  = pfn_to_page(pfn2 - 1) + 1;

		do {
			if (PageReserved(page))
				reserved_pages++;
			else if (!page_count(page))
				free_pages++;
			page++;
		} while (page < end);
	}

根据meminfo记录的bank信息,检查页的状态,后面会打印详细的内存状态信息,这里就不罗列出来了!


二、分别详细的看一下上面提到的几个函数。

1、free_unused_memmap 函数

/*
 * The mem_map array can get very big.  Free the unused area of the memory map.
 */
static void __init free_unused_memmap(struct meminfo *mi)
{
	unsigned long bank_start, prev_bank_end = 0;
	unsigned int i;

	/*
	 * This relies on each bank being in address order.
	 * The banks are sorted previously in bootmem_init().
	 */
	for_each_bank(i, mi) {
		struct membank *bank = &mi->bank[i];

		bank_start = bank_pfn_start(bank);

#ifdef CONFIG_SPARSEMEM
		/*
		 * Take care not to free memmap entries that don't exist
		 * due to SPARSEMEM sections which aren't present.
		 */
		bank_start = min(bank_start,
				 ALIGN(prev_bank_end, PAGES_PER_SECTION));
#else
		/*
		 * Align down here since the VM subsystem insists that the
		 * memmap entries are valid from the bank start aligned to
		 * MAX_ORDER_NR_PAGES.
		 */
		bank_start = round_down(bank_start, MAX_ORDER_NR_PAGES);
#endif
		/*
		 * If we had a previous bank, and there is a space
		 * between the current bank and the previous, free it.
		 */
		if (prev_bank_end && prev_bank_end < bank_start)
			free_memmap(prev_bank_end, bank_start);

		/*
		 * Align up here since the VM subsystem insists that the
		 * memmap entries are valid from the bank end aligned to
		 * MAX_ORDER_NR_PAGES.
		 */
		prev_bank_end = ALIGN(bank_pfn_end(bank), MAX_ORDER_NR_PAGES);
	}

#ifdef CONFIG_SPARSEMEM
	if (!IS_ALIGNED(prev_bank_end, PAGES_PER_SECTION))
		free_memmap(prev_bank_end,
			    ALIGN(prev_bank_end, PAGES_PER_SECTION));
#endif
}

这个写的真的很详细,不忍心再写了。主要判断bank间的空隙,标记他们,

free_memmap调用mark_bootmem标记page为unused,放到后面释放掉.

2.free_all_bootmem()函数

/**
 * free_all_bootmem - release free pages to the buddy allocator
 *
 * Returns the number of pages actually released.
 */
unsigned long __init free_all_bootmem(void)
{
unsigned long total_pages = 0;
bootmem_data_t *bdata;


list_for_each_entry(bdata, &bdata_list, list)   
total_pages += free_all_bootmem_core(bdata);
return total_pages;
}

进入所有节点进行释放,返回释放总页数;

mm/bootmem.c中:

static unsigned long __init free_all_bootmem_core(bootmem_data_t *bdata)
{
	int aligned;
	struct page *page;
	unsigned long start, end, pages, count = 0;

	if (!bdata->node_bootmem_map)                        //bit位图不存在,退出
		return 0;

	start = bdata->node_min_pfn;
	end = bdata->node_low_pfn;

	/*
	 * If the start is aligned to the machines wordsize, we might
	 * be able to free pages in bulks of that order.
	 */
	aligned = !(start & (BITS_PER_LONG - 1));

	bdebug("nid=%td start=%lx end=%lx aligned=%d\n",
		bdata - bootmem_node_data, start, end, aligned);

	while (start < end) {                                    //够32bit,一个long长度的,按32释放,不然后面单独释放
		unsigned long *map, idx, vec;

		map = bdata->node_bootmem_map;                   //得到bit位图
		idx = start - bdata->node_min_pfn;               //最低页号

        /*
         *  找出对应位图所在位置(先找出对应的long型数),
         *  比如如果是33,那么33/32 == 1,那么就在map[1]中,map[0]表示到32,然后将其取反 
         */
                vec = ~map[idx / BITS_PER_LONG];                 
                                                                 
                /* 必须保证对其,结束页大于开始页+32 */
		if (aligned && vec == ~0UL && start + BITS_PER_LONG < end) {
			int order = ilog2(BITS_PER_LONG);        //得到order,也就是2的多少次方,此时都是空闲,那么以这个为基数进行释放

                        /* 根据pfn找到对应的页,进行处理 */
			__free_pages_bootmem(pfn_to_page(start), order);  
			count += BITS_PER_LONG;                 //计数器循环+32
		} else {
			unsigned long off = 0;

                       /* 如果不满足都为空,那么找到空的页,单独释放掉,此时order为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;                        //每一次过渡32个页
	}

        /* 将位图部分同样释放到buddy */
	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);

	bdebug("nid=%td released=%lx\n", bdata - bootmem_node_data, count);

	return count;
}

看一下他们调用的公共函数:__free_pages_bootmem(pfn_to_page(start), order);

mm/page_alloc.c

/*
 * permit the bootmem allocator to evade page validation on high-order frees
 */
void __meminit __free_pages_bootmem(struct page *page, unsigned int order)
{
    if (order == 0) {
        __ClearPageReserved(page);                //清除该页的保留标志
        set_page_count(page, 0);                  //引用计数清0
        set_page_refcounted(page);                 
        __free_page(page);                        //释放该页到冷热页缓存
    } else {                                      //order不是0用于释放32的整数倍页
        int loop;

        prefetchw(page);
        for (loop = 0; loop < BITS_PER_LONG; loop++) {
            struct page *p = &page[loop];         //以该页为起始循环设置对应page

            if (loop + 1 < BITS_PER_LONG)
                prefetchw(p + 1);
            __ClearPageReserved(p);               //清除其预留标志
            set_page_count(p, 0);                 //设置count
        }

        set_page_refcounted(page);
        __free_pages(page, order);                //order不是0的释放
    }
}

这里存在两个函数,一个是__free_page(page),另一个是__free_pages(page, order),

分别看一下,

mm/page_alloc.c

void __free_pages(struct page *page, unsigned int order)
{
    if (put_page_testzero(page)) {
        if (order == 0)
            free_hot_cold_page(page, 0);                      //order为0的时候释放到冷热页
        else
            __free_pages_ok(page, order);                     //否则释放到伙伴系统
    }
}
而__free_page(page)被定义为

linux/gfp.h

#define  __free_page(page) __free_pages((page), 0)
同样是调用了__free_pages,

__free_pages前文已经说过,这里不再赘述;
3.free_highpages
  下面看一下高端内存的填补;
 arch/arm/mm/init.c
static void __init free_highpages(void)
{
#ifdef CONFIG_HIGHMEM                                                       //高端内存部分是可以配置的
	unsigned long max_low = max_low_pfn + PHYS_PFN_OFFSET;              //得到高端内存地址
	struct memblock_region *mem, *res;

	/* set highmem page free */
	for_each_memblock(memory, mem) {                                     //memory,用于描述一些高端内存信息,
		unsigned long start = memblock_region_memory_base_pfn(mem);  //有的时候高端内存只不过是一些寄存器存在那里,
		unsigned long end = memblock_region_memory_end_pfn(mem);     //得到基地址和结束地址 

		/* Ignore complete lowmem entries */
		if (end <= max_low)                                          //忽略不足高端最低配置部分
			continue;

		/* Truncate partial highmem entries */
		if (start < max_low)                                        //规范最低地址
			start = max_low;

		/* Find and exclude any reserved regions */
		for_each_memblock(reserved, res) {                         //去除中间保留部分,如果有保留,分成几部分释放
			unsigned long res_start, res_end;

			res_start = memblock_region_reserved_base_pfn(res);
			res_end = memblock_region_reserved_end_pfn(res);

			if (res_end < start)
				continue;
			if (res_start < start)
				res_start = start;
			if (res_start > end)
				res_start = end;
			if (res_end > end)
				res_end = end;
			if (res_start != start)
				totalhigh_pages += free_area(start, res_start,
							     NULL);
			start = res_end;
			if (start == end)
				break;
		}

		/* And now free anything which remains */
		if (start < end)
			totalhigh_pages += free_area(start, end, NULL);    //释放掉
	}
	totalram_pages += totalhigh_pages;
#endif
}


 
  

 
  
static inline int free_area(unsigned long pfn, unsigned long end, char *s)
{
	unsigned int pages = 0, size = (end - pfn) << (PAGE_SHIFT - 10);

	for (; pfn < end; pfn++) {
		struct page *page = pfn_to_page(pfn);                     //释放对应的页
		ClearPageReserved(page);
		init_page_count(page);
		__free_page(page);
		pages++;
	}

	if (size && s)
		printk(KERN_INFO "Freeing %s memory: %dK\n", s, size);

	return pages;
}


 
  

三、总结

        结合前面的分配释放,描述了从bootmem到buddy的过度,主要是bootmem时候内存的释放,以及高端内存的填充;

Thanks

 
  
 
  
 
  
 
  
 
 

你可能感兴趣的:(Kernel,linux,kernel,札记)