Archive for the ‘内存管理’ category

Linux页框分配函数的实现(3)-快速分配函数

2 3 月, 2012

关于页框分配函数的实现,前文中已经对主体实现函数和慢速分配函数作了简单说明。这两个函数主要的功能即根据系统内存实时的使用情况及时调整分配限制,并再次调用快速分配函数get_page_from_freelist()。

该函数可以看作是伙伴系统的前置函数,它通过传递进来的分配标志和分配阶,并结合系统当时的内存使用情况来判断是否可以进行内存分配。如果可以分配,那么进行实际的内存分配工作,既调用那些基于伙伴算法而实现的内存分配函数。

3.快速分配函数

static struct page *
get_page_from_freelist(gfp_t gfp_mask, nodemask_t *nodemask, unsigned int order,
		struct zonelist *zonelist, int high_zoneidx, int alloc_flags,
		struct zone *preferred_zone, int migratetype)
{
	struct zoneref *z;
	struct page *page = NULL;
	int classzone_idx;
	struct zone *zone;
	nodemask_t *allowednodes = NULL;/* zonelist_cache approximation */
	int zlc_active = 0;		/* set if using zonelist_cache */
	int did_zlc_setup = 0;		/* just call zlc_setup() one time */

	classzone_idx = zone_idx(preferred_zone);

现在开始遍历指定zonelist上的zone。在通过for_each_zone_zonelist_nodemask()遍历时,该函数会过滤掉那些索引值大于high_zoneidx的zone。比如当前high_zoneidx所指定的zone为ZONE_NORMAL,那么当前遍历的zone只能为ZONE_DMA或ZONE_NORMAL类型,而不能是ZONE_HIGH,这些关系判断的依据即为这些类型对应的索引值。因为内存管理区的类型是通过枚举类型来描述的,因此索引值也就是这些类型对应的枚举值。

zonelist_scan:
	for_each_zone_zonelist_nodemask(zone, z, zonelist,
						high_zoneidx, nodemask) {
		if (NUMA_BUILD && zlc_active &&
			!zlc_zone_worth_trying(zonelist, z, allowednodes))
				continue;
		if ((alloc_flags & ALLOC_CPUSET) &&
			!cpuset_zone_allowed_softwall(zone, gfp_mask))
				goto try_next_zone;

即便当前zone的索引小于high_zoneidx,也不能急于分配内存,还要检查这个内存管理区是空闲的页框是否充足。这个过程通过上述两个if语句来完成。接下来,如果分配标志中设置了ALLOC_NO_WATERMARKS,即表明此刻不再考虑分配水位线。否则就要分析此刻内存的水位线。

通过分配标志从水位线数组中获得当前的水位线mark,再传入zone_watermark_ok()判断在当前的水位线下是否可以分配内存。如果可以分配内存则跳入try_this_zone。

		BUILD_BUG_ON(ALLOC_NO_WATERMARKS < NR_WMARK); 		if (!(alloc_flags & ALLOC_NO_WATERMARKS)) { 			unsigned long mark; 			int ret; 			mark = zone->watermark[alloc_flags & ALLOC_WMARK_MASK];
			if (zone_watermark_ok(zone, order, mark,
				    classzone_idx, alloc_flags))
				goto try_this_zone;

			if (zone_reclaim_mode == 0)
				goto this_zone_full;

如果运行到此处,说明此刻空闲内存不足,那么通过zone_reclaim()进行内存回收,回收的情况通过ret来描述。如果结果是ZONE_RECLAIM_NOSCAN,说明并没有进行回收,那么直接尝试下一个zone;如果结果是ZONE_RECLAIM_FULL,说明虽然进行了回收但是并没有回收到;默认的情况则是没有回收到足够多的内存。后两种情况均跳入this_zone_full处。

			ret = zone_reclaim(zone, gfp_mask, order);
			switch (ret) {
			case ZONE_RECLAIM_NOSCAN:
				/* did not scan */
				goto try_next_zone;
			case ZONE_RECLAIM_FULL:
				/* scanned but unreclaimable */
				goto this_zone_full;
			default:
				/* did we reclaim enough */
				if (!zone_watermark_ok(zone, order, mark,
						classzone_idx, alloc_flags))
					goto this_zone_full;
			}
		}

如果跳到此标号处说明可以在当前zone上分配内存,随即调用buffered_rmqueue()进入伙伴算法。

try_this_zone:
		page = buffered_rmqueue(preferred_zone, zone, order,
						gfp_mask, migratetype);
		if (page)
			break;

跳到此处说明当前zone的空闲内存不足,那么标记它。这样下次分配时直接将其忽略。

this_zone_full:
		if (NUMA_BUILD)
		        zlc_mark_zone_full(zonelist, z);

此处说明当前zone上的空闲内存不足,则需要在其他zone上尝试分配。

try_next_zone:
		if (NUMA_BUILD && !did_zlc_setup && nr_online_nodes > 1) {
			allowednodes = zlc_setup(zonelist, alloc_flags);
			zlc_active = 1;
			did_zlc_setup = 1;
		}
	}

此时,遍历zone的循环结束。如果第一次循环结束后page仍未空,则进行第二次分配,即跳入zonelist_scan重新遍历。当第二次分配结束后不管结果如何均返回。循环的次数由alc_active控制。

