每处理器变量和每处理器计数器

每处理器变量

“每处理器变量”常被称为 per-CPU 变量(per-processor variable 或 per-CPU variable),在多核系统和操作系统内核中是一种优化手段,主要用于减少处理器之间的数据竞争(cache line bouncing)和提高并发性能

在多处理器系统中,每处理器变量为每个处理器生成一个变量量的副本,每个处理器访问自己的副本,从而避免了处理器之间的互斥和处理器缓存之间的的同步,提高了程序的执行速度。

基本原理

  1. 核心思想:为系统中的每个 CPU 核心创建变量的独立副本
  2. 访问方式:当代码运行时,自动访问当前 CPU 对应的变量副本
  3. 优势:避免了多 CPU 同时访问共享数据时的锁竞争

实现机制

存储布局

  • 编译阶段:所有 per-CPU 变量被收集到特殊的 <font style="color:rgb(64, 64, 64);">.data..percpu</font>
  • 启动阶段:内核为每个 CPU 复制这个段,创建独立的副本

地址转换

访问 per-CPU 变量时,内核通过以下方式转换地址:

每个CPU上变量的实际地址 = 变量基地址 + __per_cpu_offset[cpu_number]

每处理器变量分为静态和动态两种。

静态每处理器变量

定义per-cpu变量DEFINE_PER_CPU(type, name),声明per-cpu变量DECLARE_PER_CPU(type, name)

/*
 * Variant on the per-CPU variable declaration/definition theme used for
 * ordinary per-CPU variables.
 */
#define DECLARE_PER_CPU(type, name)					\
	DECLARE_PER_CPU_SECTION(type, name, "")

#define DEFINE_PER_CPU(type, name)					\
	DEFINE_PER_CPU_SECTION(type, name, "")

把宏DEFINE_PER_CPU(type, name)展开以后是:

__attribute__((section(".data..percpu"))) __typeof__(type) name

可以看出,普通的静态每处理器变量存放在.data..percpu段中(elf格式文件的section)。


定义静态每处理器变量还有一些DEFINE_PER_CPU的变体,具体参考include/linux/percpu-defs.h


如果想要静态每处理器变量可以被其他内核模块引用,需要使用EXPORT_PER_CPU_SYMBOL(var)导出到符号表。

使用示例

DEFINE_PER_CPU(int, request_count); // 静态方式定义int类型per-cpu变量reqeust_count

void handle_request(void)
{
    // 简单增加计数器
    this_cpu_inc(request_count);
    
    // 更复杂的操作
    int *count = this_cpu_ptr(&request_count);
    if (*count > MAX_REQUESTS) {
        schedule_work(&flush_work);
        *count = 0;
    }
}

动态每处理器变量

为动态每处理器变量分配和释放内存的函数如下:

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

extern void free_percpu(void __percpu *__pdata);
/**
 * __alloc_percpu - allocate dynamic percpu area
 * @size: size of area to allocate in bytes
 * @align: alignment of area (max PAGE_SIZE)
 *
 * Equivalent to __alloc_percpu_gfp(size, align, %GFP_KERNEL).
 */
void __percpu *__alloc_percpu(size_t size, size_t align)
{
	return pcpu_alloc(size, align, false, GFP_KERNEL);
}
EXPORT_SYMBOL_GPL(__alloc_percpu);

为动态每处理器变量分配内存还有一些alloc_percpu的变体,具体参考include/linux/percpu.h

使用示例

int __percpu *dynamic_counter = alloc_percpu(int); // 运行时动态分配per-CPU变量
if (!dynamic_counter) {
    // 处理分配失败
}

访问每处理器变量

// 获取当前CPU的变量的指针(不禁止抢占)
this_cpu_ptr(ptr);

// 获取指定CPU的变量的指针
per_cpu_ptr(ptr, cpu);

// 获取指定CPU的变量的值
per_cpu(var, cpu);

// 获取当前CPU的变量的地址(下面两个宏成对使用)
get_cpu_ptr(var); // 禁止内核抢占
put_cpu_ptr(var); // 开启内核抢占

