<?xml version="1.0" encoding="UTF-8"?>
<rss version="2.0"
	xmlns:content="http://purl.org/rss/1.0/modules/content/"
	xmlns:wfw="http://wellformedweb.org/CommentAPI/"
	xmlns:dc="http://purl.org/dc/elements/1.1/"
	xmlns:atom="http://www.w3.org/2005/Atom"
	xmlns:sy="http://purl.org/rss/1.0/modules/syndication/"
	xmlns:slash="http://purl.org/rss/1.0/modules/slash/"
	>

<channel>
	<title>Soul Of Free Loop &#187; 变量</title>
	<atom:link href="https://zohead.com/archives/tag/variable/feed/" rel="self" type="application/rss+xml" />
	<link>https://zohead.com</link>
	<description>Uranus Zhou&#039;s Blog</description>
	<lastBuildDate>Sat, 19 Jul 2025 15:42:46 +0000</lastBuildDate>
	<language>zh-CN</language>
		<sy:updatePeriod>hourly</sy:updatePeriod>
		<sy:updateFrequency>1</sy:updateFrequency>
	<generator>http://wordpress.org/?v=3.8</generator>
	<item>
		<title>Linux kernel percpu变量解析</title>
		<link>https://zohead.com/archives/linux-kernel-percpu-variable/</link>
		<comments>https://zohead.com/archives/linux-kernel-percpu-variable/#comments</comments>
		<pubDate>Sat, 16 Jun 2012 11:27:20 +0000</pubDate>
		<dc:creator><![CDATA[Uranus Zhou]]></dc:creator>
				<category><![CDATA[kernel]]></category>
		<category><![CDATA[Linux]]></category>
		<category><![CDATA[代码分析]]></category>
		<category><![CDATA[技术]]></category>
		<category><![CDATA[CPU]]></category>
		<category><![CDATA[percpu]]></category>
		<category><![CDATA[SMP]]></category>
		<category><![CDATA[变量]]></category>

		<guid isPermaLink="false">http://zohead.com/?p=229</guid>
		<description><![CDATA[本文同步自（如浏览不正常请点击跳转）：https://zohead.com/archives/linux-kernel-percpu-variable/ Linux 2.6 kernel 中的 percpu 变量是经常用到的东西，因为现在很多计算机都已经支持多处理器了，而且 kernel 默认都会被编译成 SMP 的，相对于原来多个处理器共享数据并进行处理的方式，用 percpu 变量在 SMP、NUMA 等架构下可以提高性能，而且很多情况下必须用 percpu 来对不同的处理器做出数据区分。 本文以 kernel 中的 softirq 为例简单说下 percpu 变量，我们先来看看 kern [&#8230;]]]></description>
				<content:encoded><![CDATA[<p>本文同步自（如浏览不正常请点击跳转）：<a href="https://zohead.com/archives/linux-kernel-percpu-variable/" target="_blank">https://zohead.com/archives/linux-kernel-percpu-variable/</a></p>
<p>Linux 2.6 kernel 中的 percpu 变量是经常用到的东西，因为现在很多计算机都已经支持多处理器了，而且 kernel 默认都会被编译成 SMP 的，相对于原来多个处理器共享数据并进行处理的方式，用 percpu 变量在 SMP、NUMA 等架构下可以提高性能，而且很多情况下必须用 percpu 来对不同的处理器做出数据区分。</p>
<p>本文以 kernel 中的 softirq 为例简单说下 percpu 变量，我们先来看看 kernel 中唤醒 ksoftirqd 的实现，ksoftirqd 在 ps 命令看到的进程列表中很容易找到，是每个处理器都有一个（如果有 4 个处理器，则有 4 个 kernel 线程名称分别从 ksoftirqd/0 到 ksoftirqd/3），关于 softirq 本身的实现不在本文讨论范围内，唤醒 ksoftirqd 的实现在 kernel/softirq.c 文件中：</p>
<pre class="brush: cpp; title: kernel/softirq.c; notranslate">
static DEFINE_PER_CPU(struct task_struct *, ksoftirqd);

void wakeup_softirqd(void)
{
	/* Interrupts are disabled: no need to stop preemption */
	struct task_struct *tsk = __get_cpu_var(ksoftirqd);

	if (tsk &amp;&amp; tsk-&gt;state != TASK_RUNNING)
		wake_up_process(tsk);
}
</pre>
<p>这里就用到了 percpu 变量 ksoftirqd，它是通过 DEFINE_PER_CPU 宏来进程定义的 percpu task_struct 列表，通过 __get_cpu_var 宏来得到相应处理器的 ksoftirqd/n 的 task_struct，然后调用 wake_up_process 函数唤醒进程（也就是 ksoftirqd/n kernel 线程），关于 wake_up_process 等进程调度的相关实现在之前的日志中有介绍的，请参考 [<a href="https://zohead.com/archives/linux-kernel-learning-process-scheduling/" target="_blank">这里</a>]。</p>
<p>__get_cpu_var、DEFINE_PER_CPU 等 percpu 宏的实现在 include/linux/percpu.h、include/asm-generic/percpu.h 等头文件中。先看看 include/asm-generic/percpu.h 中的一些定义：</p>
<pre class="brush: cpp; title: include/asm-generic/percpu.h; notranslate">
#ifdef CONFIG_SMP

/*
 * per_cpu_offset() is the offset that has to be added to a
 * percpu variable to get to the instance for a certain processor.
 *
 * Most arches use the __per_cpu_offset array for those offsets but
 * some arches have their own ways of determining the offset (x86_64, s390).
 */
#ifndef __per_cpu_offset
extern unsigned long __per_cpu_offset[NR_CPUS];

#define per_cpu_offset(x) (__per_cpu_offset[x])
#endif

/*
 * Determine the offset for the currently active processor.
 * An arch may define __my_cpu_offset to provide a more effective
 * means of obtaining the offset to the per cpu variables of the
 * current processor.
 */
#ifndef __my_cpu_offset
#define __my_cpu_offset per_cpu_offset(raw_smp_processor_id())
#endif
#ifdef CONFIG_DEBUG_PREEMPT
#define my_cpu_offset per_cpu_offset(smp_processor_id())
#else
#define my_cpu_offset __my_cpu_offset
#endif

/*
 * Add a offset to a pointer but keep the pointer as is.
 *
 * Only S390 provides its own means of moving the pointer.
 */
#ifndef SHIFT_PERCPU_PTR
/* Weird cast keeps both GCC and sparse happy. */
#define SHIFT_PERCPU_PTR(__p, __offset)	({				\
	__verify_pcpu_ptr((__p));					\
	RELOC_HIDE((typeof(*(__p)) __kernel __force *)(__p), (__offset)); \
})
#endif

/*
 * A percpu variable may point to a discarded regions. The following are
 * established ways to produce a usable pointer from the percpu variable
 * offset.
 */
#define per_cpu(var, cpu) \
	(*SHIFT_PERCPU_PTR(&amp;(var), per_cpu_offset(cpu)))
#define __get_cpu_var(var) \
	(*SHIFT_PERCPU_PTR(&amp;(var), my_cpu_offset))
#define __raw_get_cpu_var(var) \
	(*SHIFT_PERCPU_PTR(&amp;(var), __my_cpu_offset))

#define this_cpu_ptr(ptr) SHIFT_PERCPU_PTR(ptr, my_cpu_offset)
#define __this_cpu_ptr(ptr) SHIFT_PERCPU_PTR(ptr, __my_cpu_offset)

#ifdef CONFIG_HAVE_SETUP_PER_CPU_AREA
extern void setup_per_cpu_areas(void);
#endif

#else /* ! SMP */

#define per_cpu(var, cpu)			(*((void)(cpu), &amp;(var)))
#define __get_cpu_var(var)			(var)
#define __raw_get_cpu_var(var)			(var)
#define this_cpu_ptr(ptr) per_cpu_ptr(ptr, 0)
#define __this_cpu_ptr(ptr) this_cpu_ptr(ptr)

#endif	/* SMP */

#ifndef PER_CPU_BASE_SECTION
#ifdef CONFIG_SMP
#define PER_CPU_BASE_SECTION &quot;.data.percpu&quot;
#else
#define PER_CPU_BASE_SECTION &quot;.data&quot;
#endif
#endif

#ifdef CONFIG_SMP

#ifdef MODULE
#define PER_CPU_SHARED_ALIGNED_SECTION &quot;&quot;
#define PER_CPU_ALIGNED_SECTION &quot;&quot;
#else
#define PER_CPU_SHARED_ALIGNED_SECTION &quot;.shared_aligned&quot;
#define PER_CPU_ALIGNED_SECTION &quot;.shared_aligned&quot;
#endif
#define PER_CPU_FIRST_SECTION &quot;.first&quot;

#else

#define PER_CPU_SHARED_ALIGNED_SECTION &quot;&quot;
#define PER_CPU_ALIGNED_SECTION &quot;.shared_aligned&quot;
#define PER_CPU_FIRST_SECTION &quot;&quot;

#endif
</pre>
<p>通常所有的 percpu 变量是一起存放在特定的 section 里的，像上面头文件中的 .data.percpu 基础 section（ 当然非 SMP 系统下就是 .data 了）、.shared_aligned、.first section。使用 objdump 可以看到编译 kernel 时的 vmlinux 文件的 section（结果没有完全显示）：</p>
<pre class="brush: bash; title: ; notranslate">
objdump -h vmlinux

vmlinux:     file format elf64-x86-64

  0 .text         0037a127  ffffffff81000000  0000000001000000  00200000  2**12
                  CONTENTS, ALLOC, LOAD, READONLY, CODE
  3 .rodata       0013c8ec  ffffffff8137f000  000000000137f000  0057f000  2**6
                  CONTENTS, ALLOC, LOAD, READONLY, DATA
 11 .data         0004d920  ffffffff814ec000  00000000014ec000  006ec000  2**12
                  CONTENTS, ALLOC, LOAD, DATA
 19 .data.percpu  00012880  0000000000000000  000000000153b000  00a00000  2**12
                  CONTENTS, ALLOC, LOAD, DATA
</pre>
<p>可以看到 vmlinux 文件中的 .data 和 .data.percpu section。</p>
<p>percpu 变量的地址实际上就是其在上面说到的 section 里的偏移量，这个偏移量还要加上特定处理器的偏移量（也就是上面头文件中的 per_cpu_offset、my_cpu_offset 等）得到最终的变量地址，并最终以指针引用的方式得到值，这样访问的效果就有点类似于访问全局变量了。percpu 变量通常用于更新非常频繁而访问机会又相对比较少的场合，这样的处理方式可以避免多处理器环境下的频繁加锁等操作。</p>
<p>从上面的注释也可以看到 per_cpu_offset 是在一个 percpu 变量上增加的偏移量，大多数系统架构下使用 __per_cpu_offset 数组来作为偏移量，而 x86_64 等架构下处理方式则不同。my_cpu_offset 是在调用 per_cpu_offset 时使用 smp_processor_id() 得到当前处理器 ID 作为参数，__my_cpu_offset 则是用 raw_smp_processor_id() 的值作为 per_cpu_offset 的参数（smp_processor_id() 在抢占被关闭时是安全的）。SHIFT_PERCPU_PTR 宏用于给指针增加偏移量，它使用的 RELOC_HIDE 宏在不同的编译器下实现不同，在 include/linux/compiler.h 头文件中，看看 gcc 编译下的处理：</p>
<pre class="brush: cpp; title: include/linux/compiler-gcc.h; notranslate">
#define RELOC_HIDE(ptr, off)					\
  ({ unsigned long __ptr;					\
    __asm__ (&quot;&quot; : &quot;=r&quot;(__ptr) : &quot;0&quot;(ptr));		\
    (typeof(ptr)) (__ptr + (off)); })
</pre>
<p>可以看到 gcc 中使用内嵌汇编先将 ptr 值赋给 __ptr（unsigned long 类型），然后在 __ptr 基础上增加偏移量，这样可以避免编译报错，ptr 值不变而且最终以 ptr 指定的类型来返回。</p>
<p>include/asm-generic/percpu.h 头文件中定义了 per_cpu、__get_cpu_var、__raw_get_cpu_var、this_cpu_ptr、__this_cpu_ptr 等几个常用的宏。per_cpu 就用于得到某个指定处理器的变量，__get_cpu_var 用于得到当前处理器的 percpu 变量值。</p>
<p>再来看看 DEFINE_PER_CPU 的实现，它在 include/linux/percpu-defs.h 头文件中：</p>
<pre class="brush: cpp; title: include/linux/percpu-defs.h; notranslate">
#define __PCPU_ATTRS(sec)						\
	__percpu __attribute__((section(PER_CPU_BASE_SECTION sec)))	\
	PER_CPU_ATTRIBUTES

#define DEFINE_PER_CPU_SECTION(type, name, sec)				\
	__PCPU_ATTRS(sec) PER_CPU_DEF_ATTRIBUTES			\
	__typeof__(type) name

#define DEFINE_PER_CPU(type, name)					\
	DEFINE_PER_CPU_SECTION(type, name, &quot;&quot;)
</pre>
<p>使用 DEFINE_PER_CPU 宏可以静态的定义 percpu 变量。__PCPU_ATTRS 指定输入的 section 类型，DEFINE_PER_CPU_SECTION 用于在特定的 section 上定义特定类型的变量。__typeof__ 和 上面见到的 typeof 是一样的，都用于获取 type 的数据类型。__attribute__((section(xxx))) 表示把定义的变量存储在指定的 section 上。DEFINE_PER_CPU 就用于定义在 PER_CPU_BASE_SECTION section 上（从最开始的代码中也可以看出非 SMP 时用 .data 段，SMP 时用 .data.percpu 段）。</p>
<p>然后是 get_cpu_var 宏的实现，它在 include/linux/percpu.h 头文件中：</p>
<pre class="brush: cpp; title: include/linux/percpu.h; notranslate">
/*
 * 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();				\
	&amp;__get_cpu_var(var); }))

/*
 * The weird &amp; is necessary because sparse considers (void)(var) to be
 * a direct dereference of percpu variable (var).
 */
#define put_cpu_var(var) do {				\
	(void)&amp;(var);					\
	preempt_enable();				\
} while (0)

#define alloc_percpu(type)	\
	(typeof(type) __percpu *)__alloc_percpu(sizeof(type), __alignof__(type))
</pre>
<p>get_cpu_var 会先禁止抢占然后调用 __get_cpu_var 得到 percpu 变量值。put_cpu_var 则重新启用抢占。</p>
<p>另外在 include/linux/percpu.h 等文件中还定义了 alloc_percpu 和 free_percpu 宏来动态定义和释放 percpu 变量，他们都是通过 percpu memory allocator 来实现的，在 mm/percpu.c 中，动态分配的 percpu 变量可以通过 per_cpu_ptr 宏来得到，为此 kernel 还引入了 this_cpu_ptr、this_cpu_read 等一系列相关机制用寄存器替代内存提高对 percpu 变量的访问速度，关于 percpu memory allocator 等信息以后再来详细分析了。</p>
<p>以上为个人分析结果，有任何问题欢迎指正咯 ^_^</p>
]]></content:encoded>
			<wfw:commentRss>https://zohead.com/archives/linux-kernel-percpu-variable/feed/</wfw:commentRss>
		<slash:comments>0</slash:comments>
		</item>
	</channel>
</rss>
