您的位置:首页 > 其它

再读高速缓存

2008-07-29 17:52 369 查看
快乐虾 http://blog.csdn.net/lights_joy/ lights@hb165.com



本文适用于
ADI bf561 DSP
优视BF561EVB开发板
uclinux-2008r1-rc8 (移植到vdsp5)
Visual DSP++ 5.0


欢迎转载,但请保留作者信息


内核经常需要请求和释放单个页面。为了提升系统性能,每个内存管理区zone定义了一个“每CPU”页面高速缓存。所有高速缓存包含一些预先分配的页,它们被用于满足本地CPU发出的单一内存请求。
实际上,这里为每个内存管理区和每个CPU提供了两个高速缓存:一个热高速缓存,它存放的页框中所包含的内容很可能就在CPU硬件高速缓存中;还有一个冷高速缓存。
内核使用两个位标来监视热高速缓存和冷高速缓存的大小:如果页个数低于下界low,内核通过buddy系统分配batch个单一页面来补充对应的高速缓存;否则,如果页框个数高过上界high,内核从高速缓存中释放batch个页框到buddy系统中。

1.1.1 相关数据结构

1.1.1.1 per_cpu_pageset

这个结构体用于描述高速缓存页的使用。它的定义在include/linux/mmzone.h中:
enum zone_stat_item {
/* First 128 byte cacheline (assuming 64 bit words) */
NR_FREE_PAGES,
NR_INACTIVE,
NR_ACTIVE,
NR_ANON_PAGES, /* Mapped anonymous pages */
NR_FILE_MAPPED, /* pagecache pages mapped into pagetables.
only modified from process context */
NR_FILE_PAGES,
NR_FILE_DIRTY,
NR_WRITEBACK,
/* Second 128 byte cacheline */
NR_SLAB_RECLAIMABLE,
NR_SLAB_UNRECLAIMABLE,
NR_PAGETABLE, /* used for pagetables */
NR_UNSTABLE_NFS, /* NFS unstable pages */
NR_BOUNCE,
NR_VMSCAN_WRITE,
NR_VM_ZONE_STAT_ITEMS };

struct per_cpu_pages {
int count; /* number of pages in the list */
int high; /* high watermark, emptying needed */
int batch; /* chunk size for buddy add/remove */
struct list_head list; /* the list of pages */
};

struct per_cpu_pageset {
struct per_cpu_pages pcp[2]; /* 0: hot. 1: cold */
s8 stat_threshold;
s8 vm_stat_diff[NR_VM_ZONE_STAT_ITEMS];
} ____cacheline_aligned_in_smp;
这个结构体将只用在struct zone当中,且通过zone_pcp这个宏来进行访问:
#define zone_pcp(__z, __cpu) (&(__z)->pageset[(__cpu)])
内核经常需要请求和释放单个页面。为了提升系统性能,每个内存管理区zone定义了一个“每CPU”页面高速缓存。所有高速缓存包含一些预先分配的页,它们被用于满足本地CPU发出的单一内存请求。
实际上,这里为每个内存管理区和每个CPU提供了两个高速缓存:一个热高速缓存,它存放的页框中所包含的内容很可能就在CPU硬件高速缓存中;还有一个冷高速缓存。
内核使用两个位标来监视热高速缓存和冷高速缓存的大小:如果页个数低于下界low,内核通过buddy系统分配batch个单一页面来补充对应的高速缓存;否则,如果页框个数高过上界high,内核从高速缓存中释放batch个页框到buddy系统中。
这个结构体的初始化由setup_pageset函数完成:
inline void setup_pageset(struct per_cpu_pageset *p, unsigned long batch)
{
struct per_cpu_pages *pcp;

memset(p, 0, sizeof(*p));

pcp = &p->pcp[0]; /* hot */
pcp->count = 0;
pcp->high = 6 * batch;
pcp->batch = max(1UL, 1 * batch);
INIT_LIST_HEAD(&pcp->list);

pcp = &p->pcp[1]; /* cold*/
pcp->count = 0;
pcp->high = 2 * batch;
pcp->batch = max(1UL, batch/2);
INIT_LIST_HEAD(&pcp->list);
}
对于64M的SDRAM而言,batch的值为3。

1.1.2 per_cpu_pageset初始化

内核为每个zone定义了一个“每CPU”页面高速缓存。所有高速缓存包含一些预先分配的页,它们被用于满足本地CPU发出的单一内存请求。实际上,这里为每个内存管理区和每个CPU提供了两个高速缓存:一个热高速缓存,它存放的页框中所包含的内容很可能就在CPU硬件高速缓存中;还有一个冷高速缓存。
内核使用两个位标来监视热高速缓存和冷高速缓存的大小:如果页个数低于下界low,内核通过buddy系统分配batch个单一页面来补充对应的高速缓存;否则,如果页框个数高过上界high,内核从高速缓存中释放batch个页框到buddy系统中。

1.1.2.1 zone_pcp_init

这个函数在free_area_init_core函数中调用。其实现在mm/page_alloc.c中:
static __meminit void zone_pcp_init(struct zone *zone)
{
int cpu;
unsigned long batch = zone_batchsize(zone);

for (cpu = 0; cpu < NR_CPUS; cpu++) {
setup_pageset(zone_pcp(zone,cpu), batch);
}
if (zone->present_pages)
printk(KERN_DEBUG " %s zone: %lu pages, LIFO batch:%lu/n",
zone->name, zone->present_pages, batch);
}
这个函数的功能也简单,就是初始化zone结构体中的pageset成员。
在这里有:
#define zone_pcp(__z, __cpu) (&(__z)->pageset[(__cpu)])

1.1.2.2 zone_batchsize

