Linux同步互斥2

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 变量通常用于以下几种场景:

  1. 每个 CPU 独立计数器: 例如,内核可能需要统计每个 CPU 的中断次数、调度次数、任务切换次数等。这些计数器的增量操作可以完全在每个 CPU 内部进行,而无需进行全局同步。

  2. 任务调度中的负载均衡: 在多核系统中,调度器需要考虑每个 CPU 的负载。使用 Per-CPU 变量来存储每个 CPU 上的负载信息,有助于减少全局锁的使用和提高调度效率。

  3. 内核数据结构: 内核中有一些全局数据结构(例如任务结构、虚拟内存区域等),可能会根据每个 CPU 的需要进行不同的操作。使用 Per-CPU 变量可以避免在这些数据结构上的竞争。

  4. 中断处理和软中断: 中断处理程序通常需要处理与 CPU 状态相关的本地数据(例如本地中断计数)。Per-CPU 变量可以有效避免多个 CPU 在处理这些数据时的竞争。

1.3、Linux 中的 Per-CPU 变量实现

在 Linux 中,Per-CPU 变量通过以下方式实现:

  1. 定义 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 上的 varthis_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);

宏的详细说明:

  1. DEFINE_PER_CPU(type, name)

    • 用于在内核中定义 Per-CPU 变量。每个 CPU 都会有一个独立的 name 变量副本,类型为 type
    • 定义后的变量会在 .data 段或 .percpu 段中分配内存。

    示例

  • 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 变量时,直接获得其指针。

    示例

  • int *counter = this_cpu_ptr(&my_counter);
    
  • this_cpu_read(var)

    • 读取当前 CPU 上的 Per-CPU 变量的值。通过此宏可以直接获取当前 CPU 上 var 的副本。

    示例

  • int counter = this_cpu_read(my_counter);
    
  • this_cpu_write(var, val)

    • 写入当前 CPU 上的 Per-CPU 变量。通过此宏将新的值 val 写入到当前 CPU 上的 var

    示例

  • 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 变量,可以使用这个宏。

    示例

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_ptrint *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 说明:

  1. 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()

    示例

  • 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_CPUDECLARE_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安排

SMPUP
Build-in kernel".data..percpu" section".data" section
defined in module".data..percpu" section".data" section

(2)first per cpu变量的section安排

SMPUP
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安排

SMPUP
Build-in kernel".data..percpu..shared_aligned" section".data" section
defined in module".data..percpu" section".data" section

(4)aligned per cpu变量的section安排

SMPUP
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安排

SMPUP
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安排

SMPUP
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 宏的作用可以分为两步:

  1. 验证指针有效性:通过 __verify_pcpu_ptr(__p) 来确保传入的指针是指向一个有效的 Per-CPU 变量。
  2. 类型转换:通过 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_KERNELGFP_ATOMIC)。
  • percpu_counter_add(), percpu_counter_read(): 这些函数专门用于操作 Per-CPU 计数器。

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值