DSP

再读高速缓存

2019-07-13 16:22发布

快乐虾 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); } 对于64MSDRAM而言,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)