这个函数用于计算batchsize。

static int __devinit zone_batchsize(struct zone *zone)
{
int batch;

/*
* The per-cpu-pages pools are set to around 1000th of the
* size of the zone. But no more than 1/2 of a meg.
*
* OK, so we don't know how big the cache is. So guess.
*/
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;

/*
* Clamp the batch to a 2^n - 1 value. Having a power
* of 2 value was found to be more likely to have
* suboptimal cache aliasing properties in some cases.
*
* For example if 2 tasks are alternately allocating
* batches of pages, one task can end up with a lot
* of pages of one half of the possible page colors
* and the other with pages of the other colors.
*/
batch = (1 << (fls(batch + batch/2)-1)) - 1;

return batch;
}
对于64M内存,batch计算的结果为3。

1.1.2.3 setup_pageset

inline void setup_pageset(struct per_cpu_pageset *p, unsigned long batch)
{
struct per_cpu_pages *pcp;

memset(p, 0, sizeof(*p));

pcp = &p->pcp[0]; /* hot */
pcp->count = 0;
pcp->high = 6 * batch;
pcp->batch = max(1UL, 1 * batch);
INIT_LIST_HEAD(&pcp->list);

pcp = &p->pcp[1]; /* cold*/
pcp->count = 0;
pcp->high = 2 * batch;
pcp->batch = max(1UL, batch/2);
INIT_LIST_HEAD(&pcp->list);
}
这个函数比较简单,没啥可说的。

1.1.3 页面回收

当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.4 换页策略

内核中当高速缓存的页面较多时,会将部分页面切换到可用内存的链表中,这个操作由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);
}
因为在页面回收时是将要回收的页面插入到双链表的表头,而从上述函数中可以看出,在将页面切换出高速缓存的时候,也是从链表头按顺序进行的,因此整个切换策略就是后进先出,即最后进入高速缓存的先切换出去。

1.1.5 缓存填充

当向内核请求一个页时,如果其order为0,即单页,那么内核将从缓存中进行分配,如果缓存中没有可用的页,那么内核将调用rmqueue_bulk函数取一些页填充到缓存中。
/*
* Obtain a specified number of elements from the buddy allocator, all under
* a single hold of the lock, for efficiency. Add them to the supplied list.
* Returns the number of new pages which were placed at *list.
*/
static int rmqueue_bulk(struct zone *zone, unsigned int order,
unsigned long count, struct list_head *list)
{
int i;

spin_lock(&zone->lock);
for (i = 0; i < count; ++i) {
struct page *page = __rmqueue(zone, order);
if (unlikely(page == NULL))
break;
list_add_tail(&page->lru, list);
}
spin_unlock(&zone->lock);
return i;
}
就是调用__rmqueue函数取得指定数量的可用内存页。
需要注意的是内核调用此函数时使用的参数:
pcp->count = rmqueue_bulk(zone, 0,
pcp->batch, &pcp->list);
order为0,而要求的count为缓存指定的batch,对于64M内存,此值为3。




参考资料

uClinux2.6(bf561)中的CPLB(2008/2/19)
uclinux2.6(bf561)中的bootmem分析(1):猜测(2008/5/9)
uclinux2.6(bf561)中的bootmem分析(2):调用前的参数分析(2008/5/9)
uclinux2.6(bf561)中的bootmem分析(3):init_bootmem_node(2008/5/9)
uclinux2.6(bf561)中的bootmem分析(4):alloc_bootmem_pages(2008/5/9)
uclinux2.6(bf561)内核中的paging_init(2008/5/12)
uclinux-2008r1(bf561)内核的icache支持(1):寄存器配置初始化(2008/5/16)
uclinux-2008r1(bf561)内核的icache支持(2):icplb_table的生成(2008/5/16)
uclinux-2008r1(bf561)内核的icache支持(3):__fill_code_cplbtab(2008/5/16)
uclinux-2008r1(bf561)内核的icache支持(4):换页问题(2008/5/16)
再读uclinux-2008r1(bf561)内核中的bootmem(2008/6/3)
uclinux-2008r1(bf561)内核中与存储管理相关的几个全局变量(2008/6/4)
uclinux-2008r1(bf561)内核存储区域初探(2008/6/4)
uclinux-2008r1(bf561)内核中的zonelist初始化(2008/6/5)
uclinux-2008r1(bf561)内核中内存管理相关的几个结构体(2008/6/5)
再读内核存储管理(1):相关的全局变量(2008/6/17)
再读内核存储管理(2):相关的数据结构(2008/6/17)
再读内核存储管理(3):bootmem分配策略(2008/6/17)
再读内核存储管理(4):存储区域管理(2008/6/17)
再读内核存储管理(5):buddy算法(2008/6/17)
再读内核存储管理(6):高速缓存的应用(2008/6/17)
再读内核存储管理(7):icache支持(2008/6/17)
再读内核存储管理(8):片内SRAM的使用(2008/6/17)
初读SLAB(2008/6/26)
三读bootmem(2008/7/24)
再读uclinux-2008r1(bf561)内核存储区域管理(1):相关数据结构(2008/7/25)
再读uclinux-2008r1(bf561)内核存储区域管理(2):可用页表初始化(2008/7/25)
再读uclinux-2008r1(bf561)内核存储区域管理(3):zone初始化(2008-7-25)
再读uclinux-2008r1(bf561)内核存储区域管理(4):zonelist初始化(2008-7-25)
再读uclinux-2008r1(bf561)内核存储区域管理(5):page初始化(2008-7-25)
再读BUDDY算法2008-7-29
内容来自用户分享和网络整理,不保证内容的准确性,如有侵权内容,可联系管理员处理 点击这里给我发消息
标签: