mm/page_alloc.c
  纵观这个模块,实际上是buddy算法的核心所在.除了buddy算法的几个核心函数,
其余都是简单的再封装.

I) 全局量,概述

  从头到尾的看吧.首先是几个变量的声明,这些变量是lru表的关键组成部分:

nt nr_swap_pages; /*可用(空闲)的swap page 数量*/
int nr_active_pages;/*active_list  中的ram页面个数*/
int nr_inactive_dirty_pages; /*inactive_dirty_list 中ram页面的个数*/
pg_data_t *pgdat_list; /*node 表*/

struct list_head active_list;  /*像那些被频繁访问的页面*/
struct list_head inactive_dirty_list; /*修改过的,看起来最近不会被访问到
                                        的一般已经从进程的页表中脱离*/
  
  在分析mm/filemap.c的时候分析过lru. mm/memory.c中也稍有涉及,主要是从lru
表中恢复页面映射.
  这里再次用一个图表看一下lru队列和操作lru的几个关键函数的作用:

        mm->rss  *********************************
                                                |swap_out->try_to_swap_out
active_list->*******************************    |
                      /\               | refill_inactive_scan  
                       |page_launder  \ /      \ /
inactive_dirty_list->*****************************************************
                                      | page_launder 
                                     \ /  (wite back to disk if necessary)
zone_t->inactive_clean_pages->************* --- reclaim_page->直接使用
                                  |  kreclaimd->reclaim_page
                                 \ /
zone_t->free_area(buddy)->***********************

   这里除了定义lru的几个队列再无其他和lru相关的东西了,我也就是这样简单
提提.

  接下来是关于zone的一组参数:(没有什么好解释的)

/*zone 参数*/
static char *zone_names[MAX_NR_ZONES] = { "DMA", "Normal", "HighMem" };
static int zone_balance_ratio[MAX_NR_ZONES] = { 32, 128, 128, };
static int zone_balance_min[MAX_NR_ZONES] = { 10 , 10, 10, };
static int zone_balance_max[MAX_NR_ZONES] = { 255 , 255, 255, };




II)Buddy 算法核心
  最后开始理解最终要的buddy系统,为了说明白算法到底如何工作,我们先来引入
几个重要的概念:

1)某组page: 
     假设order是2,则page_idx是0-3的是一组,4-7是一组.组中最低地址的那个页
面代表这个组连接进area的list链表,并且组内其他page不会存在于其他任何area
的list。

2)组对(相关组):
  area中有两个重要的数据结构:
     (1)list : 属于此order(area)的pgae,或者page组的链表
     (2)*map : 每一位代表一对相关组(buddy)。所谓相关组就是可以合并成更高
               order的两个连续page组,我们也称之为相关组.
               
  map中的组对bit位的意义我们暂且不说,当释放时0代表这对相关组的其中一组还
在使用,无法合并,如果释放是1则代表可以合并。初始值是0,对应的list也为空。
  
  
  
  map中bit的意义对于理解buddy算法很有好处,可惜一直未能彻底搞明白,这次希
望运气好些.

  
  通过对初始化的分析看看map中bit位的变化:
1)初始为0,也代表配对page无法合并吗?(显然不是)list也为空.内存初始化调用所
有页面的释放函数,挂入area0 的list,对应位置1.
2)配对的page释放后对应位又变成0,从list摘出先释放的那个一齐放入area1.(如果
重复释放某个page,page组,就会出现严重错误)

3)分配的时候,从areaX的list中出队,mapbit从0变成1,如果这时释放刚分配的page,
1代表可以合并.如果另一个组也分配出去了,位图又变成0,代表释放的时候不能合并.
此时不会在分配这两个组了,因为list中这两个组都已经出队.

  所以,操作顺序,bitmap,和list相互配合完成buddy的管理.
  
  仔细考虑,总结一下,map中的bit含义其实是:
       0,相关组的状态一致
       1,相关组的状态不同
  (god,是个异或)
       
  所以分配的时候反转两次,释放的时候反转两次.在看上面的分析:
1)初始为0,连个页面都处于使用中. 释放其中一个的时候,bit为0,代表状态相同,即
都在使用中(我们是在释放啊),我们释放完成后当然无法合并.同时我们释放其中一个
后反转bit,bit变成1,代表现在状态不同了.

2)当配对的一个释放时,发现bit是1,代表状态不同.因为这在使用中,所以另一个已经
释放,故这个释放后就可以合并了.这就是为什么释放时bit为0代表可以合并.

3)请自己分析。


  
  
  Buddy算法的核心函数之一,__free_pages_ok:释放page组,根据bitmap判断buddy
page组的状态,合并buddy page组到更高的order.
/*
 * Buddy system. Hairy. You really aren't expected to understand this
 * (真的不期望你们理解这段代码---原作者)
 * Hint: -mask = 1+~mask
 */
static void FASTCALL(__free_pages_ok (struct page *page, unsigned long order));
static void __free_pages_ok (struct page *page, unsigned long order)
{
	unsigned long index, page_idx, mask, flags;
	free_area_t *area;
	struct page *base;
	zone_t *zone;

  //要释放的页面不能再有任何人使用了
	if (page->buffers)
		BUG();
  ..........//省略
  
  //清除ref,diry位,恢复其初始生命值
	page->flags &= ~((1<<PG_referenced) | (1<<PG_dirty));
	page->age = PAGE_AGE_START;
	
	zone = page->zone;

	mask = (~0UL) << order;  //  like: 111111110000,此order的mask
	base = mem_map + zone->offset;
	page_idx = page - base; 
	if (page_idx & ~mask) //~mask:  0000000001111,
		BUG();// page_idx 必须是2^order的整倍数, xxxxxxxx0000

	index = page_idx >> (1 + order); //index是指代表page_idx所指组对
                                  //的位图索引,所以是idx>>order>>1
                                                           
	area = zone->free_area + order;

	spin_lock_irqsave(&zone->lock, flags);

	zone->free_pages -= mask;  //-mask :00010000,2^order

	while (mask + (1 << (MAX_ORDER-1))) {// 11xxxx00+001000000=000xxx00
		struct page *buddy1, *buddy2;

		if (area >= zone->free_area + MAX_ORDER)
			BUG();
		if (!test_and_change_bit(index, area->map))
		//释放时如果为0,代表另一组还在使用中,并反转对应位
		/*
		 * the buddy page is still allocated.
		 */
			break;

		/*
		 * Move the buddy up one level.
		 */
		buddy1 = base + (page_idx ^ -mask);//-mask:00010000,疑惑取得另一个组
		buddy2 = base + page_idx;
		if (BAD_RANGE(zone,buddy1))
			BUG();
		if (BAD_RANGE(zone,buddy2))
			BUG();

		memlist_del(&buddy1->list);
		mask <<= 1; //11xxxx00->11xxx000,更大的一个order
		area++;
		index >>= 1; 
		page_idx &= mask;//新order下代表组对的页面索引
		                 //(清除一个bit,既,idx值小的那个组)
	}
	memlist_add_head(&(base + page_idx)->list, &area->free_list);

	spin_unlock_irqrestore(&zone->lock, flags);

	......
}



  Buddy系统的另一个核心函数,expand:当指定的order,low无页面可以分配的时候
从oredr为high的area分配,将high中一个页面组依次向低order的area拆分,一次一
半,直至order为low的那个area为止.,我们要order为1的页面组,从order为3的开
始分配, 8=4+2+(2),(2)代表我们所要的页面组.
static inline struct page * expand (zone_t *zone, struct page *page,
	 unsigned long index, int low, int high, free_area_t * area)
{
  //low :目标order     high: 从此order的area分配
  //page: order为high 的页面组
  //index:page组在order为high的area中的位图索引
	unsigned long size = 1 << high;

	while (high > low) {//分解到low
		if (BAD_RANGE(zone,page))
			BUG();
		area--; //lower area
		high--; //lower order
		size >>= 1;//size 减半
		memlist_add_head(&(page)->list, &(area)->free_list);//将拆分的头一个组
		                                                   //入低级别的area
		MARK_USED(index, high, area);//在此低级别的area中反转状态
		index += size;//低一个order的位图索引
		page += size; //留下同一对的后一个组分配给用户
	}
	if (BAD_RANGE(zone,page))
		BUG();
	return page;
}

  分析完expand后对于rmqueue应该不难了:
static struct page * rmqueue(zone_t *zone, unsigned long order)
{
	free_area_t * area = zone->free_area + order;
	unsigned long curr_order = order;
	struct list_head *head, *curr;
	unsigned long flags;
	struct page *page;

	spin_lock_irqsave(&zone->lock, flags); /*only __free_pages_ok   show_free_areas_core and this */
	do {//指定的order可能没有页面了,要依次向高order的area遍历
		head = &area->free_list;
		curr = memlist_next(head);

		if (curr != head) {//还有页面可以分配
			unsigned int index;
			page = memlist_entry(curr, struct page, list);
			if (BAD_RANGE(zone,page))
				BUG();
			memlist_del(curr);
			index = (page - mem_map) - zone->offset;
			MARK_USED(index, curr_order, area);//反转状态
			zone->free_pages -= 1 << order;

			page = expand(zone, page, index, order, curr_order, area);
			spin_unlock_irqrestore(&zone->lock, flags);

			set_page_count(page, 1);
			if (BAD_RANGE(zone,page))
				BUG();
			DEBUG_ADD_PAGE
			return page;	
		}
		//如果此order的area无页面就试试高一个order的area
		curr_order++;
		area++;
	} while (curr_order < MAX_ORDER);
	spin_unlock_irqrestore(&zone->lock, flags);

	return NULL;
}



III) __alloc_pages:基于zone的buddy系统分配的核心策略

  先看一个内部接口函数,按照指定的可回收页面(free+inactive clean)的水
位寻找合适的zone分配page:
static struct page * __alloc_pages_limit(zonelist_t *zonelist,
			unsigned long order, int limit, int direct_reclaim)
{  
//limit指定按照什么水位寻找zone,有三种标准,PAGES_MIN,PAGES_LOW,PAGES_HIGH
zone_t **zone = zonelist->zones;

	for (;;) {
		zone_t *z = *(zone++);
		unsigned long water_mark;

		if (!z)
			break;
		if (!z->size)
			BUG();

		/*
		 * We allocate if the number of free + inactive_clean
		 * pages is above the watermark.
		 */
		switch (limit) {
			default:
			case PAGES_MIN: //只要拥有z->pages_min这么多的空闲页面即可
				water_mark = z->pages_min;
				break;
			case PAGES_LOW:
				water_mark = z->pages_low;
				break;
			case PAGES_HIGH:
				water_mark = z->pages_high;
		}

		if (z->free_pages + z->inactive_clean_pages > water_mark) {
			struct page *page = NULL;
			/* If possible, reclaim a page directly. */
			if (direct_reclaim && z->free_pages < z->pages_min + 8)
				page = reclaim_page(z);
			/* If that fails, fall back to rmqueue. */
			if (!page)
				page = rmqueue(z, order);
			if (page)
				return page;
		}
	}

	/* Found nothing. */
	return NULL;

}
  这里说的可回收页面包括两个部分即:free_pages+inactive_clean_pages.

  __alloc_pages处于内核内存管理的最底层,无论slab,vmallc,kmalloc,mmap,brk
还是page cache,buffer都要通过__alloc_pages获取最基本的物理内存pages.
  linux执行这样一种内存管理策略:
  a)充分利用物理内存,建立各种cache,优化程序性能,减少磁盘操作.这一点和win
dows系统不同,windows系统中总是有很多内存空闲,即便是进行了大量的磁盘操作后.
而linux中真正空闲的物理内存几乎就看不到.

  b)保证有足够的潜在物理内存(页面),可以立即加以回收,或称潜在可分配页面.通
