再读高速缓存

快乐虾

http://blog.csdn.net/lights_joy/

[email protected]

 

 

本文适用于

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   缓存填充

当向内核请求一个页时,如果其order0,即单页,那么内核将从缓存中进行分配,如果缓存中没有可用的页,那么内核将调用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);

order0,而要求的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

 

 

 

 

 

 

 

 

 

你可能感兴趣的:(数据结构,算法,struct,list,存储,colors)