每处理器变量和每处理器计数器
每处理器变量
“每处理器变量”常被称为 per-CPU 变量(per-processor variable 或 per-CPU variable),在多核系统和操作系统内核中是一种优化手段,主要用于减少处理器之间的数据竞争(cache line bouncing)和提高并发性能。
在多处理器系统中,每处理器变量为每个处理器生成一个变量量的副本,每个处理器访问自己的副本,从而避免了处理器之间的互斥和处理器缓存之间的的同步,提高了程序的执行速度。
基本原理
- 核心思想:为系统中的每个 CPU 核心创建变量的独立副本
- 访问方式:当代码运行时,自动访问当前 CPU 对应的变量副本
- 优势:避免了多 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_countvoid 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) modulesclean: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。 |
8 | NUMA架构优化 | 非统一内存访问(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_CPUstruct list_head list; /* All percpu_counters are on a list */
#endifs32 __percpu *counters;
};
- 成员
count
是总的计数值。 - 成员
lock
用来保护总的计数值。 - 成员
counters
指向每处理器变量,每个处理器对应一个临时计数值。
使用步骤
- 包含头文件
#include <linux/percpu_counter.h>
- 声明变量
static struct percpu_counter my_counter;
- 初始化
percpu_counter_init(&my_counter, 0, GFP_KERNEL);
- 修改计数
percpu_counter_add(&my_counter, 1); // 加 1
percpu_counter_sub(&my_counter, 1); // 减 1
- 读取总值
s64 total = percpu_counter_sum(&my_counter);
- 销毁资源
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.oall:make -C /lib/modules/$(shell uname -r)/build M=$(PWD) modulesclean: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);
参考资料
- Professional Linux Kernel Architecture,Wolfgang Mauerer
- Linux内核深度解析,余华兵
- Linux设备驱动开发详解,宋宝华
- linux kernel 4.12