	if (unlikely(NUMA_BUILD && page == NULL && zlc_active)) {
		/* Disable zlc cache for second zonelist scan */
		zlc_active = 0;
		goto zonelist_scan;
	}
	return page;
}

至此,快速分配函数分析完毕。

Linux页框分配函数的实现(2)-慢速内存分配

28 2 月, 2012

2. 慢速分配函数

进入慢速分配函数后,先检查所请求的分配阶是否超过了MAX_ORDER。如果指定了GFP_THISNODE标志后,则不能继续进行慢速内存分配,因为该标志指明了内存不能进行回收,因此直接跳到nopage处的代码。

在经历一系列的参数检查之后,该函数通过调用wake_all_kswapd()唤醒每个zone所属node中的kswapd守护进程。这个守护进程负责换出很少使用的页,以提高目前系统可以用的空闲页框。

在kswapd交换进程被唤醒之后,该函数开始尝试新一轮的分配。它首先通过gfp_to_alloc_flags()对分配标志进行调整,稍微降低分配标准以便这次调用get_page_from_freelist()有可能分配到内存。

static inline struct page *
__alloc_pages_slowpath(gfp_t gfp_mask, unsigned int order,
        struct zonelist *zonelist, enum zone_type high_zoneidx,
        nodemask_t *nodemask, struct zone *preferred_zone,
        int migratetype)
{
        const gfp_t wait = gfp_mask & __GFP_WAIT;
        struct page *page = NULL;
        int alloc_flags;
        unsigned long pages_reclaimed = 0;
        unsigned long did_some_progress;
        struct task_struct *p = current;

        if (order >= MAX_ORDER) {
                WARN_ON_ONCE(!(gfp_mask & __GFP_NOWARN));
                return NULL;
        }

        if (NUMA_BUILD && (gfp_mask & GFP_THISNODE) == GFP_THISNODE)
                goto nopage;

restart:
        wake_all_kswapd(order, zonelist, high_zoneidx);
        alloc_flags = gfp_to_alloc_flags(gfp_mask);
        page = get_page_from_freelist(gfp_mask, nodemask, order, zonelist,
                        high_zoneidx, alloc_flags & ~ALLOC_NO_WATERMARKS,
                        preferred_zone, migratetype);
        if (page)
                goto got_pg;

如果page不为空,则说明内存申请成功,否则继续进行慢速内存分配。如果设置了ALLOC_NO_WATERMARKS标志,那么此时会忽略水印,并此时进入__alloc_pages_high_priority()。该函数内部会至少会再调用一次get_page_from_freelist(),如果设置了__GFP_NOFAIL标志,则不断的循环等待并尝试进行内存分配。

rebalance:
        if (alloc_flags & ALLOC_NO_WATERMARKS) {
                page = __alloc_pages_high_priority(gfp_mask, order,
                                zonelist, high_zoneidx, nodemask,
                                preferred_zone, migratetype);
                if (page)
                        goto got_pg;
        }

如果没有设置__GFP_WAIT,即wait为0,则不继续进行内存分配,直接跳到nopage处。如果PF_MEMALLOC被设置,也就是说调用内存分配函数着本身就是内存回收进程,则直接跳到nopage处。

        if (!wait)
                goto nopage;

        if (p->flags & PF_MEMALLOC)
                goto nopage;

        if (test_thread_flag(TIF_MEMDIE) && !(gfp_mask & __GFP_NOFAIL))
                goto nopage;

到目前为止,分配函数已经尝试好几次页框分配。如果现在仍未分配到请求的内存,那么接下来将进入一个比较耗时的阶段。内核通过将很少使用的页换出到磁盘上,以便在物理内存中有更多的空闲页框。这个过程可能会产生阻塞,也就是说会产生睡眠,因此它比较耗时。

__alloc_pages_direct_reclaim()的作用就是先通过try_to_free_pages()回收一些最近很少用的页,将其写回磁盘上的交换区,以便在物理内存中腾出更多的空间。接着,内核会再次调用get_page_from_freelist()尝试分配内存。

        page = __alloc_pages_direct_reclaim(gfp_mask, order,
                                        zonelist, high_zoneidx,
                                        nodemask,
                                        alloc_flags, preferred_zone,
                                        migratetype, &did_some_progress);
        if (page)
                goto got_pg;

如果内核进行了上述的回收和重新分配的过程后,仍未分配成功,既did_some_progress为0,那么此时内核不的不考虑是否发生了OOM(out of memory)。如果当前请求内存的进程发生了OOM,也就是说该进程试图拥有过多的内存,那么此时内核会调用OOM killer杀死它。并且跳转到restart处,重新进行内存分配。

        if (!did_some_progress) {
                if ((gfp_mask & __GFP_FS) && !(gfp_mask & __GFP_NORETRY)) {
                        if (oom_killer_disabled)
                                goto nopage;
                        page = __alloc_pages_may_oom(gfp_mask, order,
                                        zonelist, high_zoneidx,
                                        nodemask, preferred_zone,
                                        migratetype);
                        if (page)
                                goto got_pg;

                        if (order > PAGE_ALLOC_COSTLY_ORDER &&
                                                !(gfp_mask & __GFP_NOFAIL))
                                goto nopage;

                        goto restart;
                }
        }

此时再次判断是否要重新进行一次内存申请。如果有这个必要,那么等待写操作完成后再次跳到rebalance处重试。

        pages_reclaimed += did_some_progress;
        if (should_alloc_retry(gfp_mask, order, pages_reclaimed)) {
                congestion_wait(BLK_RW_ASYNC, HZ/50);
                goto rebalance;
        }

页框分配函数结束时候一般有两种情况,其中之一即为分配失败,并没有得到所需页框,因此打印一些内存分配失败的信息。

nopage:
        if (!(gfp_mask & __GFP_NOWARN) && printk_ratelimit()) {
                printk(KERN_WARNING "%s: page allocation failure."
                        " order:%d, mode:0x%x\n",
                        p->comm, order, gfp_mask);
                dump_stack();
                show_mem();
        }
        return page;

另一种情况,也就是得到了所需页框,那么直接返回这组页框的首页框描述符。

got_pg:
        if (kmemcheck_enabled)
                kmemcheck_pagealloc_alloc(page, order, gfp_mask);
        return page;

}