// 获取当前CPU的变量的值(下面两个宏成对使用)
get_cpu_var(var); // 禁止内核抢占
put_cpu_var(var); // 开启内核抢占


// 其它per-cpu方法参考include/linux/percpu-defs.h

下面具体介绍一下上面这些访问per-cpu变量的方法。

this_cpu_ptr(ptr)

获取当前CPU的变量的指针(不禁止内核抢占)

宏展开

this_cpu_ptr(ptr)展开结果:

unsigned long __ptr;
__ptr = (unsigned long) (ptr);
(typeof(ptr)) (__ptr + per_cpu_offset(raw_smp_processor_id()))

可以看出,当前CPU的变量副本的地址等于变量基准地址加上当前处理器的偏移

基本使用方法
// 获取指向当前CPU的per-CPU变量的指针
type *var_ptr = this_cpu_ptr(&per_cpu_var);
测试代码示例

以下是一个简单的内核模块示例,演示如何使用 **<font style="color:rgb(64, 64, 64);background-color:rgb(236, 236, 236);">this_cpu_ptr</font>**

#include <linux/module.h>
#include <linux/kernel.h>
#include <linux/percpu.h>
#include <linux/smp.h>

// 定义一个per-CPU变量
static DEFINE_PER_CPU(int, my_percpu_counter);

// 模块初始化函数
static int __init percpu_test_init(void)
{
    int *counter_ptr;
    int cpu;

    pr_info("Per-CPU test module loaded\n");

    // 获取当前CPU的指针并操作
    counter_ptr = this_cpu_ptr(&my_percpu_counter);
    *counter_ptr = 100;
    pr_info("CPU %d: Set local counter to %d\n", smp_processor_id(), *counter_ptr);

    // 显示所有CPU的计数器值
    for_each_possible_cpu(cpu) {
        pr_info("CPU %d: counter = %d\n", 
                cpu, *per_cpu_ptr(&my_percpu_counter, cpu));
    }

    return 0;
}

// 模块退出函数
static void __exit percpu_test_exit(void)
{
    pr_info("Per-CPU test module unloaded\n");

    // 显示退出时的计数器值
    pr_info("CPU %d: Final counter value = %d\n",
            smp_processor_id(), *this_cpu_ptr(&my_percpu_counter));
}

module_init(percpu_test_init);
module_exit(percpu_test_exit);

MODULE_LICENSE("GPL");
MODULE_AUTHOR("congchp");
MODULE_DESCRIPTION("Per-CPU variable test module");
obj-m:=this_cpu_ptr.o	

CURRENT_PAHT:=$(shell pwd) 
LINUX_KERNEL:=$(shell uname -r)   

LINUX_KERNEL_PATH:=/usr/src/linux-headers-$(LINUX_KERNEL)
all:

	make -C $(LINUX_KERNEL_PATH) M=$(CURRENT_PAHT) modules

clean:

	make -C $(LINUX_KERNEL_PATH) M=$(CURRENT_PAHT) clean

测试结果如下:

[97432.747362] Per-CPU test module loaded
[97432.747365] CPU 0: Set local counter to 100
[97432.747366] CPU 0: counter = 100
[97432.747367] CPU 1: counter = 0
[97432.747367] CPU 2: counter = 0
[97432.747368] CPU 3: counter = 0
[97432.747368] CPU 4: counter = 0
[97432.747369] CPU 5: counter = 0

per_cpu_ptr(ptr, cpu)

基本使用方法
// 获取指向指定CPU的per-CPU变量的指针
type *var_ptr = per_cpu_ptr(&per_cpu_var, cpu_id);
测试代码示例
#include <linux/module.h>
#include <linux/kernel.h>
#include <linux/percpu.h>
#include <linux/smp.h>
#include <linux/cpumask.h>

// 定义一个per-CPU变量
static DEFINE_PER_CPU(int, cpu_stats);

