0 CPU页帧缓存概念
在前一节中,我们学习了buddy伙伴关系系统,它适用于申请连续的大块物理内存;而有些时候,经常需要申请和释放单个页帧。但是,如果使用伙伴关系系统,需要查表、进行合并等操作,效率不高。为了提高性能,每个内存ZONE区都提供了一个per-CPU变量,CPU页帧缓存。每个CPU页帧缓存都包含一些预分配好的页帧,满足本地CPU发起的单个页帧请求。
实际上,每个内存ZONE区和每个CPU都有2个缓存:一个是热缓存,它存储页帧,其内容可能包含在CPU的硬件缓存中;另一个是冷缓存。
如果内核或用户进程在分配后立即写入页帧,那么从热缓存中获取页帧将有利于系统性能。实际上,每次访问页帧的某个内存位置,都会导致硬件Cache中替换其它页帧的某一行(Cache-line),当然,除非硬件Cache已经包含刚刚访问的“热”页帧中内存位置的一行。
相反,如果要用DMA操作填充页帧,则从冷缓存中取页帧是很方便的。在这种情况下,不涉及CPU,也不会修改硬件Cache的任何行。从冷缓存中取页帧可以为其他类型的内存分配请求保留热页帧。
CPU页帧缓存的数据结构是per_cpu_pageset类型的数组,其存储在内存ZONE描述符中的pageset成员中,如下面的代码所示:
structzone{
/*...*/
structper_cpu_pagesetpageset[NR_CPUS];
/*...*/
}
数组个数与CPU个数相关,其中的每个数组元素又包含2个per_cpu_pages描述符成员:一个是热缓存;另一个是冷缓存。而per_cpu_pages数据类型的成员如下表所示:
structper_cpu_pages{
intcount;/*缓存中的页帧数量*/
intlow;/*阈值下限,用于缓存补充*/
inthigh;/*阈值上限,需要清空缓存*/
intbatch;/*需从缓存中添加或减少的页帧数*/
structlist_headlist;/*缓存中页帧描述符列表,即内存页列表*/
};
内核使用两个阈值(low和high)监控冷/热缓存的大小:如果页帧数量低于阈值,则内核使用伙伴系统分配一定数量的单个页帧(batch);否则,页帧数量超过阈值上限,内核将缓存中的页帧释放到伙伴系统中(batch)。batch、low和high的值,具体依赖于内存ZONE区的页帧数量。
1 通过CPU页帧缓存分配页帧
buffered_rmqueue()函数在给定的内存ZONE区中分配页帧。它利用CPU页帧缓存来处理单个页帧请求。
Linux v2.6.11内核源码实现如下所示(文件位置:/mm/page_alloc.c):
staticstructpage*
buffered_rmqueue(structzone*zone,intorder,intgfp_flags)
{
unsignedlongflags;
structpage*page=NULL;
intcold=!!(gfp_flags&__GFP_COLD);
if(order==0){
structper_cpu_pages*pcp;
pcp=&zone->pageset[get_cpu()].pcp[cold];
local_irq_save(flags);
if(pcp->count<= pcp->low)
pcp->count+=rmqueue_bulk(zone,0,
pcp->batch,&pcp->list);
if(pcp->count){
page=list_entry(pcp->list.next,structpage,lru);
list_del(&page->lru);
pcp->count--;
}
local_irq_restore(flags);
put_cpu();
}
if(page==NULL){
spin_lock_irqsave(&zone->lock,flags);
page=__rmqueue(zone,order);
spin_unlock_irqrestore(&zone->lock,flags);
}
if(page!=NULL){
BUG_ON(bad_range(zone,page));
mod_page_state_zone(zone,pgalloc,1<< order);
prep_new_page(page, order);
if (gfp_flags & __GFP_ZERO)
prep_zero_page(page, order, gfp_flags);
if (order && (gfp_flags & __GFP_COMP))
prep_compound_page(page, order);
}
return page;
}
输入参数分别是内存ZONE区的描述符的地址(zone)、内存分配请求大小(2^order)和分配标志gfp_flags。如果在gfp_flags中设置了__GFP_COLD标志,则应从冷缓存中获取页帧,否则应从热缓存中获取页帧(此标志仅对单个页帧请求有意义)。该函数基本上执行以下操作:
如果order不等于0,则页帧缓存不能使用,函数直接跳转到第4步。
检查由__GFP_COLD标志标识的内存ZONE区域的CPU缓存是否必须被补充(per_cpu_pages的count ≤ low)。在本例中,它执行以下子步骤:
重复调用__rmqueue()函数,从伙伴系统中分配batch个页帧。
将分配的页帧描述符插入到缓存的列表中。
更新count变量(将新分配的页帧数量加上)。
如果count > 0,从缓存列表中取一个页帧,然后跳转到第5步。(CPU页帧缓存可能是空的,在第2步的__rmqueue()没有申请到页帧时就会发生)
到这儿,如果内存请求没有被满足,调用__rmqueue()申请从伙伴系统中分配所请求页帧。
如果内存请求被满足,初始化该页帧(第1个)的页描述符:清除某些标志、设置private为0,设置页帧引用计数器为1。另外,如果设置了__GPF_ZERO,将申请的内存清零。
返回页帧(第1个)的描述符,失败返回NULL。
2 通过CPU页帧缓存释放页帧
从CPU页帧缓存中释放页帧,使用free_hot_page()和free_cold_page()函数。它们都是free_hot_cold_page()的封装函数,如下所示(文件位置:/mm/page_alloc.c):
staticvoidfastcallfree_hot_cold_page(structpage*page,intcold)
{
structzone*zone=page_zone(page);
structper_cpu_pages*pcp;
unsignedlongflags;
arch_free_page(page,0);
kernel_map_pages(page,1,0);
inc_page_state(pgfree);
if(PageAnon(page))
page->mapping=NULL;
free_pages_check(__FUNCTION__,page);
pcp=&zone->pageset[get_cpu()].pcp[cold];
local_irq_save(flags);
if(pcp->count>=pcp->high)
pcp->count-=free_pages_bulk(zone,pcp->batch,&pcp->list,0);
list_add(&page->lru,&pcp->list);
pcp->count++;
local_irq_restore(flags);
put_cpu();
}
free_hot_cold_page()接受的参数是待释放页帧的描述符地址page,表示热缓存还是冷缓存的标志cold。
执行的步骤如下:
根据页帧,获取page->flags标志。
根据cold标志获取对应页帧缓存的描述符per_cpu_pages地址。
检查缓存是否不足:如果count ≥ high,调用free_pages_bulk()函数。该函数会重复调用__free_pages_bulk()函数释放指定的页帧到伙伴系统中。
将该页帧添加到缓存列表中,增加count计数。
应该注意的是,在Linux v2.6内核中,没有任何页帧被释放到冷缓存中:内核总是假设释放的页帧相对于硬件缓存来说是热的。当然,这并不意味着冷缓存是空的:当达到低阈值时,缓存由buffered_rmqueue()补充。
3 移除__GFP_COLD
虽然我们前边分析了基于冷热缓存的CPU页帧缓存,但是,从v4.14版本以后的内核中已经移除,参考patch。Patches 1-4是与移除冷缓存最相关的部分;Patches 5-8是可选的,因为它们都是删除无用但也不影响性能的代码。
free_hot_cold_page的大多数调用者用户都声称被释放的页是热缓存的。唯一的例外是页回收代码,因为在不久的将来可能会释放足够多的页,因此CPU的本地页帧缓存列表将被回收,热缓存信息将丢失。由于没有人真正关心被释放到分配器的页的热信息,所以省略该参数即可。
审核编辑:刘清
-
计数器
+关注
关注
32文章
2306浏览量
97574 -
Cache
+关注
关注
0文章
130浏览量
29605 -
LINUX内核
+关注
关注
1文章
318浏览量
23055
原文标题:Linux内核8.5-内存管理之CPU本地页帧缓存
文章出处:【微信号:嵌入式ARM和Linux,微信公众号:嵌入式ARM和Linux】欢迎添加关注!文章转载请注明出处。
发布评论请先 登录
Linux内存管理之页面回收
HVM的缓存控制与内存管理
Linux内存管理导读
Linux性能及调优指南:内存架构
如何在 Linux 上查看本地 DNS 缓存
什么是CPU缓存?它有哪些作用?
缓存之美——如何选择合适的本地缓存?

Linux内存管理之CPU本地页帧缓存
评论