简介
在上一篇文章, 我们介绍了kernel对物理页帧的管理机制, 在实际的应用场景中,kernel经常申请和释放单个的物理页, 这在之前的模型下,效率会比较低.
因此为了提升在这样的场景下的系统的性能,
每个zone中引入了cpu自己的页帧缓存( per-cpu
frame
cache
).
每个cpu会维护一些预先分配的单个页的缓存用于该cpu
申请单个的物理页.
物理页cache类型
实际上对于每个zone来说有两种cache类型:
hot
cache
: 这些物理页中存放的内容很可能就在当前的cpu硬件cache中.cold
cache
: 这些物理页中的内容不在当前cpu硬件cache中.
这里有必要说下cpu对物理内存的方式,当cpu尝试访问一个物理地址,
它首先会查看硬件cache中是否已经存在该地址处的内容,
如果已经存在那么直接返回cache中的内容,反之,
它会将真实物理内存处的内容,按照cache
line
的大小加载到
cache中,然后再将其内容返回.
可见如果程序刚刚申请了一块内存,然后便尝试对其进行写操作, 那么如果该部分物理页还在cache中,就减少了一次内存加载.
当然, cold
cache
也有其作用,当访问的物理页被用于 DMA
那么在这种情况下,就没必要使用 hot
cache
的那些物理页.
数据类型
在每个zone当中存在一个数组,数组的个数为cpu的个数,
而每个数组的元素为 struct
per_cpu_pageset
,
其实它只是对 struct
per_cpu_pages
的一个封装:
struct per_cpu_pages {
int count; /* 该链表中物理页的个数 */
int high; /* 当链表中的物理页个数超过该数值,会将部分页返还给zone buddy系统 */
int batch; /* 每次返还给buddy系统的物理页的个数 */
/*
* 对于不同的migrate type有自己的物理页链表,
* 其中hot cache的物理页存放在链表的前半部,
* 而cold cache存在放链表的后半部.
*/
struct list_head lists[MIGRATE_PCPTYPES];
};
申请
一般来说申请的流程比较简单,首先找到该cpu对应的链表,
如果其中存在物理页,之后根据 gfp_flag
中是否有
__GFP_COLD
标记,从而选择 cold/hot
cache
.
那么如果当前链表为空呢?这时就需要从 zone
buddy
系统中
拿一些物理页用于填充.
这部分的操作在函数 rmqueue_bulk
中:
static int rmqueue_bulk(struct zone *zone, unsigned int order,
unsigned long count, struct list_head *list,
int migratetype, int cold)
{
int mt = migratetype, 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))
list_add(&page->lru, list);
else
list_add_tail(&page->lru, list);
...
set_freepage_migratetype(page, mt);
list = &page->lru;
...
}
...
spin_unlock(&zone->lock);
return i;
}
注意,这里的 order
为0,也是说每次申请一个物理页,
而 count
则为 pcp->batch
. __rmqueue
内部的流程可以参见上一篇文章的分配章节
.
释放
释放的流程和申请类似,首先依然是找到当前cpu对应的链表,
如果是 cold cache
则将物理页挂入链表尾部,
反之插入到链表开头:
pcp = &this_cpu_ptr(zone->pageset)->pcp;
if (cold)
list_add_tail(&page->lru, &pcp->lists[migratetype]);
else
list_add(&page->lru, &pcp->lists[migratetype]);
pcp->count++;
如果该链表中的物理页超过了设定的阈值( pcp->high
),
那么需要将部分页归还给 zone
buddy
系统:
if (pcp->count >= pcp->high) {
free_pcppages_bulk(zone, pcp->batch, pcp);
pcp->count -= pcp->batch;
}
这里由于存在多个 migrate
type
,故当归还的时候,
采用了平均归还的策略,给每个 migrate
type
都归还物理页.
这里需要注意的是,给还的 migrate
type
只有这几个:
MIGRATE_UNMOVABLE,
MIGRATE_RECLAIMABLE,
MIGRATE_MOVABLE,
假如总共有6个物理页需要归还,
那么 MIGRATE_UNMOVABLE
会得到1个物理页,
MIGRATE_RECLAIMABLE
会得到2个物理页,
而 MIGRATE_MOVABLE
会得到3个物理页.
当然,如果中间某个 migrate
type
的链表为空,
则会跳过该类型,而下个类型会被多归还一个:
do {
batch_free++;
if (++migratetype == MIGRATE_PCPTYPES)
migratetype = 0;
list = &pcp->lists[migratetype];
} while (list_empty(list));
确定了归还的 migrate
type
的链表( list
)
以及归还的物理页个数( batch_free
),
下面就是将其返回给 buddy
系统了,
这里的操作同释放物理页类型
(详见上一篇文章的释放章节).
do {
int mt; /* migratetype of the to-be-freed page */
page = list_entry(list->prev, struct page, lru);
/* must delete as __free_one_page list manipulates */
list_del(&page->lru);
mt = get_freepage_migratetype(page);
/* MIGRATE_MOVABLE list may include MIGRATE_RESERVEs */
__free_one_page(page, zone, 0, mt);
trace_mm_page_pcpu_drain(page, 0, mt);
...
} while (--to_free && --batch_free && !list_empty(list));
注意,这里是从链表尾部开始释放,也就是说优先释放 cold
cache
的物理页.
FIN.