通过上述的过程可以看到,页框分配函数__alloc_pages()会多次尝试进行分配内存。而具体的页框分配操作是在get_page_from_freelist()中完成的,它根据伙伴算法分配所需大小的页框。

Linux页框分配函数的实现(1)-主体分配函数

11 1 月, 2012

内核中有六个基本的页框分配函数,它们内部经过封装,最终都会调用alloc_pages_node()。这个函数的参数比alloc_pages()多了一个nid,它用来指定节点id,如果nid小于0,则说明在当前节点上分配页框。正确获取到节点id后,接下来调用__alloc_pages()。

static inline struct page *alloc_pages_node(int nid, gfp_t gfp_mask, unsigned int order)
{
        if (nid < 0)
                nid = numa_node_id();

        return __alloc_pages(gfp_mask, order, node_zonelist(nid, gfp_mask));
}

__alloc_pages()第三个参数根据nid和gfp_mask得到适当的zonelist链表,该过程通过node_zonelist()完成。该函数的实现比较简单,其中NODE_DATA()根据nid返回对应的内存节点描述符,而gfp_zonelist()根据flags标志选取对应的内存管理区链表。其实node_zonelist()就是根据flags在相应内存节点的node_zonelists数组中选择一个何时的内存管理区链表zonelist。

static inline int gfp_zonelist(gfp_t flags)
{
        if (NUMA_BUILD && unlikely(flags & __GFP_THISNODE))
                return 1;

        return 0;
}

