Linux同步互斥2(基于Linux6.6)---Per-CPU变量
一、为何引入Per-CPU变量?
1.1、为什么需要 Per-CPU 变量?
1. 减少锁的争用和上下文切换
在多核系统中,如果所有的线程都共享一块内存区域(例如一个全局变量),那么每次访问该共享数据时都需要进行同步操作(例如通过自旋锁或互斥锁)。每次锁的获取和释放都会带来一定的开销,并且如果多个 CPU 同时访问同一资源,还可能导致频繁的上下文切换,从而影响系统性能。
Per-CPU 变量的优势:每个 CPU 拥有自己的变量副本,因此可以在不进行任何同步的情况下,独立地访问和修改自己的副本。这样可以大大减少因锁竞争导致的性能瓶颈和同步开销。
2. 提高缓存局部性
在多核处理器中,每个 CPU 都有自己的本地缓存(L1、L2 缓存)。如果多个 CPU 访问相同的内存位置,这可能导致缓存一致性问题,并且在缓存之间进行数据同步时会引发额外的延迟。
Per-CPU 变量的优势:每个 CPU 都有自己的私有数据副本,这些副本通常会被存储在该 CPU 的本地缓存中。这样可以提高数据的缓存局部性,减少缓存一致性协议的负担,从而提高性能。
3. 避免锁和同步的开销
锁和同步机制虽然是保证多线程安全的必要手段,但它们会带来一定的性能开销。尤其是在高并发的场景下,锁的竞争会严重影响程序的效率。
Per-CPU 变量的优势:每个 CPU 拥有自己的变量副本,完全避免了锁的使用和数据同步问题。各个 CPU 可以并行操作自己的变量副本,从而显著提高了性能。
4. 减少内存访问的冲突
在多核系统中,如果多个线程共享同一块内存,可能会导致内存访问冲突。例如,多个 CPU 同时访问相同的内存位置,可能导致内存带宽的争夺和性能瓶颈。
Per-CPU 变量的优势:通过为每个 CPU 分配独立的内存区域,避免了多个 CPU 访问同一内存位置,从而减少了内存访问冲突。
1.2、使用 Per-CPU 变量的场景
Per-CPU 变量通常用于以下几种场景:
-
每个 CPU 独立计数器: 例如,内核可能需要统计每个 CPU 的中断次数、调度次数、任务切换次数等。这些计数器的增量操作可以完全在每个 CPU 内部进行,而无需进行全局同步。
-
任务调度中的负载均衡: 在多核系统中,调度器需要考虑每个 CPU 的负载。使用 Per-CPU 变量来存储每个 CPU 上的负载信息,有助于减少全局锁的使用和提高调度效率。
-
内核数据结构: 内核中有一些全局数据结构(例如任务结构、虚拟内存区域等),可能会根据每个 CPU 的需要进行不同的操作。使用 Per-CPU 变量可以避免在这些数据结构上的竞争。
-
中断处理和软中断: 中断处理程序通常需要处理与 CPU 状态相关的本地数据(例如本地中断计数)。Per-CPU 变量可以有效避免多个 CPU 在处理这些数据时的竞争。
1.3、Linux 中的 Per-CPU 变量实现
在 Linux 中,Per-CPU 变量通过以下方式实现:
-
定义 Per-CPU 变量: 使用
DEFINE_PER_CPU
宏定义 Per-CPU 变量。例如,定义一个整数类型的 Per-CPU 变量:
-
DEFINE_PER_CPU(int, my_counter);
这个变量
my_counter
将为每个 CPU 分配一个独立的副本。 -
访问 Per-CPU 变量: 访问 Per-CPU 变量时,可以使用
this_cpu_read()
和this_cpu_write()
宏来确保访问的是当前 CPU 的副本。例如: -
int counter = this_cpu_read(my_counter); // 读取当前 CPU 的 my_counter this_cpu_write(my_counter, counter + 1); // 写入当前 CPU 的 my_counter
这些宏提供了访问当前 CPU 上的变量副本的方法,而不需要进行同步或锁操作。
-
Per-CPU 数据的内存分配: 在 Linux 内核中,Per-CPU 变量通常会被分配在一个特殊的内存区域,该区域在每个 CPU 上都有独立的副本。内核会为每个 CPU 动态地分配内存,并确保内存的访问不会发生冲突。
-
Per-CPU 变量的初始化和清理: Per-CPU 变量通常会在内核初始化时被分配和初始化,并在系统关闭时清理。内核会自动管理这些变量的生命周期,以确保内存的正确分配和释放。
二、接口
2.1、静态声明和定义 Per-CPU 变量的 API
在 Linux 内核中,静态声明和定义 Per-CPU 变量的 API 主要通过宏来实现,以下是这些宏的常见用法及相关说明:
宏名称 | 用途 | 示例代码 |
---|---|---|
DEFINE_PER_CPU(type, name) | 静态定义 Per-CPU 变量。为每个 CPU 分配一个 name 类型为 type 的独立副本。 | DEFINE_PER_CPU(int, my_counter); |
DECLARE_PER_CPU(type, name) | 声明一个 Per-CPU 变量,表示该变量将在其他地方定义(通常是头文件中使用)。 | DECLARE_PER_CPU(int, my_counter); |
this_cpu_ptr(ptr) | 获取当前 CPU 的 ptr 指针,这个指针指向当前 CPU 上 ptr 变量的副本。 | int *counter = this_cpu_ptr(&my_counter); |
this_cpu_read(var) | 读取当前 CPU 上的 var 的值。相当于访问当前 CPU 上 Per-CPU 变量的副本。 | int counter = this_cpu_read(my_counter); |
this_cpu_write(var, val) | 写入 val 到当前 CPU 上的 var 。 | this_cpu_write(my_counter, counter + 1); |
get_cpu_var(var) | 获取并锁定当前 CPU 上 var 的值(通常用于更复杂的锁定机制)。 | int counter = get_cpu_var(my_counter); |
put_cpu_var(var) | 释放当前 CPU 上的 var (与 get_cpu_var() 配合使用)。 | put_cpu_var(my_counter); |
per_cpu_ptr(ptr, cpu) | 获取指定 CPU (cpu 是一个 CPU ID) 上的 ptr 指针,指向该 CPU 上 ptr 变量的副本。 | int *counter = per_cpu_ptr(&my_counter, cpu); |
宏的详细说明:
-
DEFINE_PER_CPU(type, name)
:- 用于在内核中定义 Per-CPU 变量。每个 CPU 都会有一个独立的
name
变量副本,类型为type
。 - 定义后的变量会在
.data
段或.percpu
段中分配内存。
示例:
- 用于在内核中定义 Per-CPU 变量。每个 CPU 都会有一个独立的
-
DEFINE_PER_CPU(int, my_counter);
这会为每个 CPU 分配一个独立的
int
类型变量my_counter
。 -
DECLARE_PER_CPU(type, name)
:- 用于声明一个 Per-CPU 变量,通常在头文件中使用,以便在源文件中进行定义。
- 该宏本身并不分配内存,只是告诉编译器该变量将在其他地方定义。
示例:
-
DECLARE_PER_CPU(int, my_counter);
-
this_cpu_ptr(ptr)
:- 获取当前 CPU 对应的
ptr
变量的指针。这是一个针对当前 CPU 的 Per-CPU 变量的直接访问方法。 - 适用于访问 Per-CPU 变量时,直接获得其指针。
示例:
- 获取当前 CPU 对应的
-
int *counter = this_cpu_ptr(&my_counter);
-
this_cpu_read(var)
:- 读取当前 CPU 上的 Per-CPU 变量的值。通过此宏可以直接获取当前 CPU 上
var
的副本。
示例:
- 读取当前 CPU 上的 Per-CPU 变量的值。通过此宏可以直接获取当前 CPU 上
-
int counter = this_cpu_read(my_counter);
-
this_cpu_write(var, val)
:- 写入当前 CPU 上的 Per-CPU 变量。通过此宏将新的值
val
写入到当前 CPU 上的var
。
示例:
- 写入当前 CPU 上的 Per-CPU 变量。通过此宏将新的值
-
this_cpu_write(my_counter, counter + 1);
-
get_cpu_var(var)
和put_cpu_var(var)
:get_cpu_var()
会获取当前 CPU 上的var
,并锁定该变量;put_cpu_var()
用于释放该变量。- 这些宏通常用于需要线程安全访问的复杂场景,尤其是涉及多核 CPU 时。
get_cpu_var()
和put_cpu_var()
提供了一种基于锁的访问方式。
示例:
-
int counter = get_cpu_var(my_counter); put_cpu_var(my_counter);
-
per_cpu_ptr(ptr, cpu)
:- 用于访问指定 CPU (
cpu
是一个 CPU ID) 上的ptr
变量的副本。 - 如果需要访问其他 CPU 上的 Per-CPU 变量,可以使用这个宏。
示例:
- 用于访问指定 CPU (
int *counter = per_cpu_ptr(&my_counter, 1); // 获取 CPU 1 上的 my_counter 变量
2.2、动态分配Per-CPU变量的API
在 Linux 内核中,除了静态分配 Per-CPU 变量的 API 外,内核还提供了动态分配 Per-CPU 变量的相关 API。以下是动态分配 Per-CPU 变量的一些常见宏和函数的表格:
API 名称 | 用途 | 示例代码 |
---|---|---|
alloc_percpu(type) | 动态分配一个 Per-CPU 变量,返回指向 Per-CPU 变量的指针。 | int *my_counter = alloc_percpu(int); |
free_percpu(ptr) | 释放通过 alloc_percpu() 动态分配的 Per-CPU 变量。 | free_percpu(my_counter); |
per_cpu_ptr(ptr, cpu) | 获取指定 CPU (cpu 是 CPU ID) 上的 ptr 变量的指针,类似静态分配的 per_cpu_ptr 。 | int *counter = per_cpu_ptr(my_counter, cpu); |
get_cpu_var(var) | 获取当前 CPU 上的 var 变量,类似静态分配的 get_cpu_var ,但是可以用于动态分配的变量。 | int counter = get_cpu_var(my_counter); |
put_cpu_var(var) | 释放由 get_cpu_var 锁定的动态分配的 Per-CPU 变量。 | put_cpu_var(my_counter); |
API 说明:
-
alloc_percpu(type)
:- 动态分配一个 Per-CPU 变量。它返回一个指向每个 CPU 上该类型变量的指针。每个 CPU 会有一个独立的副本。内存通常分配在内核堆上,返回的指针是该变量在每个 CPU 上的地址。
- 需要注意的是,
alloc_percpu()
分配的内存需要通过free_percpu()
来释放。
示例:
-
int *my_counter = alloc_percpu(int); if (!my_counter) { pr_err("Failed to allocate per-cpu variable\n"); return; }
-
free_percpu(ptr)
:- 用于释放通过
alloc_percpu()
分配的 Per-CPU 变量。只有通过alloc_percpu()
分配的 Per-CPU 变量才需要调用此 API 来释放内存。 - 在不再需要 Per-CPU 变量时,调用
free_percpu()
以释放内存。
示例:
- 用于释放通过
-
free_percpu(my_counter);
-
per_cpu_ptr(ptr, cpu)
:- 获取指定 CPU 上的
ptr
指针,类似于静态分配的per_cpu_ptr
,但适用于动态分配的 Per-CPU 变量。通过这个宏可以访问指定 CPU 上的变量。 - 注意,如果需要访问当前 CPU 的变量,可以直接使用
this_cpu_ptr()
宏,但如果要访问其他 CPU 上的变量,应该使用per_cpu_ptr()
。
示例:
- 获取指定 CPU 上的
-
int *counter = per_cpu_ptr(my_counter, 1); // 获取 CPU 1 上的 my_counter
-
get_cpu_var(var)
和put_cpu_var(var)
:get_cpu_var(var)
用于获取当前 CPU 上动态分配的 Per-CPU 变量的值,并加锁该变量。通常用于需要原子操作或访问安全的场景。put_cpu_var(var)
用于释放由get_cpu_var
获取的变量资源,解锁并使其可用。
示例:
int counter = get_cpu_var(my_counter); // 获取当前 CPU 上的 my_counter
put_cpu_var(my_counter); // 释放锁
三、实现
3.1、静态Per-CPU变量定义
以DEFINE_PER_CPU的实现为例子,描述linux kernel中如何实现静态Per-CPU变量定义。具体代码如下:
include/linux/percpu-defs.h
#define DEFINE_PER_CPU(type, name) \
DEFINE_PER_CPU_SECTION(type, name, "")
type就是变量的类型,name是per cpu变量符号。DEFINE_PER_CPU_SECTION宏可以把一个per cpu变量放到指定的section中,具体代码如下:
include/linux/percpu-defs.h
#define DEFINE_PER_CPU_SECTION(type, name, sec) \
__PCPU_ATTRS(sec) __typeof__(type) name
#endif
在这里具体arch specific的percpu代码中可以定义PER_CPU_DEF_ATTRIBUTES,以便控制该per cpu变量的属性,当然,如果arch specific的percpu代码不定义,那么在general arch-independent的代码中会定义为空。这里可以顺便提一下Per-CPU变量的软件层次:
Per-CPU 变量的软件层次
Per-CPU 变量的管理和访问是在多个层次上进行的,下面将从 数据存储层次、内存分配层次、访问和同步机制 等几个方面进行阐述。
1. 数据存储层次(存储模型)
Per-CPU 变量在 Linux 内核中通常采用一个独立的内存区域来存储每个 CPU 的数据副本。每个 CPU 上都会有该变量的一份拷贝,这些副本通常存储在专门的内存区域中。
- 每个 CPU 独立存储:在每个 CPU 上,Per-CPU 变量会分配一块内存来存储变量副本,这样每个 CPU 在访问这些变量时不会和其他 CPU 产生冲突。
- 共享内存的最小化:Per-CPU 变量的目的是避免多个 CPU 访问同一内存位置,减少缓存一致性协议(如 MESI)的负担,提高性能。
Linux 内核的 Per-CPU 存储模型通过以下几种方式来实现:
- 静态分配:通过
DEFINE_PER_CPU
等宏静态地声明每个 CPU 的变量副本。 - 动态分配:通过
alloc_percpu
等函数动态地分配每个 CPU 的变量副本。
2. 内存分配层次
Per-CPU 变量的内存分配与普通的内存分配有所不同。内核需要确保每个 CPU 在访问这些变量时,都能直接访问到自己专用的内存区域,而不会涉及到其他 CPU 的数据,从而避免竞争和同步问题。
-
静态分配(Per-CPU 变量声明):通过
DEFINE_PER_CPU
或DECLARE_PER_CPU
等宏声明的变量,它们的内存是静态分配的。在编译时,内核会为每个 CPU 分配一块独立的内存区域来存储这些变量。示例:
-
DEFINE_PER_CPU(int, my_counter);
这段代码会在内核中为每个 CPU 分配一个
int
类型的my_counter
变量。 -
动态分配(
alloc_percpu
):在运行时,通过alloc_percpu()
等函数动态分配 Per-CPU 变量的内存。每个 CPU 将获取一个独立的内存地址,指向自己特有的变量副本。示例:
-
int *my_counter = alloc_percpu(int);
3. 访问层次
Per-CPU 变量的访问方式有助于确保每个 CPU 可以高效地访问自己的变量副本,同时避免与其他 CPU 的同步问题。
-
访问当前 CPU 的 Per-CPU 变量:可以通过
this_cpu_ptr()
或get_cpu_var()
来访问当前 CPU 上的变量。this_cpu_ptr()
:返回当前 CPU 上的变量指针,用于访问当前 CPU 的 Per-CPU 变量。get_cpu_var()
:类似于this_cpu_ptr()
,但可以用于读取当前 CPU 上的变量值,并且会对该变量加锁,防止并发修改。
示例:
-
int *counter = this_cpu_ptr(my_counter); // 获取当前 CPU 上的 my_counter 变量
-
访问指定 CPU 的 Per-CPU 变量:可以通过
per_cpu_ptr(ptr, cpu)
来访问特定 CPU 上的 Per-CPU 变量。示例:
-
int *counter = per_cpu_ptr(my_counter, 1); // 获取 CPU 1 上的 my_counter
4. 同步机制
Per-CPU 变量的访问不需要频繁的锁机制,因为每个 CPU 拥有自己的独立副本,通常情况下,变量的读取和写入是线程安全的。
-
原子操作:在一些情况下,Per-CPU 变量可能会通过原子操作进行更新,确保每个 CPU 对自己的副本的操作是安全的。
-
没有共享内存:由于每个 CPU 都有独立的变量副本,通常不会需要额外的同步机制。只有在多个 CPU 之间需要共享数据时,才可能涉及到同步和锁机制。
-
get_cpu_var()
和put_cpu_var()
:这些 API 主要用于锁定和解锁 Per-CPU 变量。get_cpu_var()
会锁定当前 CPU 的变量,put_cpu_var()
会释放这个锁。示例:
-
int counter = get_cpu_var(my_counter); // 获取当前 CPU 上的 my_counter put_cpu_var(my_counter); // 释放锁
5. 缓存一致性和优化
虽然 Per-CPU 变量在每个 CPU 上都有独立副本,但为了确保不同 CPU 之间的一致性,Linux 内核会利用缓存一致性协议(如 MESI)来维护内存的一致性。
-
局部性优化:每个 CPU 上的 Per-CPU 变量通常会存储在该 CPU 的本地缓存中,这有助于提高访问速度。内核会尽量确保每个 CPU 上的变量副本具有较好的缓存局部性,减少跨 CPU 的访问开销。
-
内存屏障:在某些情况下,为了确保数据的一致性,内核会使用内存屏障(memory barrier)来强制同步不同 CPU 之间的缓存。
6. Per-CPU 数据结构
除了简单的单一变量,Per-CPU 机制还支持复杂的数据结构。Linux 内核允许开发者使用 Per-CPU 机制为每个 CPU 提供一份复杂数据结构的独立副本,如链表、哈希表等。这些数据结构可以通过动态分配、分配专用内存来管理。
例如,Linux 内核中的 Per-CPU 哈希表 和 Per-CPU 链表,都可以使用 alloc_percpu()
来动态分配每个 CPU 独立的数据结构副本。
看看__PCPU_ATTRS的定义:
include/linux/percpu-defs.h
#define __PCPU_ATTRS(sec) \
__percpu __attribute__((section(PER_CPU_BASE_SECTION sec))) \
PER_CPU_ATTRIBUTES
PER_CPU_BASE_SECTION 定义了基础的section name symbol,定义如下:
include/asm-generic/percpu.h
#ifndef PER_CPU_BASE_SECTION
#ifdef CONFIG_SMP
#define PER_CPU_BASE_SECTION ".data..percpu"
#else
#define PER_CPU_BASE_SECTION ".data"
#endif
#endif
虽然有各种各样的静态Per-CPU变量定义方法,但是都是类似的,只不过是放在不同的section中,属性不同而已,这里就不看其他的实现了,直接给出section的安排:
(1)普通per cpu变量的section安排
SMP | UP | |
Build-in kernel | ".data..percpu" section | ".data" section |
defined in module | ".data..percpu" section | ".data" section |
(2)first per cpu变量的section安排
SMP | UP | |
Build-in kernel | ".data..percpu..first" section | ".data" section |
defined in module | ".data..percpu..first" section | ".data" section |
(3)SMP shared aligned per cpu变量的section安排
SMP | UP | |
Build-in kernel | ".data..percpu..shared_aligned" section | ".data" section |
defined in module | ".data..percpu" section | ".data" section |
(4)aligned per cpu变量的section安排
SMP | UP | |
Build-in kernel | ".data..percpu..shared_aligned" section | ".data..shared_aligned" section |
defined in module | ".data..percpu" section | ".data..shared_aligned" section |
(5)page aligned per cpu变量的section安排
SMP | UP | |
Build-in kernel | ".data..percpu..page_aligned" section | ".data..page_aligned" section |
defined in module | ".data..percpu..page_aligned" section | ".data..page_aligned" section |
(6)read mostly per cpu变量的section安排
SMP | UP | |
Build-in kernel | ".data..percpu..readmostly" section | ".data..readmostly" section |
defined in module | ".data..percpu..readmostly" section | ".data..readmostly" section |
了解了静态定义Per-CPU变量的实现,但是为何要引入这么多的section呢?对于kernel中的普通变量,经过了编译和链接后,会被放置到.data或者.bss段,系统在初始化的时候会准备好一切(例如clear bss),由于per cpu变量的特殊性,内核将这些变量放置到了其他的section,位于kernel address space中__per_cpu_start和__per_cpu_end之间,称之Per-CPU变量的原始变量。
只有Per-CPU变量的原始变量还是不够的,必须为每一个CPU建立一个副本,怎么建?直接静态定义一个NR_CPUS的数组?
NR_CPUS定义了系统支持的最大的processor的个数,并不是实际中系统processor的数目,这样的定义非常浪费内存。此外,静态定义的数据在内存中连续,对于UMA系统而言是OK的,对于NUMA系统,每个CPU上的Per-CPU变量的副本应该位于它访问最快的那段memory上,也就是说Per-CPU变量的各个CPU副本可能是散布在整个内存地址空间的,而这些空间之间是有空洞的。本质上,副本per cpu内存的分配归属于内存管理子系统,因此,分配per cpu变量副本的内存本文大致的思路如下:
内存管理子系统会根据当前的内存配置为每一个CPU分配一大块memory,对于UMA,这个memory也是位于main memory,对于NUMA,有可能是分配最靠近该CPU的memory(也就是说该cpu访问这段内存最快),但无论如何,这些都是内存管理子系统需要考虑的。无论静态还是动态per cpu变量的分配,其机制都是一样的,只不过,对于静态per cpu变量,需要在系统初始化的时候,对应per cpu section,预先动态分配一个同样size的per cpu chunk。
percpu section的排列情况:include/asm-generic/vmlinux.lds.h
/**
* PERCPU_INPUT - the percpu input sections
* @cacheline: cacheline size
*
* The core percpu section names and core symbols which do not rely
* directly upon load addresses.
*
* @cacheline is used to align subsections to avoid false cacheline
* sharing between subsections for different purposes.
*/
#define PERCPU_INPUT(cacheline) \
__per_cpu_start = .; \
*(.data..percpu..first) \
. = ALIGN(PAGE_SIZE); \
*(.data..percpu..page_aligned) \
. = ALIGN(cacheline); \
*(.data..percpu..read_mostly) \
. = ALIGN(cacheline); \
*(.data..percpu) \
*(.data..percpu..shared_aligned) \
PERCPU_DECRYPTED_SECTION \
__per_cpu_end = .;
对于build in内核的那些per cpu变量,必然位于__per_cpu_start和__per_cpu_end之间的per cpu section。在系统初始化的时候(setup_per_cpu_areas),分配per cpu memory chunk,并将per cpu section copy到每一个chunk中。
3.2、访问静态定义的per cpu变量
代码如下:include/linux/percpu-defs.h
/*
* Must be an lvalue. Since @var must be a simple identifier,
* we force a syntax error here if it isn't.
*/
#define get_cpu_var(var) \
(*({ \
preempt_disable(); \
this_cpu_ptr(&var); \
}))
为防止当前task由于抢占而调度到其他的CPU上,在访问per cpu memory的时候都需要使用preempt_disable这样的锁的机制。来看this_cpu_ptr:
include/linux/percpu-defs.h
#define per_cpu_ptr(ptr, cpu) ({ (void)(cpu); VERIFY_PERCPU_PTR(ptr); })
#define raw_cpu_ptr(ptr) per_cpu_ptr(ptr, 0)
#define this_cpu_ptr(ptr) raw_cpu_ptr(ptr)
继续看这个VERIFY_PERCPU_PTR
include/linux/percpu-defs.h
#define VERIFY_PERCPU_PTR(__p) \
({ \
__verify_pcpu_ptr(__p); \
(typeof(*(__p)) __kernel __force *)(__p); \
})
VERIFY_PERCPU_PTR
宏的作用可以分为两步:
- 验证指针有效性:通过
__verify_pcpu_ptr(__p)
来确保传入的指针是指向一个有效的 Per-CPU 变量。 - 类型转换:通过
typeof(*(__p)) __kernel __force *
将指针转换为正确的类型,通常是 Per-CPU 变量的类型。
3.3、动态分配per cpu变量
在 Linux 内核中,动态分配 Per-CPU 变量 通常使用 alloc_percpu()
或 percpu
内存分配函数。这些变量允许每个 CPU 拥有自己的独立副本,从而避免在多核环境下的竞争条件。
1. alloc_percpu()
函数
alloc_percpu()
是用于动态分配 Per-CPU 变量的内核函数。它会为每个 CPU 分配一个独立的内存区域,并返回指向该内存区域的指针。
示例用法:
#include <linux/percpu.h>
#include <linux/slab.h>
#include <linux/kernel.h>
int __init my_init(void)
{
// 动态分配一个整数型的 Per-CPU 变量
int *percpu_var = alloc_percpu(int);
if (!percpu_var) {
pr_err("Failed to allocate per-CPU variable\n");
return -ENOMEM;
}
// 在每个 CPU 上给 Per-CPU 变量赋值
int cpu;
for_each_possible_cpu(cpu) {
per_cpu(percpu_var, cpu) = cpu; // 为每个 CPU 设置一个值
}
// 访问某个 CPU 的 Per-CPU 变量
pr_info("Value on CPU 0: %d\n", per_cpu(percpu_var, 0));
// 释放 Per-CPU 变量
free_percpu(percpu_var);
return 0;
}
解释:
alloc_percpu(int)
:为每个 CPU 分配一个int
类型的 Per-CPU 变量,返回一个指向该变量的指针。for_each_possible_cpu(cpu)
:这是一个宏,用于遍历系统中的所有 CPU。per_cpu(percpu_var, cpu)
:访问每个 CPU 上的 Per-CPU 变量percpu_var
。free_percpu(percpu_var)
:释放分配的 Per-CPU 变量。
2. alloc_percpu()
的返回值
alloc_percpu()
返回一个指向 percpu
变量的指针,它的类型通常是一个指向指定类型的指针。例如,如果我们分配一个 int
类型的 Per-CPU 变量,返回值就是 int *
类型。
3. percpu
类型
在内核中,Per-CPU 变量通常使用 percpu
关键字进行定义。例如,定义一个静态的 Per-CPU 变量时,可以使用:
DEFINE_PER_CPU(int, my_var);
动态分配的 Per-CPU 变量通过 alloc_percpu()
返回的是一个指针,而静态分配则是通过 DEFINE_PER_CPU()
宏来实现的。
4. 访问和操作 Per-CPU 变量
访问 Per-CPU 变量时,通常使用 per_cpu()
宏。例如,访问动态分配的 percpu_var
在某个特定 CPU 上的值:
int value_on_cpu_0 = per_cpu(percpu_var, 0);
5. 内存分配方式
alloc_percpu()
会根据系统的 CPU 数量分配内存。每个 CPU 都有一个独立的副本,所以不会发生多核竞争问题。对于每个 CPU,内存的大小是分配给每个 CPU 的副本大小。
6. 释放 Per-CPU 变量
分配完 Per-CPU 变量后,必须调用 free_percpu()
来释放它,以避免内存泄漏:
free_percpu(percpu_var);
7. 更多的 Per-CPU 内存分配函数
alloc_percpu_gfp()
: 这个函数与alloc_percpu()
类似,允许传入自定义的 GFP 标志(例如,GFP_KERNEL
或GFP_ATOMIC
)。percpu_counter_add()
,percpu_counter_read()
: 这些函数专门用于操作 Per-CPU 计数器。