过内核的守护进程kswapd,bdflush,kreclaimd的定期处理,加上每次内存分配对系统
的调整,即通过__alloc_pages所遇到的各种内存分配压力,不断的调整守护进程的工
作方向,保证系统拥有足够的潜在可回收内存.
  
  先看看对内存页面有些什么样的保有量要求:
  1)可分配页面的保有量要求:inactive_clean+free pages(in buddy pages)
  
  系统的期望值是freepages.high + inactive_target / 3,inactive_target就是
min((memory_pressure >> INACTIVE_SHIFT),num_physpages / 4)).可见期望的保
有量有动态的因素在内.
  现在的保有量是nr_free_pages() + nr_inactive_clean_pages();
  mm/vmscan.c中的函数free_shortage,计算期望的可分配页面和现实之差距.如果
保有量合格,但看zone中的inbuddy free pages是比期望值少.只要有一个保有量不
合格,就必须立即加以调整.free_shortage请自己阅读.

  2)潜在可分配页面的保有量要求:(buddyfree+inactiveclean+inactive_dirty)
  期望保有量:freepages.high+inactive_target
  现存量:
      nr_free_pages()+nr_inactive_clean_pages()+nr_inactive_dirty_pages.
      

所做分析已注入代码:
/*
 * 基于区的buddy 系统的核心策略
 * This is the 'heart' of the zoned buddy allocator:
 */
struct page * __alloc_pages(zonelist_t *zonelist, unsigned long order)
{
	zone_t **zone;
	int direct_reclaim = 0;
	unsigned int gfp_mask = zonelist->gfp_mask;
	struct page * page;

	/*
	 * Allocations put pressure on the VM subsystem.
	 */
	memory_pressure++;

	/*
	 * (If anyone calls gfp from interrupts nonatomically then it
	 * will sooner or later tripped up by a schedule().)
	 *
	 * We are falling back to lower-level zones if allocation
	 * in a higher zone fails.
	 */

	/*
	 * Can we take pages directly from the inactive_clean
	 * list?
	 */
	/* PF_MEMALLOC 代表是为管理目的而请求分配pages */
	if (order == 0 && (gfp_mask & __GFP_WAIT) &&
			!(current->flags & PF_MEMALLOC))
		direct_reclaim = 1;

	/*
	 * If we are about to get low on free pages and we also have
	 * an inactive page shortage, wake up kswapd.
	 */
	if (inactive_shortage() > inactive_target / 2 && free_shortage())
		wakeup_kswapd(0);/*用各种办法保持潜在可分配页面的数量*/
	/*
	 * If we are about to get low on free pages and cleaning
	 * the inactive_dirty pages would fix the situation,
	 * wake up bdflush.
	 */
	else if (free_shortage() && nr_inactive_dirty_pages > free_shortage()
			&& nr_inactive_dirty_pages >= freepages.high)
		wakeup_bdflush(0);/*加速将buffer中的数据写入磁盘的过程*/

try_again:
	/*
	 * 首先,选取那些拥有许多的空闲内存的zone
	 * We allocate free memory first because it doesn't contain
	 * any data ... DUH!
	 */
	 /* 这轮分配只看绝对空闲页的水位*/
	zone = zonelist->zones;
	for (;;) {
		zone_t *z = *(zone++);
		if (!z)
			break;
		if (!z->size)
			BUG();

		if (z->free_pages >= z->pages_low) {//空闲页面保有量合格
			page = rmqueue(z, order);     
			if (page)
				return page;
		} else if (z->free_pages < z->pages_min &&
					waitqueue_active(&kreclaimd_wait)) {
				wake_up_interruptible(&kreclaimd_wait); 
				/* kreclaimd:从zone_t->inactive_clean_list 队列中回收页面 */
		}
	}


	 /* If there is a lot of activity, inactive_target
	  * will be high and we'll have a good chance of
	  * finding a page using the HIGH limit.
	  */
   /*既然找不到空闲页面较多的zone,就找inactive_clean页面很
    *丰富的zone试试
    */
	page = __alloc_pages_limit(zonelist, order, PAGES_HIGH, direct_reclaim);
	if (page)
		return page;