// 模块初始化函数
static int __init percpu_test_init(void)
{
    int cpu;
    
    pr_info("Per-CPU pointer test module loaded\n");
    
    // 初始化所有CPU的计数器
    for_each_possible_cpu(cpu) {
        int *ptr = per_cpu_ptr(&cpu_stats, cpu);
        *ptr = 100 + cpu;  // 每个CPU设置不同的初始值
    }
    
    // 打印所有CPU的计数器值
    for_each_online_cpu(cpu) {
        pr_info("CPU %d: stats = %d (direct access: %d)\n", 
               cpu, 
               *per_cpu_ptr(&cpu_stats, cpu),
               per_cpu(cpu_stats, cpu));
    }
    
    // 修改当前CPU的计数器值
    *per_cpu_ptr(&cpu_stats, smp_processor_id()) = 999;
    pr_info("Current CPU %d modified its value to %d\n",
           smp_processor_id(),
           per_cpu(cpu_stats, smp_processor_id()));
    
    return 0;
}

// 模块退出函数
static void __exit percpu_test_exit(void)
{
    int cpu;
    
    pr_info("Final per-CPU values:\n");
    for_each_possible_cpu(cpu) {
        pr_info("CPU %d: %d\n", cpu, *per_cpu_ptr(&cpu_stats, cpu));
    }
    
    pr_info("Per-CPU test module unloaded\n");
}

module_init(percpu_test_init);
module_exit(percpu_test_exit);

MODULE_LICENSE("GPL");
MODULE_AUTHOR("congchp");
MODULE_DESCRIPTION("per_cpu_ptr() test module");

测试结果如下:

[126158.431394] Final per-CPU values:
[126158.431397] CPU 0: 100
[126158.431398] CPU 1: 101
[126158.431399] CPU 2: 102
[126158.431399] CPU 3: 103
[126158.431400] CPU 4: 999
[126158.431400] CPU 5: 105
[126158.431401] Per-CPU test module unloaded

per_cpu(var, cpu)

获取指定CPU的变量的值,类似C++中的引用。

基本使用方法
// 获取指定CPU的per-CPU变量的值
value = per_cpu(var, cpu_id);

// 设置指定CPU的per-CPU变量的值
per_cpu(var, cpu_id) = new_value;
测试代码示例
#include <linux/module.h>
#include <linux/kernel.h>
#include <linux/percpu.h>
#include <linux/smp.h>
#include <linux/cpumask.h>

// 定义两个per-CPU变量
static DEFINE_PER_CPU(int, cpu_counter);
static DEFINE_PER_CPU(char[20], cpu_name);

// 模块初始化函数
static int __init percpu_test_init(void)
{
    int cpu;
    
    pr_info("Per-CPU access test module loaded\n");
    
    // 初始化所有可能的CPU的变量
    for_each_possible_cpu(cpu) {
        // 设置整型变量
        per_cpu(cpu_counter, cpu) = 1000 + cpu * 10;
        
        // 设置字符串变量
        snprintf(per_cpu(cpu_name, cpu), sizeof(per_cpu(cpu_name, cpu)),
                "CPU-%d", cpu);
    }
    
    // 打印所有在线CPU的变量值
    pr_info("Current CPU: %d\n", smp_processor_id());
    for_each_online_cpu(cpu) {
        pr_info("CPU %d: counter=%d, name='%s'\n", 
               cpu, 
               per_cpu(cpu_counter, cpu),
               per_cpu(cpu_name, cpu));
    }
    
    // 修改当前CPU的值
    per_cpu(cpu_counter, smp_processor_id()) = 9999;
    snprintf(per_cpu(cpu_name, smp_processor_id()), 
            sizeof(per_cpu(cpu_name, smp_processor_id())),
            "MODIFIED-CPU-%d", smp_processor_id());
    
    pr_info("Current CPU %d modified values: counter=%d, name='%s'\n",
           smp_processor_id(),
           per_cpu(cpu_counter, smp_processor_id()),
           per_cpu(cpu_name, smp_processor_id()));
    
    return 0;
}

// 模块退出函数
static void __exit percpu_test_exit(void)
{
    int cpu;
    
    pr_info("Final per-CPU values:\n");
    for_each_possible_cpu(cpu) {
        // 检查CPU是否在线
        if (cpu_online(cpu)) {
            pr_info("CPU %d (online): counter=%d, name='%s'\n",
                   cpu,
                   per_cpu(cpu_counter, cpu),
                   per_cpu(cpu_name, cpu));
        } else {
            pr_info("CPU %d (offline): counter=%d, name='%s'\n",
                   cpu,
                   per_cpu(cpu_counter, cpu),
                   per_cpu(cpu_name, cpu));
        }
    }
    
    pr_info("Per-CPU test module unloaded\n");
}

