linux内核percpu变量声明,Linux kernel percpu变量解析
Linux 2.6 kernel 中的 percpu 變量是經常用到的東西,因為現在很多計算機都已經支持多處理器了,而且 kernel 默認都會被編譯成 SMP 的,相對于原來多個處理器共享數據并進行處理的方式,用 percpu 變量在 SMP、NUMA 等架構下可以提高性能,而且很多情況下必須用 percpu 來對不同的處理器做出數據區分。
本文以 kernel 中的 softirq 為例簡單說下 percpu 變量,我們先來看看 kernel 中喚醒 ksoftirqd 的實現,ksoftirqd 在 ps 命令看到的進程列表中很容易找到,是每個處理器都有一個(如果有 4 個處理器,則有 4 個 kernel 線程名稱分別從 ksoftirqd/0 到 ksoftirqd/3),關于 softirq 本身的實現不在本文討論范圍內,喚醒 ksoftirqd 的實現在 kernel/softirq.c 文件中:
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 && tsk->state != TASK_RUNNING)
wake_up_process(tsk);
}
這里就用到了 percpu 變量 ksoftirqd,它是通過 DEFINE_PER_CPU 宏來進程定義的 percpu task_struct 列表,通過 __get_cpu_var 宏來得到相應處理器的 ksoftirqd/n 的 task_struct,然后調用 wake_up_process 函數喚醒進程(也就是 ksoftirqd/n kernel 線程),關于 wake_up_process 等進程調度的相關實現在之前的日志中有介紹的,請參考 [這里]。
__get_cpu_var、DEFINE_PER_CPU 等 percpu 宏的實現在 include/linux/percpu.h、include/asm-generic/percpu.h 等頭文件中。先看看 include/asm-generic/percpu.h 中的一些定義:
#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(&(var), per_cpu_offset(cpu)))
#define __get_cpu_var(var) \
(*SHIFT_PERCPU_PTR(&(var), my_cpu_offset))
#define __raw_get_cpu_var(var) \
(*SHIFT_PERCPU_PTR(&(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), &(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 ".data.percpu"
#else
#define PER_CPU_BASE_SECTION ".data"
#endif
#endif
#ifdef CONFIG_SMP
#ifdef MODULE
#define PER_CPU_SHARED_ALIGNED_SECTION ""
#define PER_CPU_ALIGNED_SECTION ""
#else
#define PER_CPU_SHARED_ALIGNED_SECTION ".shared_aligned"
#define PER_CPU_ALIGNED_SECTION ".shared_aligned"
#endif
#define PER_CPU_FIRST_SECTION ".first"
#else
#define PER_CPU_SHARED_ALIGNED_SECTION ""
#define PER_CPU_ALIGNED_SECTION ".shared_aligned"
#define PER_CPU_FIRST_SECTION ""
#endif
通常所有的 percpu 變量是一起存放在特定的 section 里的,像上面頭文件中的 .data.percpu 基礎 section( 當然非 SMP 系統下就是 .data 了)、.shared_aligned、.first section。使用 objdump 可以看到編譯 kernel 時的 vmlinux 文件的 section(結果沒有完全顯示):
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
可以看到 vmlinux 文件中的 .data 和 .data.percpu section。
percpu 變量的地址實際上就是其在上面說到的 section 里的偏移量,這個偏移量還要加上特定處理器的偏移量(也就是上面頭文件中的 per_cpu_offset、my_cpu_offset 等)得到最終的變量地址,并最終以指針引用的方式得到值,這樣訪問的效果就有點類似于訪問全局變量了。percpu 變量通常用于更新非常頻繁而訪問機會又相對比較少的場合,這樣的處理方式可以避免多處理器環境下的頻繁加鎖等操作。
從上面的注釋也可以看到 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 編譯下的處理:
#define RELOC_HIDE(ptr, off)\
({ unsigned long __ptr;\
__asm__ ("" : "=r"(__ptr) : "0"(ptr));\
(typeof(ptr)) (__ptr + (off)); })
可以看到 gcc 中使用內嵌匯編先將 ptr 值賦給 __ptr(unsigned long 類型),然后在 __ptr 基礎上增加偏移量,這樣可以避免編譯報錯,ptr 值不變而且最終以 ptr 指定的類型來返回。
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 變量值。
再來看看 DEFINE_PER_CPU 的實現,它在 include/linux/percpu-defs.h 頭文件中:
#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, "")
使用 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 段)。
然后是 get_cpu_var 宏的實現,它在 include/linux/percpu.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();\
&__get_cpu_var(var); }))
/*
* The weird & is necessary because sparse considers (void)(var) to be
* a direct dereference of percpu variable (var).
*/
#define put_cpu_var(var) do {\
(void)&(var);\
preempt_enable();\
} while (0)
#define alloc_percpu(type)\
(typeof(type) __percpu *)__alloc_percpu(sizeof(type), __alignof__(type))
get_cpu_var 會先禁止搶占然后調用 __get_cpu_var 得到 percpu 變量值。put_cpu_var 則重新啟用搶占。
另外在 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 等信息以后再來詳細分析了。
以上為個人分析結果,有任何問題歡迎指正咯 ^_^
總結
以上是生活随笔為你收集整理的linux内核percpu变量声明,Linux kernel percpu变量解析的全部內容,希望文章能夠幫你解決所遇到的問題。
- 上一篇: 哈利波特魔法觉醒手游哈利彩蛋怎么打
- 下一篇: 《楚乔传》采薇是谁演的 采薇结局怎么死的