	/*
	 * 还不行就找inactive_clean页面还行的zone
	 * zone->pages_low < free + inactive_clean
	 * When the working set is very large and VM activity
	 * is low, we're most likely to have our allocation
	 * succeed here.
	 */
	page = __alloc_pages_limit(zonelist, order, PAGES_LOW, direct_reclaim);
	if (page)
		return page;

	/*
	 * 没有zone 的空闲页面(buddy+inactive clean)能够满足需求了
	 * 
	 * We wake up kswapd, in the hope that kswapd will
	 * resolve this situation before memory gets tight.
	 *
	 * We also yield the CPU, because that:
	 * - gives kswapd a chance to do something
	 * - slows down allocations, in particular the
	 *   allocations from the fast allocator that's
	 *   causing the problems ...
	 * - ... which minimises the impact the "bad guys"
	 *   have on the rest of the system
	 * - if we don't have __GFP_IO set, kswapd may be
	 *   able to free some memory we can't free ourselves
	 */
	wakeup_kswapd(0); /* 参数0, 代表不睡眠*/
	/* kswapd -->致力于保持潜在可分配页面的保有量*/
	if (gfp_mask & __GFP_WAIT) {
		__set_current_state(TASK_RUNNING);
		current->policy |= SCHED_YIELD;
		schedule();
	}

	/*
	 * After waking up kswapd, we try to allocate a page
	 * from any zone which isn't critical yet.
	 *
	 * 也许我们不能等Kswapd 完成他的工作
	 * 先以更低的水位要求试试
	 */
	page = __alloc_pages_limit(zonelist, order, PAGES_MIN, direct_reclaim);
	if (page)
		return page;


	/*
	 * Damn, we didn't succeed.
	 * 
	 */
  /* 对于普通进程还有情况我们可以 考虑到*/
	if (!(current->flags & PF_MEMALLOC)) { 
	
    if (order > 0 && (gfp_mask & __GFP_WAIT)) {
		/* 我们在处理 higher order 的分配,并且可以等待 */
			zone = zonelist->zones;
    	/*将dirty页面写入磁盘*/
			current->flags |= PF_MEMALLOC; //page_launder 也可能分配页面
			page_launder(gfp_mask, 1); //这个进程作为调用环境,提升其
			current->flags &= ~PF_MEMALLOC;//优先级避免递归运行到这里
			for (;;) {
				zone_t *z = *(zone++);
				if (!z)
					break;
				if (!z->size)
					continue;
				while (z->inactive_clean_pages) {
					/*补充空闲页面到buddy*/
					struct page * page;
					/* Move one page to the free list. */
					page = reclaim_page(z); 
					if (!page)
						break;
					__free_page(page); //释放到buddy
					/*也许就有连续页面了*/
					/* Try if the allocation succeeds. */
					page = rmqueue(z, order); //再试试high_order 的分配
					if (page)
						return page;
				}
			}
		}


		/*
		 * We have to do this because something else might eat
		 * the memory kswapd frees for us and we need to be
		 * reliable. 
		 */
		if ((gfp_mask & (__GFP_WAIT|__GFP_IO)) == (__GFP_WAIT|__GFP_IO)) {
			/* 如果容许io操作,并可以等待,唤醒kswapd
			  * 并等待kswapd 恢复内存的平衡状态
			  */
			wakeup_kswapd(1); /* 参数1, 代表可以阻塞*/
			memory_pressure++;
			if (!order) //* 主意:我们在higher order 时不'again',
 		                         // 因为,可能kswapd 永远( *ever* )不能为我们
		                         // 释放出一个大的连续区域.
				goto try_again;
		/*
		 * If __GFP_IO isn't set, we can't wait on kswapd because
		 * kswapd just might need some IO locks /we/ are holding ...
		 *
		 * SUBTLE: The scheduling point above makes sure that
		 * kswapd does get the chance to free memory we can't
		 * free ourselves...
		 */
		} else if (gfp_mask & __GFP_WAIT) {
		       //不能进行io的情况下代替kswapd做些
		       //不进行io 努力
			try_to_free_pages(gfp_mask);
			memory_pressure++;
			if (!order)
				goto try_again;
		}

	}