module_init(percpu_test_init);
module_exit(percpu_test_exit);

MODULE_LICENSE("GPL");
MODULE_AUTHOR("Your Name");
MODULE_DESCRIPTION("per_cpu() macro test module");

测试结果如下:

[155224.109328] Per-CPU access test module loaded
[155224.109332] Current CPU: 2
[155224.109333] CPU 0: counter=1000, name='CPU-0'
[155224.109334] CPU 1: counter=1010, name='CPU-1'
[155224.109335] CPU 2: counter=1020, name='CPU-2'
[155224.109335] CPU 3: counter=1030, name='CPU-3'
[155224.109336] CPU 4: counter=1040, name='CPU-4'
[155224.109336] CPU 5: counter=1050, name='CPU-5'
[155224.109337] Current CPU 2 modified values: counter=9999, name='MODIFIED-CPU-2'

get_cpu_ptr(var) & put_cpu_ptr(var)

get_cpu_ptr(var)put_cpu_ptr(var)成对使用,获取当前CPU变量的地址

get_cpu_ptr(var)put_cpu_ptr(var)之间的区域内核抢占被禁止

基本使用方法
// 获取当前CPU的per-CPU变量指针(禁止抢占)
ptr = get_cpu_ptr(&per_cpu_var);

// 操作per-CPU变量...

// 释放指针(允许抢占)
put_cpu_ptr(&per_cpu_var);
测试代码示例
#include <linux/module.h>
#include <linux/kernel.h>
#include <linux/percpu.h>
#include <linux/smp.h>
#include <linux/delay.h>

// 定义per-CPU数据结构
struct cpu_stats
{
    unsigned long packets;
    unsigned long bytes;
    char state[16];
};

static DEFINE_PER_CPU(struct cpu_stats, net_stats);

// 模拟网络包处理函数
static void process_packet(size_t len)
{
    struct cpu_stats *stats;

    // 安全获取当前CPU的统计指针
    stats = get_cpu_ptr(&net_stats);

    // 更新统计信息
    stats->packets++;
    stats->bytes += len;
    snprintf(stats->state, sizeof(stats->state), "CPU%d-UPDATING", smp_processor_id());

    // 模拟耗时操作(此时不会被抢占)
    mdelay(1);

    snprintf(stats->state, sizeof(stats->state), "CPU%d-READY", smp_processor_id());

    // 释放指针
    put_cpu_ptr(&net_stats);
}

// 模块初始化函数
static int __init percpu_test_init(void)
{
    int cpu;

    pr_info("Per-CPU atomic test module loaded\n");

    // 初始化所有CPU的统计
    for_each_possible_cpu(cpu)
    {
        struct cpu_stats *stats = per_cpu_ptr(&net_stats, cpu);
        stats->packets = 0;
        stats->bytes = 0;
        snprintf(stats->state, sizeof(stats->state), "CPU%d-INIT", cpu);
    }

    // 在当前CPU上处理几个包
    process_packet(1500);
    process_packet(68);
    process_packet(1024);

    // 显示所有CPU的统计
    pr_info("Current statistics:\n");
    for_each_online_cpu(cpu)
    {
        // 安全访问其他CPU的数据
        struct cpu_stats *stats = per_cpu_ptr(&net_stats, cpu);
        pr_info("CPU %d: packets=%lu bytes=%lu state=%s\n",
                cpu, stats->packets, stats->bytes, stats->state);
    }

    return 0;
}

