per-CPU分配和释放单页框

内核经常请求和释放单个页框。为了提升系统性能,每个内存管理区定义了一个“每CPU”页框高速缓存。所有“每CPU”高速缓存包含一些预先分配的页框,它们被用于满足本地CPU发出的单一内存请求。

为每个内存管理区和每CPU提供了两个高速缓存:一个热高速缓存,它存放页框中所包含的内容很可能就在CPU硬件高速缓存中;还有一个冷高速缓存。

在内存管理区中,分配单页使用per-cpu机制,分配多页使用伙伴算法,结构图如下:

1.相关结构体

zone结构体中pageset成员指向内存域per-CPU管理结构,NR_CPUS定义系统cpu个数

struct zone {
    ...   
    #ifdef CONFIG_NUMA    //若定义了CONFIG_NUMA宏,pageset为二级指针,否则为数组
         struct per_cpu_pageset	*pageset[NR_CPUS];
    #else
         struct per_cpu_pageset	pageset[NR_CPUS];
    #endif
    ...
}

per_cpu_pageset结构体定义如下:

struct per_cpu_pageset {
	struct per_cpu_pages pcp;   //per_cpu_pages结构中定义了页帧的数量、链表等信息
#ifdef CONFIG_NUMA
	s8 expire;
#endif
#ifdef CONFIG_SMP
	s8 stat_threshold;
	s8 vm_stat_diff[NR_VM_ZONE_STAT_ITEMS];
#endif
} ____cacheline_aligned_in_smp;

这个结构体将只用在struct zone当中,且通过zone_pcp宏进行访问:

#ifdef CONFIG_NUMA
#define zone_pcp(__z, __cpu) ((__z)->pageset[(__cpu)])
#else
#define zone_pcp(__z, __cpu) (&(__z)->pageset[(__cpu)])
#endif
struct per_cpu_pages {
	int count;		/* 内存区per-cpu的总数 */
	int high;		/* 定义per-cpu最高值 */
	int batch;		/* 从伙伴系统中添加或者删除的块大小*/

	/* per-cpu数组链表 ,每个迁移类型为一个链表,可以防止内存碎片*/
	struct list_head lists[MIGRATE_PCPTYPES];
};

2. per_cpu缓存初始化


static __meminit void zone_pcp_init(struct zone *zone)
{
	int cpu;
	unsigned long batch = zone_batchsize(zone);   //根据内存域中页帧的多少计算batch大小

	for (cpu = 0; cpu < NR_CPUS; cpu++) {    //遍历所有cpu,进行初始化
#ifdef CONFIG_NUMA
		/* Early boot. Slab allocator not functional yet */
		zone_pcp(zone, cpu) = &boot_pageset[cpu];
		setup_pageset(&boot_pageset[cpu],0);
#else
		setup_pageset(zone_pcp(zone,cpu), batch);  //初始化per_cpu_pages结构
#endif
	}
	if (zone->present_pages)
		printk(KERN_DEBUG "  %s zone: %lu pages, LIFO batch:%lu\n",
			zone->name, zone->present_pages, batch);
}
static int zone_batchsize(struct zone *zone)
{
#ifdef CONFIG_MMU
	int batch;

	batch = zone->present_pages / 1024;
	if (batch * PAGE_SIZE > 512 * 1024)
		batch = (512 * 1024) / PAGE_SIZE;
	batch /= 4;		/* We effectively *= 4 below */
	if (batch < 1)
		batch = 1;
	batch = rounddown_pow_of_two(batch + batch/2) - 1;
	return batch;
#else
	return 0;
#endif
}
static void setup_pageset(struct per_cpu_pageset *p, unsigned long batch)
{
	struct per_cpu_pages *pcp;
	int migratetype;

	memset(p, 0, sizeof(*p));    //初始化p指向的内存域,全填充0

	pcp = &p->pcp;
	pcp->count = 0;
	pcp->high = 6 * batch;
	pcp->batch = max(1UL, 1 * batch);
	for (migratetype = 0; migratetype < MIGRATE_PCPTYPES; migratetype++)
		INIT_LIST_HEAD(&pcp->lists[migratetype]);
}

以上就是每cpu的初始化过程。接下来主要看看系统如何分配和释放单页框:

buffered_rmqueue函数用于分配,free_hot_cold_page函数用于释放

3. 分配单页框

当order==0时,即就是分配2^0=1个页框。系统对单页框的分配释放比较频繁,为提升系统性能采用per-cpu机制。

