1.1 高速缓存的应用
内核经常需要请求和释放单个页面。为了提升系统性能,每个内存管理区zone定义了一个“每CPU”页面高速缓存。所有高速缓存包含一些预先分配的页,它们被用于满足本地CPU发出的单一内存请求。
实际上,这里为每个内存管理区和每个CPU提供了两个高速缓存:一个热高速缓存,它存放的页框中所包含的内容很可能就在CPU硬件高速缓存中;还有一个冷高速缓存。
内核使用两个位标来监视热高速缓存和冷高速缓存的大小:如果页个数低于下界low,内核通过buddy系统分配batch个单一页面来补充对应的高速缓存;否则,如果页框个数高过上界high,内核从高速缓存中释放batch个页框到buddy系统中。
1.1.1 页面回收
当buddy算法回收一个页时,它会首先试图将其放在热高速缓存中,其实现如下:
fastcall void __free_pages(struct page *page, unsigned int order)
{
if (put_page_testzero(page)) {
if (order == 0)
free_hot_page(page);
else
__free_pages_ok(page, order);
}
}
void
fastcall free_hot_page(struct page *page)
{
free_hot_cold_page(page, 0);
}
跟踪
free_hot_cold_page函数:
/*
* Free a 0-order page
*/
static
void fastcall free_hot_cold_page(struct page *page, int cold)
{
struct zone *zone = page_zone(page); //
返回ZONE_DMA这个区域
struct per_cpu_pages *pcp;
unsigned long flags;
if (PageAnon(page))
page->mapping = NULL;
if (free_pages_check(page))
return;
if (!PageHighMem(page)) //
总为FALSE
debug_check_no_locks_freed(page_address(page), PAGE_SIZE);
arch_free_page(page, 0); //
空语句
kernel_map_pages(page, 1, 0); //
空语句
pcp = &zone_pcp(zone, get_cpu())->pcp[cold];
local_irq_save(flags);
__count_vm_event(PGFREE); //
空语句
list_add(&page->lru, &pcp->list);
pcp->count++;
if (pcp->count >= pcp->high) {
free_pages_bulk(zone, pcp->batch, &pcp->list, 0);
pcp->count -= pcp->batch;
}
local_irq_restore(flags);
put_cpu();
}
从这个函数可以看出,如果pcp中的页数较少的时候,它将直接把回收的页面放在高速缓存中(热高速缓存或者冷高速缓存),即上述函数中的list_add调用。在这里pcp实际指向DMA_ZONE中的pcp。当高速缓存的页面较多时,它将切换出部分最后进入缓存的页,将这些页链接到可用的页面链表中(使用BUDDY算法)。
1.1.2 换页策略
内核中当高速缓存的页面较多时,会将部分页面切换到可用内存的链表中,这个操作由
free_pages_bulk函数完成:
/*
* Frees a list of pages.
* 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_pages_bulk(struct zone *zone, int count,
struct list_head *list, int order)
{
spin_lock(&zone->lock);
zone->all_unreclaimable = 0;
zone->pages_scanned = 0;
while (count--) {
struct page *page;
VM_BUG_ON(list_empty(list));
page = list_entry(list->prev, struct page, lru);
/* have to delete it as __free_one_page list manipulates */
list_del(&page->lru);
__free_one_page(page, zone, order);
}
spin_unlock(&zone->lock);
}
因为在页面回收时是将要回收的页面插入到双链表的表头,而从上述函数中可以看出,在将页面切换出高速缓存的时候,也是从链表头按顺序进行的,因此整个切换策略就是后进先出,即最后进入高速缓存的先切换出去。