	/*
	 * Final phase: allocate anything we can!
	 *
	 * Higher order allocations, GFP_ATOMIC allocations and
	 * recursive allocations (PF_MEMALLOC) end up here.
	 *
	 * Only recursive allocations can use the very last pages
	 * in the system, otherwise it would be just too easy to
	 * deadlock the system...
	 */
	zone = zonelist->zones;
	for (;;) {
		zone_t *z = *(zone++);
		struct page * page = NULL;
		if (!z)
			break;
		if (!z->size)
			BUG();

		/*
		 * SUBTLE: direct_reclaim is only possible if the task
		 * becomes PF_MEMALLOC while looping above. This will
		 * happen when the OOM killer selects this task for
		 * instant execution...(看英文吧)
		 */
		if (direct_reclaim) {
			page = reclaim_page(z);
			if (page)
				return page;
		}

		/* XXX: is pages_min/4 a good amount to reserve for this? */
		if (z->free_pages < z->pages_min / 4 &&
				!(current->flags & PF_MEMALLOC))
			continue;
		page = rmqueue(z, order);
		if (page)
			return page;
	}

	/* No luck.. */
	printk(KERN_ERR "__alloc_pages: %lu-order allocation failed.\n", order);
	return NULL;
}
   
  与内存分配有关的函数还有:
unsigned long get_zeroed_page(int gfp_mask)
void __free_pages(struct page *page, unsigned long order)
void free_pages(unsigned long addr, unsigned long order)
  另外还有几个用于统计内存压力的函数:
  unsigned int nr_free_pages (void)
  unsigned int nr_inactive_clean_pages (void)
  unsigned int nr_free_buffer_pages (void)
  unsigned int nr_free_highpages (void)
  这些函数较为简单,不再分析.


IV)zone 初始化的相关函数
	 重点来看看2.4的zonelist.不知道你是否有疑问,其实2.4的zonelist用处不是
想象的那么大,还没有成熟,或者说还没有写完.
   2.4的pgdata,zonelist的关系如下图:
                        +---------+        zone_list
   pgdat->node_zonelist |zone_list|-----> +--------+  
                        +---------+       | normal?+
                           256          +  dma?  +
                        +---------+       |  high? +
                                          +--------+
   zone_list中第一个zone的类型是由zone_list在node_zonelist中的索引所决定
的,即由GFP_XXX决定.
   可惜看GFP的定义,根本不能够有256,这是其一. 其次,pgdat中的zone_list都
是指向本node的zone,而不是依据距node的距离排列,这就起不到node分区的本意.看
函数 alloc_pages(mm/numa.c),是在pgdat之间遍历而已,并无优先级的考虑.
   再看mm/page_alloc.c中的build_zonelists:
static inline void build_zonelists(pg_data_t *pgdat)
{
	int i, j, k;

	for (i = 0; i < NR_GFPINDEX; i++) {//依次设置pgdat->node_zonelist[GPF]
		zonelist_t *zonelist;
		zone_t *zone;

		zonelist = pgdat->node_zonelists + i;
		memset(zonelist, 0, sizeof(*zonelist));

		zonelist->gfp_mask = i;
		j = 0;
		k = ZONE_NORMAL;
		if (i & __GFP_HIGHMEM)
			k = ZONE_HIGHMEM;
		if (i & __GFP_DMA)
			k = ZONE_DMA;

		switch (k) {
			default:
				BUG();
			/*
			 * fallthrough:(注意没有break,若请求dma无可替代)
			 */
			case ZONE_HIGHMEM:
				zone = pgdat->node_zones + ZONE_HIGHMEM;
				if (zone->size) {
#ifndef CONFIG_HIGHMEM
					BUG();
#endif
					zonelist->zones[j++] = zone;
				}
			case ZONE_NORMAL:
				zone = pgdat->node_zones + ZONE_NORMAL;
				if (zone->size)
					zonelist->zones[j++] = zone;
			case ZONE_DMA:
				zone = pgdat->node_zones + ZONE_DMA;
				if (zone->size)
					zonelist->zones[j++] = zone;
		}
		zonelist->zones[j++] = NULL;
	} 
}2.6的内核对比一下,2.6中结构变成:
                          +---------+        zone_list
     pgdat->node_zonelist |zone_list|-----> +--------+  
                          +---------+       | nodes*3+
                              3           +        +
                          +---------+       |        +
                          |  n.d.h  |       +--------+
