内核经常请求和释放单个页框。为了提升系统性能,每个内存管理区定义了一个“每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
这个版本以后没有具体的冷页高速缓存,对于冷热页只是在链表头(热)还是链表尾(冷)的区别,不知道这样理解对不对?如有误,还望指点。