由于node_zonelists数组的元素个数最大为2,因此gfp_zonelist()返回0或者1。如果flags中设置了__GFP_THISNODE并且NUMA被设置,则表明使用当前节点对应的zonelist,返回1。否则使用备用zonelist,也就是说当本地节点中zone不足时,在其他节点中申请页框。

static inline int gfp_zonelist(gfp_t flags)
{
        if (NUMA_BUILD && unlikely(flags & __GFP_THISNODE))
                return 1;

        return 0;
}

__alloc_pages()内部再次封装__alloc_pages_nodemask()。

static inline struct page *
__alloc_pages(gfp_t gfp_mask, unsigned int order, struct zonelist *zonelist)
{
        return __alloc_pages_nodemask(gfp_mask, order, zonelist, NULL);
}

1. 主体分配函数

现在进入__alloc_pages_nodemask(),它作为页框分配函数的核心部分。该函数可以通过get_page_from_freelist()快速分配所请求的内存,但是大多数情况下调用该函数都会失败,因为通常物理内存的使用情况都比较紧张,这一点从其后if语句中的unlikely就可以看出。

struct page *
__alloc_pages_nodemask(gfp_t gfp_mask, unsigned int order,struct zonelist *zonelist, nodemask_t *nodemask)
{
        enum zone_type high_zoneidx = gfp_zone(gfp_mask);
        struct zone *preferred_zone;
        struct page *page;
        int migratetype = allocflags_to_migratetype(gfp_mask);

        gfp_mask &= gfp_allowed_mask;

        lockdep_trace_alloc(gfp_mask);

        might_sleep_if(gfp_mask & __GFP_WAIT);

        if (should_fail_alloc_page(gfp_mask, order))
                return NULL; 

        if (unlikely(!zonelist->_zonerefs->zone))
                return NULL;            

        first_zones_zonelist(zonelist, high_zoneidx, nodemask, &preferred_zone);
        if (!preferred_zone)
                return NULL;

        page = get_page_from_freelist(gfp_mask|__GFP_HARDWALL, nodemask, order,
                        zonelist, high_zoneidx, ALLOC_WMARK_LOW|ALLOC_CPUSET,
                        preferred_zone, migratetype);
        if (unlikely(!page))
                page = __alloc_pages_slowpath(gfp_mask, order,
                                zonelist, high_zoneidx, nodemask,
                                preferred_zone, migratetype);

        trace_mm_page_alloc(page, order, gfp_mask, migratetype);
        return page;
}

首先,gfp_zone()根据gfp_mask选取适当类型的zone。在经过几项参数检查后,该函数通过zonelist->_zonerefs->zone判断zonelist是否为空,既至少需要一个zone可用。接着根据一开始选取的zone类型high_zoneidx,通过first_zones_zonelist()确定优先分配内存的内存管理区。

如果一切顺利,将会进入get_page_from_freelist(),这个函数可以看作是伙伴算法的前置函数,它通过分配标志和分配阶判断是否能进行此次内存分配。如果可以分配,则它进行实际的内存分配工作,既利用伙伴算法进行分配内存。否则,进入__alloc_pages_slowpath(),此时内核需要放宽一些分配条件,或回收一些系统的内存,然后再调用几次get_page_from_freelist()以申请所需内存。

请求页框API简介

4 1 月, 2012

在用户态下程序中,我们可以通过malloc()动态申请内存空间。在内核空间中,专门有一个内核子系统处理对连续页框的内存分配请求,这个内核子系统即为管理区页框分配器(zoned page frame allocator)。该分配器包含六个专门用于分配页框的API,这些API都是基于伙伴算法而实现的,因此这些API申请的页框数只能为2的整数幂大小。

内存分配器API

1.alloc_pages()

该宏用来分配2的order次方个连续的页框,如果申请成功返回第一个所分配页框的描述符地址,申请失败的话返回NULL。

#define alloc_pages(gfp_mask, order) \
                alloc_pages_node(numa_node_id(), gfp_mask, order)

2.alloc_page()

该函数用来分配一个单独的页框,它可以看作是alloc_pages()当order等于0时的特殊情况。

#define alloc_page(gfp_mask) alloc_pages(gfp_mask, 0)

3.__get_free_pages()

通过该函数可以申请长为2的order次方大小的连续页框,但是它返回的是这段连续页框中第一个页所对应的线性地址。从源码中可以看出,该函数内部仍然调用了alloc_pages函数,并利用page_address函数将页描述符地址转换为线性地址。

unsigned long __get_free_pages(gfp_t gfp_mask, unsigned int order)
{
        struct page *page;

        VM_BUG_ON((gfp_mask & __GFP_HIGHMEM) != 0);

        page = alloc_pages(gfp_mask, order);
        if (!page)
                return 0;
        return (unsigned long) page_address(page);
}

4.__get_free_page()

该宏可以看作是__get_free_pages函数的特殊情况,它用于申请一个单独的页框。

#define __get_free_page(gfp_mask) \
        __get_free_pages((gfp_mask),0)

5.get_zeroed_page()

该函数用来获取一个填满0的页框,其中__GFP_ZERO参数用来体现这一点。

unsigned long get_zeroed_page(gfp_t gfp_mask)
{
        return __get_free_pages(gfp_mask | __GFP_ZERO, 0);
}

6.__get_dma_pages()

该宏获得的页框用于DMA操作。

#define (gfp_mask, order) \
                __get_free_pages((gfp_mask) | GFP_DMA,(order))

请求页框的标志

从上述几个分配器API中可以看到,除了用于指示请求页框大小的order参数外,还包括一组标志gfp_mask,它指明了如何寻找空闲的页框。下面仅说明几个常见的分配标志。

__GFP_DMA:该标志指明只能从ZONE_DMA内存管理区获得页框。

__GFP_HIGHMEM:如果该标志被设置,则按照ZONE_HIGHMEM,ZONE_NORMAL和ZONE_DMA的请求顺序获得页框,既首先在ZONE_HIGHMEME区请求所需大小的页框,如果该区无法满足请求页框的大小,则再向ZONE_DMA区发出请求。如果该标志没有被设置,则按照默认的ZONE_NORMAL和ZONE_DMA内存管理区的顺序获取页框。

__GFP_ZERO:如果设置了该标志,那么所申请的页框必须被填满0。

API关系图

本文所介绍的这几个API本质上都调用了alloc_pages(),而alloc_pages()又在其内部调用了alloc_pages_node(),它们之间的关系如下图所示:

从图中可以看出,alloc_pages_node()是所有分配器API的核心函数。

物理内存管理中的基本数据结构

29 12 月, 2011

Linux内核在管理内存时将物理内存从逻辑上划分为节点(node),内存管理区(zone),页框(frame page)三级结构。物理内存先被划分为内存节点,每个节点关联一个CPU,各个节点又被划分几个内存管理区,在一个内存管理区中则是页框。页框是内存管理的基本单位,它可以存放任何种类的数据。不过,由于实际中计算机硬件的制约,部分页框的使用受到了限制,内核将具有相同性质的页框进行分类组织,即形成内存管理区。为了兼容NUMA架构的计算机,内核又引入了节点这个概念,每个CPU对应一个节点。

1.page结构

