Linux内核机制—percpu

一、per-cpu变量简介

在多处理器系统中,每处理器变量为每个cpu都生成一个变量的副本,每个处理器使用自己的副本,从而避免了处理器之间的互斥和同步,提高了程序的执行速度。每处理器变量分为静态per-cpu变量和动态per-cpu变量。

 

二、静态per-cpu变量

1. 静态per-cpu变量使用

(1) DEFINE_PER_CPU(type, name) 宏用来定义静态per-cpu变量

(2) DECLARE_PER_CPU(type, name) 宏用来声明per-cpu变量。

(3) EXPORT_PER_CPU_SYMBOL(val)/EXPORT_PER_CPU_SYMBOL_GPL(val) 宏将静态per-cpu变量导出到符号表。

 

2. 静态per-cpu变量说明

(1) 相关宏展开,这些宏定义在 include/linux/percpu-defs.h 中

#define DEFINE_PER_CPU(type, name)  __percpu __attribute__((section(.data..percpu))) __typeof__(type) name

#define DECLARE_PER_CPU(type, name) extern __percpu __attribute__((section(.data..percpu))) __typeof__(type) name //比上面多了一个extern

可见普通per-cpu变量存放在 ".data..percpu" 节中。


3. 定义每处理器变量常用变体

DEFINE_PER_CPU_FIRST(type, name) //定义必须在per-cpu变量集合中最先出现的per-cpu变量

DEFINE_PER_CPU_SHARED_ALIGNED(type, name) //定义和处理器缓存行对齐的per-cpu变量,仅在SMP系统中需要和处理器缓存行对齐,响应的声明是 DECLARE_PER_CPU_SHARED_ALIGNED

DEFINE_PER_CPU_ALIGNED(type, name) //定义和处理器缓存行对齐的per-cpu变量,无论是否是SMP系统都要和缓存行对齐

DEFINE_PER_CPU_PAGE_ALIGNED(type, name) //定义和叶长度对齐的per-cpu变量

DEFINE_PER_CPU_READ_MOSTLY(type, name) //定义以读为主的per-cpu变量

 

三、动态per-cpu变量

1. 动态per-cpu变量的分配

一般使用 alloc_percpu() 为动态per-cpu变量分配内存,根据使用场景,也可以使用其变体

//include/linux/percpu-defs.h
alloc_percpu_gfp(type, gfp)
alloc_percpu(type)

#define alloc_percpu_gfp(type, gfp)                    \
    (typeof(type) __percpu *)__alloc_percpu_gfp(sizeof(type), __alignof__(type), gfp)
#define alloc_percpu(type)                        \
    (typeof(type) __percpu *)__alloc_percpu(sizeof(type), __alignof__(type))

void __percpu *__alloc_percpu_gfp(size_t size, size_t align, gfp_t gfp)
{
    return pcpu_alloc(size, align, false, gfp);
}
EXPORT_SYMBOL_GPL(__alloc_percpu_gfp);

 

2. 访问动态per-cpu变量

(1) 获取per-cpu变量的地址和值

per_cpu_ptr(ptr, cpu) //获取per-cpu变量地址,没有 per_cpu_val()
per_cpu(var, cpu) //获取per-cpu变量的值

this_cpu_ptr(ptr) //获取当前per-cpu变量的地址
get_cpu_var(var) //获取当前per-cpu变量的值

get_cpu_ptr(var) //禁止抢占,并返回当前处理器变量的副本的地址
put_cpu_ptr(var) //开启抢占,这两个宏需要配对使用,以确保获取per-cpu变量时不会被其它进程抢占

get_cpu_var(var) //禁止抢占,并返回当前处理器变量的副本的值
put_cpu_var(var) //开启抢占,这两个宏需要配对使用,以确保获取per-cpu变量时不会被其它进程抢占

(2) this_cpu_ptr(ptr) 展开后为:

unsigned long __ptr = (unsigned long) (ptr);
(typedef(__ptr))((__ptr) + per_cpu_offset(cpu));

 

