存档在 2012年3月

伙伴算法的实现-分配页框

2012年3月7日

内核中alloc_pages系列页框分配函数都是基于伙伴算法实现的,这些函数最终都会调用伙伴算法的入口函数buffered_rmqueue()。

Linux内核管理物理内存有三种方式,其一就是经典的伙伴算法。但是伙伴算法分配物理内存的基本单位是页框,因此内核又引入了slab机制,基于此机制实现的物理内存分配器可以快速有效的分配小于页框的物理内存,并且可以有效避免内部碎片。另外,内核常常会申请单个页框大小的物理内存,因此内核又引入了per-CPU机制,该机制专门用于快速分配单个页框。

1.__rmqueue()

其实buffered_rmqueue()函数仍然没有进行真正的页框分配,该函数首先判断分配阶是否为0,如果是则启用per-CPU机制来分配物理内存,否则调用__rmqueue()。

static struct page *__rmqueue(struct zone *zone, unsigned int order,
                                                int migratetype)
{
        struct page *page;
retry_reserve:
        page = __rmqueue_smallest(zone, order, migratetype);

        if (unlikely(!page) && migratetype != MIGRATE_RESERVE) {
                page = __rmqueue_fallback(zone, order, migratetype);

                if (!page) {
                        migratetype = MIGRATE_RESERVE;
                        goto retry_reserve;
                }
        }

        trace_mm_page_alloc_zone_locked(page, order, migratetype);
        return page;
}

传递到此函数中的zone表示伙伴算法将从该内存管理区中分配页框,order即分配阶,migratetype表示迁移类型。该函数首选__rmqueue_smallest()进行内存分配,如果在指定的迁移类型上分配失败后,再选用其他备用的迁移列表进行内存分配,该过程通过__rmqueue_fallback()完成。总之内核总是在竭尽全力保证满足分配内存的请求。

2.__rmqueue_smallest()

该函数的实现比较简单,从当前指定的分配阶到最高分配阶依次进行遍历。在每次遍历的分配阶链表中,根据参数migratetype选择正确的迁移队列。根据以上的限定条件,当选定一个页框块链表后,只要该链表不为空,就说明可以分配该分配阶对应的页框块。

一旦选定在当前遍历的分配阶链表上分配页框,那么就通过list_entry()将该页框块从链表上移除。然后将页框块首页框的PG_buddy标志删除,删除该标志说明当前页框块已经不属于伙伴链表。并且将该首页框描述符中的priveate置0,该字段中本来保存的是其所处页框块的分配阶。以上这个过程通过rmv_page_order()完成。此外,还要更新页框块链表nr_free的值。

static inline
struct page *__rmqueue_smallest(struct zone *zone, unsigned int order,
                                                int migratetype)
{
        unsigned int current_order;
        struct free_area * area;
        struct page *page;

        for (current_order = order; current_order < MAX_ORDER; ++current_order) {
                area = &(zone->free_area[current_order]);
                if (list_empty(&area->free_list[migratetype]))
                        continue;

                page = list_entry(area->free_list[migratetype].next, struct page, lru);
                list_del(&page->lru);
                rmv_page_order(page);
                area->nr_free--;
                expand(zone, page, order, current_order, area, migratetype);
                return page;
        }

        return NULL;
}

static inline void rmv_page_order(struct page *page)
{
        __ClearPageBuddy(page);
        set_page_private(page, 0);
}

__rmqueue_smallest()内部还有一个重要的函数expand()。进入该函数的条件是当所申请的分配阶order小于当前选中的分配阶current_order,也就是说指定的分配阶链表中没有空闲的页框块,只能选用较大的页框块。因此,expand()必须按照伙伴算法的分裂原理将比较大的页框块分割成较小的块。

3.expand()

分裂函数的实现也是显而易见的,它完全遵照伙伴算法的分裂原理。这里有两个分配阶,一个是申请页框时指定的low,一个是在上级函数中遍历时所选定的high。该函数从high分配阶开始递减向low遍历,也就是从较大的页框块开始依次分裂。

比如high为4,而low为2。那么第一遍历时,将大小为16(分配阶为4)的页框块一份为2。通过list_add()将后面的8个连续页框块加入下级链表(分配阶为3),下级链表通过将area指针自减即可得到,后8个页框块的指针也通过page+size获得,而page仍然指向最初的页框块首页框。此时还要对分配阶为3的链表更新nr_free,以及通过set_page_order()对后8个页框块设置一些标志。

第二次遍历将前面8个页框块继续一分为二,将后4个页框块加入area所指向的下级链表(分配阶为2)。第三次遍历时,循环条件已经不再满足,因此返回前4个页框块首页框的描述符地址page。

static inline void expand(struct zone *zone, struct page *page,
        int low, int high, struct free_area *area,
        int migratetype)
{
        unsigned long size = 1 << high;
         while (high > low) {
                area--;
                high--;
                size >>= 1;
                VM_BUG_ON(bad_range(zone, &page[size]));
                list_add(&page[size].lru, &area->free_list[migratetype]);
                area->nr_free++;
                set_page_order(&page[size], high);
        }
}

static inline void set_page_order(struct page *page, int order)
{
        set_page_private(page, order);
        __SetPageBuddy(page);
}

伙伴算法的数据结构

2012年3月6日

伙伴算法(buddy system)在物理内存管理中占据十分重要的位置,这种算法可以有效的避免内存中的外部碎片。所谓外部碎片(external fragmentation),就是指内存频繁请求和释放大小不同的连续页框后,导致在已分配页框块周围分散了许多小块空闲的页框,尽管这些空闲页框的总数可以满足接下来的请求,但却无法满足一个大块的连续页框。本文接下来详细说明伙伴算法在内核中的结构描述,其基本原理本文不再赘述。

在每个内存管理区中都有一个free_area数组,该数组的长度为MAX_ORDER,默认值为11。free_area数组描述的就是伙伴算法中每个分配阶(从0到11)所对应的页框块链表。比如free_area[2]所对应的页框块链表中,每个节点对应4个连续的页框(2的2次方)。

struct zone {
……
struct free_area        free_area[MAX_ORDER];
……
}

#ifndef CONFIG_FORCE_MAX_ZONEORDER
#define MAX_ORDER 11
#else
#define MAX_ORDER CONFIG_FORCE_MAX_ZONEORDER
#endif

可以看到,free_area数组的元素类型是struct free_area,该结构的描述如下:

struct free_area {
        struct list_head        free_list[MIGRATE_TYPES];
        unsigned long           nr_free;
};

在这个结构中的确有一个表示当前分配阶所对应的页框块链表free_list,不过这里稍显复杂一下,因free_list是一个链表数组,这个数组也称为迁移数组。我们可以将这个数组看作是对页框块链表的进一步细分,每个数组元素对应一种迁移类型的页框块链表。迁移列表是在内核2.6.24中引入的,它更加的避免了由于系统长期运行而产生的外部碎片。除了链表结构以外,该结构使用nr_free表示当前链表中空闲页框块的数目,比如free_area[2]中nr_free的值为5,表示有5个大小为4的页框块,那么总的页框数目为20。

根据上面对伙伴算法数据结构的描述,可以得到下面的关系图:

上图表示的是某个内存节点中的某个内存管理区中的伙伴算法示意图。需要注意的是,页框描述符page中有一个lru字段,该字段即为链接每个页框块的链表节点。

struct page {
……
        struct list_head lru;
……
};

从图中也可以看出,链表中负责连接前后页框块的是该页框块首页框中的链表节点。

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

2012年3月2日

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

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

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