内核使用page结构体描述一个物理页框,该结构也称为页描述符,每个物理页框都关联一个这样的结构体。值得注意的是,page结构仅用来描述一个页框的属性,它不包含该页框中的任何数据。此外,还应该区分页框大小和page结构的大小,页框大小通常为4KB,而page结构的大小即为sizeof(struct page)。

内核在定义page结构时使用了许多联合体,这样做的目的是保证page结构尽可能的小。虽然每个page结构体占很少内存,但是由于实际系统中页框总数量巨大,因此所有页框对应的page结构所占用的内存量也很庞大。下面仅对该结构中的部分字段进行介绍。

flags:它是用来描述页框状态的标志位,更重要的是该字段的高位存放着该页框所关联的页框号、节点内管理区号以及节点号。

__count:表示该页的引用计数,如果该页为-1,表示页框空闲;如果该字段的值为N(N>=0),则说明有N+1个进程正在使用该页。
系统中所有的页描述符都放在mem_map数组中,每个页描述符在数组中的下标即为该描述符对应物理页的页框号。

2.zone结构

内核将整个页框按照不同的访问特性划分为几个区,每个区内的页框都是连续的,这样的区称为内存管理区并使用zone结构来描述。内核中使用了一个枚举类型对内存管理区的类型进行定义:

enum zone_type {
#ifdef CONFIG_ZONE_DMA
	ZONE_DMA,
#endif
#ifdef CONFIG_ZONE_DMA32
	ZONE_DMA32,
#endif
	ZONE_NORMAL,
#ifdef CONFIG_HIGHMEM
	ZONE_HIGHMEM,
#endif
	ZONE_MOVABLE,
	__MAX_NR_ZONES
};

内存管理区是一个逻辑上的概念,它的存在是因为计算机中硬件访问物理内存时有一些限制。因此,每个内存管理区的实际分布是与体系结构相关的,具体分布如下:

ZONE_DMA:某些设备通过DMA方式访问内存时,不能访问到所有的物理内存,此时只能为它们单独划分一块内存管理区。ZONE_DMA的范围根据体系结构而改变,比如X86架构下,ISA总线为16位,因此该区的范围为物理内存的前16M。但是,如果某些架构在内存的任何地址上都可以执行DMA,那么该区域就为空,即长度为0。

ZONE_DMA32:该区的作用与ZONE_DMA相同,只不过它代表的是32位可寻址并适合DMA的物理内存区域。32位的系统中该区域的长度为0,这种区域只会出现在64位的系统中。在某些64位的系统中,该区域的大小可达到4GB。

ZONE_NORMAL:这个区域包含的都是能够正常映射的页框。通过源码中的定义可以发现,所有的体系架构都包含这个区域。但是并不是每个架构下该区都能对应到实际的物理内存,根据上面所述,某些架构下ZONE_DMA32会占据整个4G的物理内存,因此该区域为空。在IA32架构下该内存管理区的范围为16MB到896MB。

ZONE_HIGHMEM:这个区域代表超出内核空间大小的物理内存,这部分内存也被成为高端内存(与之对应ZONE_DMA和ZONE_NORMAL成为低端内存)。在32位的x86系统中,高端内存即为大于896MB的物理内存。而在64位的系统中,高端内存总为空。

ZONE_MOVABLE:这个区域是一个伪内存管理区,它只在防止物理内存碎片机制中使用。

__MAX_NR_ZONES:它用来标记内存管理区的数量。

内存管理区描述符中有许多字段,有些字段理解起来并不简单,因此只介绍部分字段。

watermark:即所谓的水印值数组,它为每个内存区设置合适的内存消耗基准,该水印值随着系统中的空闲内存量而变化。该数组包含三个元素:

1).watermark[WMARK_HIGH]:当系统中空闲页框数大于其值时,表示当前内存使用情况理想。

2).watermark[WMARK_LOW]:如果空闲页框小于其值,表示空闲页量较少,应当换出内存中部分页到磁盘上。

3).watermark[WMARK_MIN]:当空闲页框数小于其值时,表示系统急需空闲页框。

free_area:它表示当前内存管理区中空闲页框。该数组中的每个元素都是一条双链表,链表中的每个元素都是固定大小的连续内存块。