3. 动态per-cpu变量的释放

//include/linux/percpu-defs.h
free_percpu(void __percpu * ptr)

 

四、per-cpu常用接口

1. 一些没有关抢占关中断保护的

//include/linux/percpu-defs.h
#define raw_cpu_sub(pcp, val)        raw_cpu_add(pcp, -(val))
#define raw_cpu_inc(pcp)        raw_cpu_add(pcp, 1)
#define raw_cpu_dec(pcp)        raw_cpu_sub(pcp, 1)
#define raw_cpu_sub_return(pcp, val)    raw_cpu_add_return(pcp, -(typeof(pcp))(val))
#define raw_cpu_inc_return(pcp)        raw_cpu_add_return(pcp, 1)
#define raw_cpu_dec_return(pcp)        raw_cpu_add_return(pcp, -1)
...

2. 一些带有关中断关抢占保护的接口

#define this_cpu_read(pcp)        __pcpu_size_call_return(this_cpu_read_, pcp)
#define this_cpu_write(pcp, val)    __pcpu_size_call(this_cpu_write_, pcp, val)
#define this_cpu_add(pcp, val)        __pcpu_size_call(this_cpu_add_, pcp, val)
#define this_cpu_and(pcp, val)        __pcpu_size_call(this_cpu_and_, pcp, val)
...

#define this_cpu_sub(pcp, val)        this_cpu_add(pcp, -(typeof(pcp))(val))
#define this_cpu_inc(pcp)        this_cpu_add(pcp, 1)
#define this_cpu_dec(pcp)        this_cpu_sub(pcp, 1)
...

 

五、使用案例

1. build_sched_domain 中的使用

/* per-cpu 的二级指针 */
struct sd_data { //kernel/sched/topology.c
    struct sched_domain *__percpu *sd;
    struct sched_domain_shared *__percpu *sds;
    struct sched_group *__percpu *sg;
    struct sched_group_capacity *__percpu *sgc;
};

static struct sched_group *get_group(int cpu, struct sd_data *sdd)
{
    struct sched_domain *sd = *per_cpu_ptr(sdd->sd, cpu);
    struct sched_group *sg;

    sg = *per_cpu_ptr(sdd->sg, cpu);

    sg->sgc->min_capacity = SCHED_CAPACITY_SCALE;

    return sg;
}

void update_top_cache_domain(int cpu)
{
    rcu_assign_pointer(per_cpu(sd_llc, cpu), sd);
    per_cpu(sd_llc_size, cpu) = size;
}

 

2. 相关代码段

(1) per-cpu 变量

static DEFINE_PER_CPU(struct cpu_status, perf_cpu_stats); //per-cpu变量

for_each_present_cpu(cpu) {
    per_cpu(perf_cpu_stats, cpu).min = 0;
    per_cpu(perf_cpu_stats, cpu).max = UINT_MAX;

for_each_cpu(i, limit_mask)
    i_cpu_stats = &per_cpu(perf_cpu_stats, i); //取变量地址

(2) per-cpu数组

static DEFINE_PER_CPU_SHARED_ALIGNED(struct worker_pool[NR_STD_WORKER_POOLS], cpu_worker_pools); //per-cpu的数组

#define for_each_cpu_worker_pool(pool, cpu)                \
    for ((pool) = &per_cpu(cpu_worker_pools, cpu)[0];        \
         (pool) < &per_cpu(cpu_worker_pools, cpu)[NR_STD_WORKER_POOLS]; \
         (pool)++)

(3) 定义per-cpu的spinlock锁

static DEFINE_PER_CPU(spinlock_t, thres_lock) = __SPIN_LOCK_UNLOCKED(thres_lock);
spin_lock_irqsave(&per_cpu(thres_lock, cpu), flags);
spin_unlock_irqrestore(&per_cpu(thres_lock, cpu), flags);

 

六、内核相关功能

1. 每处理器计数器

有些计数器,不需要时刻知道其准确值,计数的准确值和近似值对我们没有差别,这种情况下可以使用每处理器计数器来加速多处理器系统中计数器的操作。每处理器计数器的设计思路是:计数器有个总的计数值,每个处理器有一个临时计数值,每个处理器先把计数累加到自己的临时计数值上,当临时计数值达到或超过阈值的时候,再把临时计数值累加到总计数值上。

struct percpu_counter { //include/linux/percpu_counter.h
    /* 保护总计数值 */
    raw_spinlock_t lock;
    /* 总计数值 */
    s64 count;
#ifdef CONFIG_HOTPLUG_CPU
    struct list_head list;    /* All percpu_counters are on a list */
#endif
    /* 每个每处理器变量对应一个临时级数值 */
    s32 __percpu *counters;
};

相关函数:

percpu_counter_init(fbc, value, gfp) //初始化per-cpu计数器,fbc:计数器地址,value:初始值,gfp:分配per-cpu变量内存标志
percpu_counter_add(struct percpu_counter * fbc, s64 amount) //把计数累加到每处理器计数器上

取近似值函数:

percpu_counter_read(struct percpu_counter * fbc) //可能返回负数
percpu_counter_read_positive(struct percpu_counter * fbc)

取准确值函数:

percpu_counter_sum(struct percpu_counter * fbc) //可能返回负数
percpu_counter_sum_positive(struct percpu_counter * fbc)

销毁每处理器计数器:

percpu_counter_destroy(struct percpu_counter * fbc)

 

七、补充

1. 需要关抢占后读取per-cpu变量,对于 smp_processor_id() 的使用,可以使用 get_cpu()/put_cpu() 来保护临界区。

//include/linux/smp.h
#define get_cpu()        ({ preempt_disable(); __smp_processor_id(); })
#define put_cpu()        preempt_enable()

2. per-cpu二级指针的一个奇特用法

/* 注意是per-cpu的结构体变量,不是指针 */
static DEFINE_PER_CPU(struct cpu_stopper, cpu_stopper);

struct smp_hotplug_thread {
    struct task_struct        * __percpu *store;
}

/* 这样初始化,一个指向per-cpu的二级指针,却指向一个per-cpu变量的一级指针 */
static struct smp_hotplug_thread cpu_stop_threads = { //stop_machine.c
    .store            = &cpu_stopper.thread,
}

2. per-cpu变量做函数参数

void * __percpu *scratches = alloc_percpu(void *);

for_each_possible_cpu(i)
    *per_cpu_ptr(scratches, i) = scratch;


static void crypto_scomp_free_scratches(void * __percpu *scratches)
{
    int i;

    if (!scratches)
        return;

    for_each_possible_cpu(i)
        vfree(*per_cpu_ptr(scratches, i));

    free_percpu(scratches);
}

另外一个例子:

struct file_lock_list_struct {
    spinlock_t        lock;
    struct hlist_head    hlist;
};
static DEFINE_PER_CPU(struct file_lock_list_struct, file_lock_list);

seq_hlist_start_percpu(&file_lock_list.hlist, &iter->li_cpu, *pos);

struct hlist_node *seq_hlist_start_percpu(struct hlist_head __percpu *head, int *cpu, loff_t pos)
{
    struct hlist_node *node;

    for_each_possible_cpu(*cpu) {
        hlist_for_each(node, per_cpu_ptr(head, *cpu)) {
            if (pos-- == 0)
                return node;
        }
    }
    return NULL;
}

3. per-cpu解引用

per_cpu(bh_accounting, cpu).nr = 0;
per_cpu_ptr(stats, c)->s.vn_active = vn_active;

rmqueue_pcplist
    pcp = &this_cpu_ptr(zone->pageset)->pcp;

 

 

 

 

优秀博文:

http://www.wowotech.net/kernel_synchronization/per-cpu.html

 

posted on 2022-07-23 21:00  Hello-World3  阅读(1165)  评论(0编辑  收藏  举报

导航