buffer_rmqueue(){
    ......
    int cold = !!(gfp_flags & __GFP_COLD); /*分配参数指定了__GFP_COLD标志,则设置cold标志*/
    ......
    if (likely(order == 0)) {
          struct per_cpu_pages *pcp;
          struct list_head *list;
          pcp = &zone_pcp(zone, cpu)->pcp;      //取当前CPU的页面缓存对象
          list = &pcp->lists[migratetype];  /*根据指定的迁移类型,从per-CPU指定的迁移缓存链表中分配*/
          local_irq_save(flags);    //关闭中断
          if (list_empty(list)) {
                 pcp->count += rmqueue_bulk(zone, 0, pcp->batch, list,  migratetype, cold); 
                 if (unlikely(list_empty(list)))
                        goto failed;
          }
          if (cold)
                /*有cold标志就是冷页,则取出链表的最后一个节点返回*/
               page = list_entry(list->prev, struct page, lru);
          else
                /*取出链表的第一个页面,这个页面是最近刚释放到CPU缓存的,缓存热度最高*/
               page = list_entry(list->next, struct page, lru);
          list_del(&page->lru);    /*将页面从每CPU缓存链表中取出,并将每CPU缓存计数减一*/
          pcp->count--;
    ......
}

在per-cpu缓存区中页框为空时,会调用rmqueue_bulk函数从伙伴系统中批量取出batch个页框到per-cpu链表中,具体过程如下:

static int rmqueue_bulk(struct zone *zone, unsigned int order, 
			unsigned long count, struct list_head *list,
			int migratetype, int cold)
{
	int i;

	spin_lock(&zone->lock);
	for (i = 0; i < count; ++i) {
		struct page *page = __rmqueue(zone, order, migratetype);  //从伙伴系统中取页,具体函数实现参照上篇博客
		if (unlikely(page == NULL))    
			break;

		if (likely(cold == 0))    //没有cold标志,为热页,添加到链表头
			list_add(&page->lru, list);
		else
			list_add_tail(&page->lru, list);   //冷页添加到链表尾
		set_page_private(page, migratetype);
		list = &page->lru;
	}
	__mod_zone_page_state(zone, NR_FREE_PAGES, -(i << order)); //递减管理区的空闲页面计数
	spin_unlock(&zone->lock);
	return i;
}

4. 释放页框

释放和分配相似,对于单页框释放到每cpu缓存中,多页释放到伙伴系统中

void __free_pages(struct page *page, unsigned int order)
{
	if (put_page_testzero(page)) { //判断页为空闲才能释放
		trace_mm_page_free_direct(page, order);
		if (order == 0)			//释放单个页框,使用per-CPU机制
			free_hot_page(page);
		else	//多个页框,使用buddy机制
			__free_pages_ok(page, order); 
	}
}

free_hot_page函数是free_hot_cold_page函数的封装。

释放的大致流程如下:

static void free_hot_cold_page(struct page *page, int cold)
{
	struct zone *zone = page_zone(page);  /*从page->flags字段获取包含页面内存管理区的描述符地址*/
	struct per_cpu_pages *pcp;
	unsigned long flags;
	int migratetype;
	int wasMlocked = __TestClearPageMlocked(page);

	kmemcheck_free_shadow(page, 0);

	if (PageAnon(page))		/*监测页面是否为匿名页面*/
		page->mapping = NULL;
	if (free_pages_check(page))	  /*检测页面是否有错误*/
		return;

	if (!PageHighMem(page)) {
		debug_check_no_locks_freed(page_address(page), PAGE_SIZE);
		debug_check_no_obj_freed(page_address(page), PAGE_SIZE);
	}
	arch_free_page(page, 0);   /* 如果定义了HAVE_ARCH_FREE_PAGE宏,有自己特定体系结构的释放函数,此函数不为空(x86为空)*/
	kernel_map_pages(page, 1, 0); 

	pcp = &zone_pcp(zone, get_cpu())->pcp;   /*获得CPU对应zone的pcp地址*/
	migratetype = get_pageblock_migratetype(page);
	set_page_private(page, migratetype);
	local_irq_save(flags);		/*禁止当前CPU的中断,并保存中断标识*/
	if (unlikely(wasMlocked))
		free_page_mlock(page);
	__count_vm_event(PGFREE);

	/*
         * 只有不可移动页、可回收页和可移动页才能放到每CPU页框高速缓存中
         * 如果迁移类型不属于这三种,则要将该页释放回伙伴系统
         */
	if (migratetype >= MIGRATE_PCPTYPES) {
		if (unlikely(migratetype == MIGRATE_ISOLATE)) {
			free_one_page(zone, page, 0, migratetype);
			goto out;
		}
		migratetype = MIGRATE_MOVABLE;
	}

	if (cold)    //冷页添加到链表尾,热页添加到链表头
		list_add_tail(&page->lru, &pcp->lists[migratetype]);
	else
		list_add(&page->lru, &pcp->lists[migratetype]);
	pcp->count++;
	if (pcp->count >= pcp->high) {
		free_pcppages_bulk(zone, pcp->batch, pcp);
		pcp->count -= pcp->batch;
	}

out:
	local_irq_restore(flags);
	put_cpu();
}

当每cpu中的页框数高于最大值时,就调用free_pcppages_bulk函数释放batch个页框到伙伴系统,具体代码如下:

/*
 * Frees a number of pages from the PCP lists
 * Assumes all pages on list are in same zone, and of same order.
 * count is the number of pages to free.
 *
 * If the zone was previously in an "all pages pinned" state then look to
 * see if this freeing clears that state.
 *
 * And clear the zone‘s pages_scanned counter, to hold off the "all pages are
 * pinned" detection logic.
 */
static void free_pcppages_bulk(struct zone *zone, int count,
					struct per_cpu_pages *pcp)
{
	int migratetype = 0;
	int batch_free = 0;

	spin_lock(&zone->lock);
	zone_clear_flag(zone, ZONE_ALL_UNRECLAIMABLE);
	zone->pages_scanned = 0;

	__mod_zone_page_state(zone, NR_FREE_PAGES, count);
	while (count) {
		struct page *page;
		struct list_head *list;

		/*
		 * Remove pages from lists in a round-robin fashion. A
		 * batch_free count is maintained that is incremented when an
		 * empty list is encountered.  This is so more pages are freed
		 * off fuller lists instead of spinning excessively around empty
		 * lists
		 */
		do {  /*从pcp三类链表(MIGRATE_UNMOVABLE、MIGRATE_RECLAIMABLE、MIGRATE_MOVABLE)中找出不为空的释放*/
			batch_free++;
			if (++migratetype == MIGRATE_PCPTYPES)
				migratetype = 0;
			list = &pcp->lists[migratetype];
		} while (list_empty(list));

		do {
			page = list_entry(list->prev, struct page, lru);
			/* must delete as __free_one_page list manipulates */
			list_del(&page->lru);
			__free_one_page(page, zone, 0, migratetype);
			trace_mm_page_pcpu_drain(page, 0, migratetype);
		} while (--count && --batch_free && !list_empty(list));
	}
	spin_unlock(&zone->lock);
}

以上就是每cpu分配释放单页的大致过程。

以上代码基于内核2.6.32

这个版本以后没有具体的冷页高速缓存,对于冷热页只是在链表头(热)还是链表尾(冷)的区别,不知道这样理解对不对?如有误,还望指点。

时间: 2024-10-25 09:38:32

per-CPU分配和释放单页框的相关文章

linux内存源码分析 - 伙伴系统(初始化和申请页框)

本文为原创,转载请注明:http://www.cnblogs.com/tolimit/ 之前的文章已经介绍了伙伴系统,这篇我们主要看看源码中是如何初始化伙伴系统.从伙伴系统中分配页框,返回页框于伙伴系统中的. 我们知道,每个管理区都有自己的伙伴系统管理属于这个管理区的页框,这也说明了,在伙伴系统初始化时,管理区必须要已经存在(初始化完成)了.在管理区描述符(struct zone)中,struct free_area就专门用于描述伙伴系统的.在一个管理区中,伙伴系统一共维护着包含1,2,4,8,

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

内核中alloc_pages系列页框分配函数都是基于伙伴算法实现的,这些函数最终都会调用伙伴算法的入口函数buffered_rmqueue(). Linux内核管理物理内存有三种方式,其一就是经典的伙伴算法.但是伙伴算法分配物理内存的基本单位是页框,因此内核又引入了slab机制,基于此机制实现的物理内存分配器可以快速有效的分配小于页框的物理内存,并且可以有效避免内部碎片.另外,内核常常会申请单个页框大小的物理内存,因此内核又引入了per-CPU机制,该机制专门用于快速分配单个页框. 1.__rm

Linux内核剖析 之 回收页框

一.页框回收算法 1.为何要有页框回收算法? Linux在为用户态与内核分配动态内存时,检查得并不严谨. 例如: (1).对单个用户创建的进程的RAM使用的总量并不作严格的检查(进程资源的限制只针对单个进程): (2).对内核使用的许多磁盘高速缓存和内存高速缓存大小也同样不做限制. 2.为何要减少控制? 可以使内核以最好的可行方式使用可用的RAM: (1).当系统负载较低时,RAM的大部分由磁盘高速缓存占用,较少的正在运行的进程可以获益: (2).当系统负载增加时,RAM的大部分则由进程页占用,

操作系统内存管理之 分页与虚存(页表、页框、内存)

一 页面与页表 1 页面 分页存储管理是将作业的逻辑地址划分为一系列同等大小的部分,称为页.并为各页加以编号,每个作业的页的编号都是从0开始的.与之类似,把可用的物理内存也划分为同样大小的连续的部分,称为块或页框.同样为块也进行标号,从0#开始.在为进程分配内存空间时,以页为单位,每个内存中的块存放一页用户作业.只要内存中有足够多的块,这些块可以相邻也可以不相邻,就可以存放整个作业了. 页面的大小对于内存利用和系统开销来说非常重要,页面太大,在作业的最后一页必然会剩余较大不能利用的空间--内碎片

内存管理概述、内存分配与释放、地址映射机制(mm_struct, vm_area_struct)、malloc/free 的实现

http://blog.csdn.net/pi9nc/article/details/23334659 注:本分类下文章大多整理自<深入分析linux内核源代码>一书,另有参考其他一些资料如<linux内核完全剖析>.<linux c 编程一站式学习>等,只是为了更好地理清系统编程和网络编程中的一些概念性问题,并没有深入地阅读分析源码,我也是草草翻过这本书,请有兴趣的朋友自己参考相关资料.此书出版较早,分析的版本为2.4.16,故出现的一些概念可能跟最新版本内核不同.

【单页应用巨坑之History】细数History带给单页应用的噩梦

前言 在我们日常的网页浏览中,我们非常喜欢做一个操作:点击浏览器的前进后退在Ajax技术出现后,有些时候前进后退就会给开发者带来困扰,甚至一些开发者试图去干掉History随着Html5的发展,移动端的兴旺,单页应用出现了,于是History的处理被不得不提上议程了!要知道,这一直是一项让人不愿意去碰的巨坑,但是单页应用却不得不去解决 首先History的处理逻辑看似简单,实则负责,稍不注意就会出问题,我们这里来探讨下单页中History的处理规则 基础知识 javascript中History

深入理解Linux内核-回收页框

Linux 系统在为用户态进程和内核分配动态内存的时候,所作的检查是马马虎虎的对内核使用的许多磁盘高速缓存和内存高速缓存大小也同样不作限制. 页框回收算法(PFRA):1.在所有内存使用完之前,就必须执行页框回收算法2.选择目标页,它获取页框,并且使之空闲3.候选回收页:任何属于磁盘和内存高速缓存的页,以及属于进程用户态地址空间的页4.首先释放‘无害’页:先释放没有被任何进程使用的磁盘与内存高速缓存中的页5.将用户态进程的所有页定为可回收页.6.同时取消引用一个共享页框的所有页表项的映射,就可以

ZFS空间管理(分配、释放原理)

一个文件系统的空间管理,常见的技术大致有两种,bitmap和tree方式. bitmap是将文件系统所有管辖的空间细化成block(windows叫cluster),每一个block对应一个二进制位,两种状态分别表示自由/已分配.将这些二进制位集合在一起,就是bitmap.当需要分配空间时,在bitmap中查找连续的自由位,分配后,再置成已分配就可以了:释放时,将对应位置为可分配即可. tree方式是以extent的记录来描述自由/已分配空间的状态,如果以表示自由空间的tree来说,有可能是由一

【单页应用】view与model相关梳理

前情回顾 根据之前的学习,我们形成了一个view与一个messageCenterview这块来说又内建了一套mvc的东西,我们这里来理一下首先View一层由三部分组成:① view② dataAdpter③ viewController view一块两个重要数据是模板以及对应data,一个状态机status这里view只负责根据状态取出对应的模板,而后根据传入的数据返回组装好的html这里一个view多种状态是什么意思呢?比如我有一个组件,但是里面有一圈数据是需要Ajax请求的,所以我的view