lock:保护当前内存管理区的自旋锁。由于在多处理器的系统中,会出现多个CPU同时访问一个内存管理区的情形,因此需要锁来保护避免数据不一致的现象。

3.pg_data_t结构

节点这个概念是由于NUMA(非一致内存访问)模型而诞生的,该模型只存在于多处理器计算机中。NUMA根据CPU数量将整个物理内存分为几个大块,每块内存即为每个CPU的的本地内存。这样的划分使每个CPU都能以较快的速度访问本地内存,当然每个CPU也可以访问其他CPU的内存只不过速度比较慢而已。上述的每块物理内存对应一个pg_data_t数据结构,每块物理内存即为一个节点,所以的结点形成一个双链表。

与NUMA模型对应的是UMA(一致内存访问)模型,这种模型并不需要将物理内存划分为块,因此也就不存在节点这样的概念。但是为了兼容NUMA模式,UMA模型下的物理内存还是对应一个节点,也就是说整个物理内存形成一个节点,因此上述的节点链表中也就只有一个元素。

struct bootmem_data;
typedef struct pglist_data {
        struct zone node_zones[MAX_NR_ZONES];
        struct zonelist node_zonelists[MAX_ZONELISTS];
        int nr_zones;
#ifdef CONFIG_FLAT_NODE_MEM_MAP /* means !SPARSEMEM */
        struct page *node_mem_map;
#ifdef CONFIG_CGROUP_MEM_RES_CTLR
        struct page_cgroup *node_page_cgroup;
#endif
#endif
#ifndef CONFIG_NO_BOOTMEM
        struct bootmem_data *bdata;
#endif
#ifdef CONFIG_MEMORY_HOTPLUG
        spinlock_t node_size_lock;
#endif
        unsigned long node_start_pfn;
        unsigned long node_present_pages; /* total number of physical pages */
        unsigned long node_spanned_pages; /* total size of physical page
                                             range, including holes */
        int node_id;
        wait_queue_head_t kswapd_wait;
        struct task_struct *kswapd;
        int kswapd_max_order;
} pg_data_t;

node_zones:当前节点中内存管理区描述符数组。这个数组的大小使用__MAX_NR_ZONES来定义。

node_zonelists:它是zonelist结构的数组,长度为MAX_ZONELISTS。如果内核未配置NUMA,则长度为1,否则,长度为2。该数组中0号元素指定了备用的内存管理区链表,也就是当前系统中所有的zone。1号元素指定了当前节点中的管理区链表。除非分配内存时指定了GFP_THISNODE标志而采用本地内存节点上的zonelist,一般均采用备用zonelist。

struct zonelist {
        struct zonelist_cache *zlcache_ptr;                  // NULL or &zlcache
        struct zoneref _zonerefs[MAX_ZONES_PER_ZONELIST + 1];
#ifdef CONFIG_NUMA
        struct zonelist_cache zlcache;                       // optional ...
#endif
};

zonelist结构中管理区链表主要由_zonerefs数组来描述。
nr_zones:当前节点中内存管理区的数量。

node_mem_map:页框描述符数组,该数组中的页框即为当前节点中包含的物理页。

node_id:当前节点的索引,系统中节点从0开始编号。

kswapd:指向负责该节点页交换的守护进程的进程描述符。

这里只是简单的介绍了节点,内存管理区,页框所代表的数据结构,这三个结构贯穿整个内存管理系统中,许多字段的含义以及作用随着对内存管理部分的深入学习才能逐渐加深理解。

windows 7 ultimate product key

windows 7 ultimate product key

winrar download free

winrar download free

winzip registration code

winzip registration code

winzip free download

winzip free download

winzip activation code

winzip activation code

windows 7 key generator

windows 7 key generator

winzip freeware

winzip freeware

winzip free download full version

winzip free download full version

free winrar download

free winrar download

free winrar

free winrar

windows 7 crack

windows 7 crack

windows xp product key

windows xp product key

windows 7 activation crack

windows7 activation crack

free winzip

free winzip

winrar free download

winrar free download

winrar free

winrar free

download winrar free

download winrar free

windows 7 product key

windows 7 product key