// 模块退出函数
static void __exit percpu_test_exit(void)
{
    int cpu;
    unsigned long total_packets = 0, total_bytes = 0;

    pr_info("Final statistics:\n");

    // 计算总流量
    for_each_online_cpu(cpu)
    {
        struct cpu_stats *stats = get_cpu_ptr(&net_stats);

        pr_info("CPU %d: packets=%lu bytes=%lu\n",
                cpu, stats->packets, stats->bytes);

        total_packets += stats->packets;
        total_bytes += stats->bytes;

        put_cpu_ptr(&net_stats);
    }

    pr_info("TOTAL: packets=%lu bytes=%lu\n", total_packets, total_bytes);
    pr_info("Per-CPU test module unloaded\n");
}

module_init(percpu_test_init);
module_exit(percpu_test_exit);

MODULE_LICENSE("GPL");
MODULE_AUTHOR("congchp");
MODULE_DESCRIPTION("get_cpu_ptr/put_cpu_ptr test module");

测试结果如下:

[157709.313112] Per-CPU atomic test module loaded
[157709.316095] Current statistics:
[157709.316098] CPU 0: packets=0 bytes=0 state=CPU0-INIT
[157709.316099] CPU 1: packets=0 bytes=0 state=CPU1-INIT
[157709.316100] CPU 2: packets=0 bytes=0 state=CPU2-INIT
[157709.316100] CPU 3: packets=3 bytes=2592 state=CPU3-READY
[157709.316101] CPU 4: packets=0 bytes=0 state=CPU4-INIT
[157709.316102] CPU 5: packets=0 bytes=0 state=CPU5-INIT
与其它方法比较

this_cpu_ptr(ptr)相比,this_cpu_ptr(ptr)不禁止内核抢占。

get_cpu_var(var) & put_cpu_var(var)

get_cpu_var(var)put_cpu_var(var)成对使用,获取当前CPU变量的值(类似C++的引用)

get_cpu_var(var)put_cpu_var(var)之间的区域内核抢占被禁止

基本使用方法
// 获取当前CPU的per-CPU变量(禁止抢占)
get_cpu_var(var) = value;  // 设置值
value = get_cpu_var(var);  // 获取值

// 操作完成后释放(允许抢占)
put_cpu_var(var);
测试代码示例
#include <linux/module.h>
#include <linux/kernel.h>
#include <linux/percpu.h>
#include <linux/smp.h>
#include <linux/delay.h>

// 定义per-CPU变量
static DEFINE_PER_CPU(unsigned long, packet_counter);
static DEFINE_PER_CPU(unsigned long, byte_counter);

// 模拟网络包处理函数
static void process_network_packet(size_t len)
{
    // 安全获取当前CPU的计数器(禁用抢占)
    get_cpu_var(packet_counter)++;
    get_cpu_var(byte_counter) += len;

    // 模拟耗时处理(此时不会被抢占)
    mdelay(1);

    // 释放计数器(启用抢占)
    put_cpu_var(packet_counter);
    put_cpu_var(byte_counter);
}

// 模块初始化函数
static int __init percpu_test_init(void)
{
    int cpu;

    pr_info("Per-CPU variable test module loaded\n");

    // 初始化所有CPU的计数器
    for_each_possible_cpu(cpu)
    {
        per_cpu(packet_counter, cpu) = 0;
        per_cpu(byte_counter, cpu) = 0;
    }

    // 在当前CPU上处理几个包
    process_network_packet(1500);
    process_network_packet(68);
    process_network_packet(1024);

    // 显示所有CPU的统计
    pr_info("Current CPU statistics:\n");
    for_each_online_cpu(cpu)
    {
        // 安全访问其他CPU的数据
        unsigned long packets, bytes;

        packets = per_cpu(packet_counter, cpu);
        bytes = per_cpu(byte_counter, cpu);

        pr_info("CPU %d: packets=%lu bytes=%lu\n",
                cpu, packets, bytes);
    }

    return 0;
}

// 模块退出函数
static void __exit percpu_test_exit(void)
{
    int cpu;
    unsigned long total_packets = 0, total_bytes = 0;

    pr_info("Final statistics:\n");

    // 计算总流量
    for_each_online_cpu(cpu)
    {
        // 安全访问(虽然模块退出时不一定需要,但演示用法)
        unsigned long packets = get_cpu_var(packet_counter);
        unsigned long bytes = get_cpu_var(byte_counter);

        pr_info("CPU %d: packets=%lu bytes=%lu\n",
                cpu, packets, bytes);

        total_packets += packets;
        total_bytes += bytes;

        put_cpu_var(packet_counter);
        put_cpu_var(byte_counter);
    }

    pr_info("TOTAL: packets=%lu bytes=%lu\n", total_packets, total_bytes);
    pr_info("Per-CPU test module unloaded\n");
}

module_init(percpu_test_init);
module_exit(percpu_test_exit);

MODULE_LICENSE("GPL");
MODULE_AUTHOR("congchp");
MODULE_DESCRIPTION("get_cpu_var/put_cpu_var test module");

测试结果如下:

[159729.780088] Per-CPU variable test module loaded
[159729.783066] Current CPU statistics:
[159729.783066] CPU 0: packets=0 bytes=0
[159729.783067] CPU 1: packets=3 bytes=2592
[159729.783068] CPU 2: packets=0 bytes=0
[159729.783068] CPU 3: packets=0 bytes=0
[159729.783069] CPU 4: packets=0 bytes=0
[159729.783069] CPU 5: packets=0 bytes=0
与其它方法比较

每处理器变量使用场景

Per-CPU 变量主要用于频繁访问、无需跨核同步、强调性能、能够容忍最终一致性的场景,广泛应用于操作系统内核、网络栈、高性能缓存等领域。

场景描述
1性能计数器统计如中断次数、系统调用次数、上下文切换等,每个 CPU 独立统计,避免锁竞争。
2内存/缓冲区分配优化每个 CPU 拥有自己的缓存池、slab 分配器,减少并发访问,提高内存分配性能。
3调度器相关状态维护每个 CPU 维护自己的运行队列、负载状态,提升调度效率和响应速度。
4网络协议栈优化网络子系统(如 receive/transmit ring buffer)使用 per-CPU 数据结构,避免跨核通信延迟。
5时间或时钟相关维护比如每个 CPU 维护自己的时间戳、tick 计数器等,避免频繁跨核同步。
6锁统计与调试信息跟踪每个 CPU 上锁的行为和性能分析信息,帮助内核调优。
7延迟任务(如 RCU 回收)每个 CPU 维护自己的回调队列,延迟处理不会影响其他 CPU。
8NUMA架构优化非统一内存访问(NUMA)系统中,减少跨节点内存访问。

用户空间类似实现

用户空间并没有每处理器变量,per-cpu变量绑定的粒度是CPU核心。用户空间类似的是线程局部存储(TLS, Thread Local Storage),绑定的粒度是**线程。**如果想要和CPU关联上,可以结合CPU亲缘性、CPU隔离使用。

每处理器计数器

每处理器计数器(**Per-CPU Counters**)是每处理器变量的一种常见应用形式,主要用于**高频率地增加/减少计数值**的场景,如内核统计信息、内存页计数、网络包统计等。相较于全局计数器,它能大幅减少锁竞争和 cache line 震荡。  

每处理器计数器的本质

每处理器计数器的本质就是:
每个 CPU 维护一份私有计数值,只有在需要全局汇总时(如用户查询、周期性统计)才将各 CPU 的局部值合并。

为什么需要每处理器计数器

通常使用原子变量作为计数器,或者计数器中使用自旋锁,在多处理器系统中,如果处理器很多,那么计数器可能成为瓶颈:每次只能有一个处理器修改计数器,其他处理器必须等待。如果访问计数器很频繁,将会严重降低系统性能:

  • 锁竞争严重
  • cache line 拥有权频繁切换(false sharing)
  • 性能下降

有些计数器,我们不需要时刻知道它们的准确值,计数器的近似值准确值对我们没有差别。针对这种情况,我们可以使用每处理器计数器加速多处理器系统中计数器的操作, 通过**“分而治之”,将频繁的写操作本地化**,提高性能 。每处理器计数器的设计思想是:计数器有一个总的计数值,每个处理器有一个临时计数值,每个处理器先把计数累加到自己的临时计数值,当临时计数放值达到或超过阈值的时候,把临时计数值累加到总的计数值。

数据结构

每处理器计数器的定义如下:

struct percpu_counter {
	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;
};
  • 成员count是总的计数值。
  • 成员lock用来保护总的计数值。
  • 成员counters指向每处理器变量,每个处理器对应一个临时计数值。

使用步骤

  1. 包含头文件
#include <linux/percpu_counter.h>
  1. 声明变量
static struct percpu_counter my_counter;
  1. 初始化
percpu_counter_init(&my_counter, 0, GFP_KERNEL);
  1. 修改计数
percpu_counter_add(&my_counter, 1);   // 加 1
percpu_counter_sub(&my_counter, 1);   // 减 1
  1. 读取总值
s64 total = percpu_counter_sum(&my_counter);
  1. 销毁资源
percpu_counter_destroy(&my_counter);

测试代码

// per_cpu_counter_test.c

#include <linux/module.h>
#include <linux/init.h>
#include <linux/percpu_counter.h>
#include <linux/smp.h>  // for num_online_cpus()
#include <linux/sched.h>
#include <linux/kernel.h>

static struct percpu_counter my_counter;

static int __init percpu_counter_test_init(void)
{
    int cpu;

    pr_info("Initializing percpu_counter test module...\n");

    if (percpu_counter_init(&my_counter, 0, GFP_KERNEL)) {
        pr_err("Failed to initialize percpu_counter\n");
        return -ENOMEM;
    }

    for_each_online_cpu(cpu) {
        percpu_counter_add(&my_counter, 1);
        pr_info("CPU %d incremented the counter\n", cpu);
    }

    pr_info("Total count: %lld\n", percpu_counter_sum(&my_counter));
    return 0;
}

static void __exit percpu_counter_test_exit(void)
{
    pr_info("Cleaning up percpu_counter test module...\n");
    pr_info("Final count: %lld\n", percpu_counter_sum(&my_counter));
    percpu_counter_destroy(&my_counter);
}

module_init(percpu_counter_test_init);
module_exit(percpu_counter_test_exit);

MODULE_LICENSE("GPL");
MODULE_AUTHOR("ChatGPT");
MODULE_DESCRIPTION("A simple test for struct percpu_counter");

obj-m += per_counter_test.o

all:
	make -C /lib/modules/$(shell uname -r)/build M=$(PWD) modules

clean:
	make -C /lib/modules/$(shell uname -r)/build M=$(PWD) clean

测试结果如下:

[313242.349914] Initializing percpu_counter test module...
[313242.349920] CPU 0 incremented the counter
[313242.349921] CPU 1 incremented the counter
[313242.349922] CPU 2 incremented the counter
[313242.349922] CPU 3 incremented the counter
[313242.349923] CPU 4 incremented the counter
[313242.349923] CPU 5 incremented the counter
[313242.349924] Total count: 6
[313317.141827] Cleaning up percpu_counter test module...
[313317.141831] Final count: 6

percpu_counter_add详细介绍

static inline void percpu_counter_add(struct percpu_counter *fbc, s64 amount)
{
	__percpu_counter_add(fbc, amount, percpu_counter_batch);
}
void __percpu_counter_add(struct percpu_counter *fbc, s64 amount, s32 batch) // batch即阈值
{
	s64 count;

	preempt_disable(); // 禁止内核抢占
	count = __this_cpu_read(*fbc->counters) + amount; // 读取当前CPU的计数器值并计算新值
	if (count >= batch || count <= -batch) { // 当前CPU计数值超过阈值的情况
		unsigned long flags;
		raw_spin_lock_irqsave(&fbc->lock, flags);
		fbc->count += count; // 将当前CPU的累计值加到全局count
		__this_cpu_sub(*fbc->counters, count - amount); // 重置当前CPU的计数值
		raw_spin_unlock_irqrestore(&fbc->lock, flags);
	} else {
		this_cpu_add(*fbc->counters, amount);
	}
	preempt_enable();
}
EXPORT_SYMBOL(__percpu_counter_add);

参考资料

  1. Professional Linux Kernel Architecture,Wolfgang Mauerer
  2. Linux内核深度解析,余华兵
  3. Linux设备驱动开发详解,宋宝华
  4. linux kernel 4.12
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值