不详细分析2.6中的相关代码了.

  最后剩下函数free_area_init_core,在分析mm/memory.c的时候曾详细讨论过他
设置mem_map的方式,请参考理解free_area_init_core对mem_map的初始化,这里简
单讨论对zone和buddy的初始化,就不列出详细的代码了:
/*
 * 设置一个节点的zone:
 *   - 所有的页面标记为保留 reserved
 *   - page 中所以内存队列为空
 *   - 清空区内buddy系统位图
 *  nid    节点号                        pgdat  节点
 *  gmap   全局mem map                   zones_size 节点区大小数组
 *  zone_start_paddr 节点开始的物理地址
 *  zholes_size 节点空洞大小数组
 *  lmem_map 节点本地mem map数组
 */
void __init free_area_init_core(int nid, pg_data_t *pgdat, 
    struct page **gmap,	unsigned long *zones_size , 
    unsigned long zone_start_paddr, unsigned long *zholes_size, 
    struct page *lmem_map)
{
 /* NON_NUMA:(like i386) pgdat:contig_page_data,gmap:&mem_map, lmem_map:0
  * NUMA:(like i64-sn1): pgdat:NOD_PGDAT , gmap:&discard, lmem_map:0
  *                      mem_map 恒定为PAGE_OFFSET
  */
	struct page *p;
	unsigned long i, j;
	unsigned long map_size;
	unsigned long totalpages, offset, realtotalpages;
	unsigned int cumulative = 0;

	/*计算总页面数目*/
  ....
	/*总页面数减去每个区的空洞大小*/
	...
	/*
	 * 对lmem_map和全局mem_map的初始化,参考mm/memory.c的分析
	 */
	map_size = (totalpages + 1)*sizeof(struct page);
	if (lmem_map == (struct page *)0) {
		lmem_map = (struct page *) alloc_bootmem_node(pgdat, map_size);
		lmem_map = (struct page *)(PAGE_OFFSET + 
			MAP_ALIGN((unsigned long)lmem_map - PAGE_OFFSET));
	}
	*gmap = pgdat->node_mem_map = lmem_map;
	pgdat->node_size = totalpages;
	pgdat->node_start_paddr = zone_start_paddr;
	pgdat->node_start_mapnr = (lmem_map - mem_map);
	/*
	 * 初始状态下所有的页面都是保留的 - 空闲页面在早期初始化完成之后
	 * 由 free_all_bootmem() 一次性统一释放.
	 */
	for (p = lmem_map; p < lmem_map + totalpages; p++) {
		set_page_count(p, 0);
		SetPageReserved(p);
		init_waitqueue_head(&p->wait);
		memlist_init(&p->list);
	}

	offset = lmem_map - mem_map;	
	for (j = 0; j < MAX_NR_ZONES; j++) {//初始化zone,3个
		zone_t *zone = pgdat->node_zones + j;
		unsigned long mask;
		unsigned long size, realsize;

		realsize = size = zones_size[j];
		if (zholes_size)
			realsize -= zholes_size[j];

		printk("zone(%lu): %lu pages.\n", j, size);
		zone->size = size;
		........//初始化各个变量,省略
		zone->zone_mem_map = mem_map + offset;
		zone->zone_start_mapnr = offset;
		zone->zone_start_paddr = zone_start_paddr;

		/*
		 * 初始化本区内的page 的虚拟地址和所属区
		 */
		for (i = 0; i < size; i++) {
			struct page *page = mem_map + offset + i