内存管理-11-buddy伙伴子系统-2-PCP-Per-CPU页缓存

基于msm-5.4

一、概述

1. 实现背景

buddy子系统管理的物理页面,绝大多数都是放在 zone::free_area[] 中的链表中,少部分放在 zone::lowmem_reserve[] 中。还有少量页面放在 zone::__percpu pageset 这个每CPU变量中,每种迁移类型也都对应一个链表,但是没有order,都是单页大小的内存块。

由于前两个部分对所有CPU来说是全局的,链表的维护需要拿锁,这个同步带来开销。现在多核CPU是有多级缓存的,只有L1是私有的,L2/L3都是公有的,不同的CPU去访问同一个变量的时候,又涉及到缓存的同步刷新问题,这也会带来一定的开销。

使用本地缓存的页面不需要去申请锁,单个物理页面的申请释放是最频繁的,如果将单个物理页面的申请与释放做成一个每CPU的缓存,这样就不需要去拿全局锁了。Per-CPU页帧缓存的目的就是提升申请物理内存的性能,减少锁和cache缓存同步带来的开销。


2. 实现原理

struct zone 中有一个每CPU的 pageset 成员。

struct zone {
    struct per_cpu_pageset __percpu *pageset;
}

struct per_cpu_pageset {
    struct per_cpu_pages pcp;
    s8 stat_threshold;
    s8 vm_stat_diff[NR_VM_ZONE_STAT_ITEMS];
};

struct per_cpu_pages {
    int count;        //lists链表中页面的个数
    int high;        //高水位,需要清空
    int batch;        //伙伴块添加/删除的大小
    struct list_head lists[MIGRATE_PCPTYPES]; //每个迁移类型一个链表,链表上的页面都是单个物理页
};

在释放单个物理页的时候,不会释放给伙伴系统,而是直接释放到自己的本地 per_cpu_pages::lists 链表上。等到下次再申请单个物理页面时,首先去本地链表上去拿,若拿不到再到全局伙伴系统中去拿。

这个本地链表上缓存的单个物理页面的个数不是无限增加的,当超过 _watermark[] 水位值,伙伴系统中页面数较少的时候,会再将每CPU的页缓存释放到伙伴系统的全局链表上。

 

二、Linux-6.1上情况

Linux-5.4 上的 PCP 还只能缓存单页,但是 Linux-6.1 内核上可以缓存 order 0-3(通常) + 特殊的 THP(order=10)。pcp缓存链表的个数一共有 17 个。

1. pcp 允许的 order

/*
 * order <=3 或 order==10(THP) 返回 true,否则返回 false, pcp可以缓存 2^0--2^3 和
 * 2^10大小的页。
 *
 * get_page_from_freelist() --> rmqueue() --> pcp_allowed_order()
 */
static inline bool pcp_allowed_order(unsigned int order) //page_alloc.c
{
    if (order <= PAGE_ALLOC_COSTLY_ORDER) //3
        return true;
#ifdef CONFIG_TRANSPARENT_HUGEPAGE //1
    if (order == pageblock_order) //10
        return true;
#endif
    return false;
}

 

2. 数据结构

//include/linux/mmzone.h

#ifdef CONFIG_TRANSPARENT_HUGEPAGE
#define NR_PCP_THP 1
#else
#define NR_PCP_THP 0
#endif
/* pcp list 除了 2^0 上还有 2^1, 2^2, 2^3 大小的页块 */
#define NR_LOWORDER_PCP_LISTS (MIGRATE_PCPTYPES * (PAGE_ALLOC_COSTLY_ORDER + 1)) //4*(3+1) = 16
#define NR_PCP_LISTS (NR_LOWORDER_PCP_LISTS + NR_PCP_THP) //16 + 1 = 17

struct per_cpu_pages {
    spinlock_t lock;
    int count;
    int high;
    int batch;
    short free_factor;
    struct list_head lists[NR_PCP_LISTS];
} ____cacheline_aligned_in_smp;

成员介绍:

lock:

保护 lists[] 的自旋锁(处理并发 drain).


count:

缓存中的总页数(按高阶等价计).


high:

触发释放的高水位.


batch:

补货/释放的批量单位


free_factor:

动态调整释放批量大小(自适应)


lists[]:

lists 数组的组织方式:

lists[0]   <-- order-0, MIGRATE_UNMOVABLE
lists[1]   <-- order-1, MIGRATE_UNMOVABLE
lists[2]   <-- order-2, MIGRATE_UNMOVABLE
lists[3]   <-- order-3, MIGRATE_UNMOVABLE
lists[4]   <-- order-0, MIGRATE_MOVABLE
lists[5]   <-- order-1, MIGRATE_MOVABLE
...
lists[16]  <-- THP 专用(如果启用 CONFIG_TRANSPARENT_HUGEPAGE)

判断函数:

static inline unsigned int order_to_pindex(int migratetype, int order) //page_alloc.c
{
    int base = order;

#ifdef CONFIG_TRANSPARENT_HUGEPAGE
    if (order > PAGE_ALLOC_COSTLY_ORDER) { //3
        VM_BUG_ON(order != pageblock_order); //10
        return NR_LOWORDER_PCP_LISTS; //16, 是下标,表示 lists[16]
    }
#else
    VM_BUG_ON(order > PAGE_ALLOC_COSTLY_ORDER);
#endif

    return (MIGRATE_PCPTYPES * base) + migratetype; // 4 * order + migratetype, 是数组下标
}

 

3. 调试节点

(1) /proc/zoneinfo

# cat /proc/zoneinfo
...
    cpu: 3
              count: 3552 //打印 count 成员
              high:  4123 //打印 high 成员
              batch: 63  //打印 batch 成员
...

 

posted on 2024-07-02 14:43  Hello-World3  阅读(182)  评论(0)    收藏  举报

导航