赞
踩
本文转自 http://www.wowotech.net/kernel_synchronization/per-cpu.html。蜗窝出品,必属精品。
在 ARM 平台上,ARMv6 之前,SWP 和 SWPB 指令被用来支持对 shared memory 的访问:
SWP <Rt>, <Rt2>, [<Rn>]
Rn 中保存了 SWP 指令要操作的内存地址,通过该指令可以将 Rn 指定的内存数据加载到 Rt 寄存器,同时将 Rt2 寄存器中的数值保存到 Rn 指定的内存中去。
我们在原子操作那篇文档中描述的 read-modify-write 的问题,本质上是一个保持对内存 read 和 write 访问的原子性的问题。也就是说对内存的读和写的访问不能被打断。对该问题的解决可以通过硬件、软件或者软硬件结合的方法来进行。早期的 ARM CPU 给出的方案就是依赖硬件:SWP 这个汇编指令执行了一次读内存操作、一次写内存操作。但是从程序员的角度看,SWP 这条指令就是原子的,读写之间不会被任何的异步事件打断。具体底层的硬件是如何做的呢?这时候,硬件会提供一个 lock signal,在进行 memory 操作的时候设定 lock 信号,告诉总线这是一个不可被中断的内存访问,直到完成了 SWP 需要进行的两次内存访问之后再 clear lock 信号。(对于 SWP 指令,笔者还是想不通原子自加一操作是如何完成的,即 Rt2 寄存器的值依赖 Rt 寄存器中的值。希望知晓的读者能留言释疑,感激不尽。)
lock memory bus 对多核系统的性能造成严重的影响(系统中其他的 processor 对那条被 lock 的 memory bus 的访问就被 hold 住了),如何解决这个问题?最好的锁机制就是不使用锁,因此解决这个问题可以使用釜底抽薪的方法,那就是不在系统中的多个 processor 之间共享数据,给每一个 CPU 分配一个不就 OK 了吗?
当然,随着技术的发展,在 ARMv6 之后的 ARM CPU 已经不推荐使用 SWP 这样的指令,而是提供了 LDREX 和 STREX 这样的指令。这种方法是使用软硬件结合的方法来解决原子操作问题,看起来代码比较复杂,但是系统的性能可以得到提升(LDREX 和 STREX 相较 SWP,优势体现在 SMP 架构上)。其实,从硬件角度看,LDREX 和 STREX 这样的指令也是采用了 lock-free 的做法。OK,由于不再 lock bus(指的 ARMv6 后的 LDREX 和 STREX 这样的指令),看起来 Per-CPU 变量存在的基础被打破了。不过考虑 cache 的操作,实际上它还是有意义的。
在 The Memory Hierarchy 文档中,我们已经了解了关于 memory 一些基础的知识,一些基础的内容,这里就不再重复了。我们假设一个多核系统中的 cache 如下:
每个 CPU 都有自己的 L1 cache(包括 data cache 和 instruction cache),所有的 CPU 共用一个 L2 cache。L1、L2 以及 main memory 的访问速度之间的差异都是非常大,最高的性能的情况下当然是 L1 cache hit,这样就不需要访问下一阶 memory 来加载 cache line。
我们首先看在多个 CPU 之间共享内存的情况。这种情况下,任何一个 CPU 如果修改了共享内存就会导致所有其他 CPU 的 L1 cache 上对应的 cache line 变成 invalid(硬件完成)。虽然对性能造成影响,但是系统必须这么做,因为需要维持 cache 的同步。将一个共享 memory 变成 Per-CPU memory 本质上是一个耗费更多 memory 来解决 performance 的方法。当一个在多个 CPU 之间共享的变量变成每个 CPU 都有属于自己的一个私有的变量的时候,我们就不必考虑来自多个 CPU 上的并发,仅仅考虑本 CPU 上的并发就 OK 了(Per-CPU 变量解决的是多个 CPU 上的并发,本 CPU 上的并发需要其它的同步机制来解决)。当然,还有一点要注意,那就是在访问 Per-CPU 变量的时候,不能调度(Per-CPU 变量和禁止抢占加在一起才能完全避免多个 CPU 上的并发),当然更准确的说法是该 task 不能调度到其他 CPU 上去。目前的内核的做法是在访问 Per-CPU 变量的时候 disable preemptive,虽然没有能够完全避免使用锁的机制(disable preemptive也是一种锁的机制),但毫无疑问,这是一种代价比较小的锁。
声明和定义 Per-CPU 变量的 API | 描述 |
DECLARE_PER_CPU(type, name) DEFINE_PER_CPU(type, name) | 普通的、没有特殊要求的 Per-CPU 变量定义接口函数。没有对齐的要求。 |
DECLARE_PER_CPU_FIRST(type, name) DEFINE_PER_CPU_FIRST(type, name) | 通过该 API 定义的 Per-CPU 变量位于整个 Per-CPU 相关 section 的最前面。 |
DECLARE_PER_CPU_SHARED_ALIGNED(type, name) DEFINE_PER_CPU_SHARED_ALIGNED(type, name) | 通过该 API 定义的 Per-CPU 变量在 SMP 的情况下会对齐到 L1 cache line,对于UP,不需要对齐到 cach line。 |
DECLARE_PER_CPU_ALIGNED(type, name) DEFINE_PER_CPU_ALIGNED(type, name) | 无论 SMP 或者 UP,都是需要对齐到 L1 cache line。 |
DECLARE_PER_CPU_PAGE_ALIGNED(type, name) DEFINE_PER_CPU_PAGE_ALIGNED(type, name) | 为定义 page aligned Per-CPU 变量而设定的 API 接口。 |
DECLARE_PER_CPU_READ_MOSTLY(type, name) DEFINE_PER_CPU_READ_MOSTLY(type, name) | 通过该 API 定义的 Per-CPU 变量是 read mostly 的。 |
看到这样“丰富多彩”的 Per-CPU 变量的 API,你是不是已经醉了。这些定义使用在不同的场合,主要的 factor 包括:
-该变量在 section 中的位置
-该变量的对齐方式
-该变量对 SMP 和 UP 的处理不同
-访问 Per-CPU 的形态
例如:如果你准备定义的 Per-CPU 变量是要求按照 page 对齐的,那么在定义该 Per-CPU 变量的时候需要使用 DECLARE_PER_CPU_PAGE_ALIGNED。如果只要求在 SMP 的情况下对齐到 cache line,那么使用DECLARE_PER_CPU_SHARED_ALIGNED 来定义该 Per-CPU 变量。
静态定义的 Per-CPU 变量不能像普通变量那样进行访问,需要使用特定的接口函数,具体如下:
- get_cpu_var(var)
-
- put_cpu_var(var)
上面这两个接口函数已经内嵌了锁的机制(preempt disable),用户可以直接调用该接口进行本 CPU 上该变量副本的访问。如果用户确认当前的执行环境已经是 preempt disable(例如持有 spinlock),那么可以使用 lock-free 版本的 Per-CPU 变量的API:__get_cpu_var。
动态分配和释放 Per-CPU 变量的 API | 描述 |
alloc_percpu(type) | 分配类型是 type 的 Per-CPU 变量,并返回 Per-CPU 变量的地址(注意:不是各个 CPU 上的副本) |
void free_percpu(void __percpu *ptr) | 释放 ptr 指向的 Per-CPU 变量空间 |
访问动态分配 Per-CPU 变量的 API | 描述 |
get_cpu_ptr | 这个接口是和访问静态 Per-CPU 变量的 get_cpu_var 接口是类似的,当然,这个接口是 for 动态分配 Per-CPU变量 |
put_cpu_ptr | 同上 |
per_cpu_ptr(ptr, cpu) | 根据 Per-CPU 变量的地址和 CPU number,返回指定 CPU number 上该 Per-CPU 变量的地址 |
我们以 DEFINE_PER_CPU 的实现为例子,描述 Linux kernel 中如何实现静态 Per-CPU 变量定义。具体代码如下:
- #define DEFINE_PER_CPU(type, name) \
- DEFINE_PER_CPU_SECTION(type, name, "")
type 就是变量的类型,name 是 Per-CPU 变量符号。DEFINE_PER_CPU_SECTION 宏可以把一个 Per-CPU 变量放到指定的 section 中,具体代码如下:
- #define DEFINE_PER_CPU_SECTION(type, name, sec) \
- __PCPU_ATTRS(sec) PER_CPU_DEF_ATTRIBUTES \------安排section
- __typeof__(type) name--------------------------------------定义变量
在这里具体 arch specific 的 percpu 代码中(arch/arm/include/asm/percpu.h)可以定义 PER_CPU_DEF_ATTRIBUTES,以便控制该 Per-CPU 变量的属性。当然,如果 arch specific 的 percpu 代码不定义,那么在 general arch-independent 的代码中(include/asm-generic/percpu.h)会定义为空。这里可以顺便提一下 Per-CPU 变量的软件层次:
(1)arch-independent interface(架构无关层)。在include/linux/percpu.h文件中,定义了内核其他模块要使用 percpu 机制使用的接口 API 以及相关数据结构的定义。内核其他模块需要使用 Per-CPU 变量接口的时候需要 include 该头文件。
(2)arch-general interface(架构通用层)。在 include/asm-generic/percpu.h 文件中。如果所有的 arch 相关的定义都是一样的,那么就把它抽取出来,放到 asm-generic 目录下。毫无疑问,这个文件定义的接口和数据结构是硬件相关的,只不过软件抽象各个 arch-specific 的内容,形成一个 arch general layer。一般来说,我们不需要直接 include 该头文件,include/linux/percpu.h 会 include 该头文件。
(3)arch-specific(特定架构层)。这是和硬件相关的接口,在 arch/arm/include/asm/percpu.h,定义了 ARM 平台中,具体和 percpu 相关的接口代码。
我们回到正题,看看 __PCPU_ATTRS 的定义:
- #define __PCPU_ATTRS(sec) \
- __percpu __attribute__((section(PER_CPU_BASE_SECTION sec))) \
- PER_CPU_ATTRIBUTES
PER_CPU_BASE_SECTION 定义了基础的 section name symbol,定义如下:
- #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_star t和 __per_cpu_end 之间,我们称之 Per-CPU 变量的原始变量(我也想不出什么好词了)。
只有 Per-CPU 变量的原始变量还是不够的,必须为每一个 CPU 建立一个副本,怎么建?直接静态定义一个 NR_CPUS 的数组?NR_CPUS 定义了系统支持的最大的 processor 的个数,并不是实际中系统 processor 的数目,这样的定义非常浪费内存。此外,静态定义的数据在内存中连续,对于 UMA(Uniform Memory Architechture,统一内存访问架构,了解即可) 系统而言是 OK 的,对于 NUMA(Non Uniform Memory Architechture) 系统,每个 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 变量,需要在系统初始化的时候,对应 percpu section,预先动态分配一个同样 size 的 percpu chunk。在 vmlinux.lds.h 文件中,定义了 percpu section 的排列情况:
- #define PERCPU_INPUT(cacheline) \
- VMLINUX_SYMBOL(__per_cpu_start) = .; \
- *(.data..percpu..first) \
- . = ALIGN(PAGE_SIZE); \
- *(.data..percpu..page_aligned) \
- . = ALIGN(cacheline); \
- *(.data..percpu..readmostly) \
- . = ALIGN(cacheline); \
- *(.data..percpu) \
- *(.data..percpu..shared_aligned) \
- VMLINUX_SYMBOL(__per_cpu_end) = .;
对于 build in 内核的那些 Per-CPU 变量,必然位于 __per_cpu_start 和 __per_cpu_end 之间的 percpu section。在系统初始化的时候(setup_per_cpu_areas),分配 percpu memory chunk,并将 percpu section copy 到每一个 chunk 中。
代码如下:
- #define get_cpu_var(var) (*({ \
- preempt_disable(); \
- &__get_cpu_var(var); }))
再看到 get_cpu_var 和 __get_cpu_var 这两个符号,相信广大人民群众已经相当的熟悉,一个持有锁的版本,一个 lock-free 的版本。为防止当前 task 由于抢占而调度到其他的 CPU 上,在访问 percpu memory 的时候都需要使用 preempt_disable 这样的锁的机制。我们来看 __get_cpu_var:
- #define __get_cpu_var(var) (*this_cpu_ptr(&(var)))
-
- #define this_cpu_ptr(ptr) __this_cpu_ptr(ptr)
对于 ARM 平台,我们没有定义 __this_cpu_ptr,因此采用 asm-general 版本的:
#define __this_cpu_ptr(ptr) SHIFT_PERCPU_PTR(ptr, __my_cpu_offset)
SHIFT_PERCPU_PTR 这个宏定义从字面上就可以看出它是可以从原始的 Per-CPU 变量的地址,通过简单的变换(SHIFT)转成实际的 Per-CPU 变量副本的地址。实际上,percpu 内存管理模块可以保证原始的 Per-CPU 变量的地址和各个 CPU 上的 Per-CPU 变量副本的地址有简单的线性关系(就是一个固定的 offset)。__my_cpu_offset 这个宏定义就是和 offset 相关的,如果 arch specific 没有定义,那么可以采用 asm general 版本的,如下:
#define __my_cpu_offset per_cpu_offset(raw_smp_processor_id())
raw_smp_processor_id 可以获取本 CPU 的 ID,如果没有 arch specific 没有定义 __per_cpu_offset 这个宏,那么 offset 保存在 __per_cpu_offset 的数组中(下面只是数组声明,具体定义在 mm/percpu.c 文件中),如下:
- #ifndef __per_cpu_offset
- extern unsigned long __per_cpu_offset[NR_CPUS];
-
- #define per_cpu_offset(x) (__per_cpu_offset[x])
- #endif
对于 ARMV6K 和 ARMv7 版本,offset 保存在 TPIDRPRW 寄存器(又是架构相关的东西,尴尬)中,这样是为了提升系统性能。
这部分内容留给内存管理子系统吧。
精选原文下面的部分评论如下,并记录下笔者对各个问题的看法:
bird
2019-08-26 16:02很多人在考虑同步的事情,可能是因为例子里提到了 lock bus 这些。其实本质上,Per-CPU 是基于空间换时间的方法,让每个 CPU 都有自己的私有数据段(放在 L1 中),并将一些变量私有化到每个 CPU 的私有数据段中。单个 CPU 在访问自己的私有数据段时,不需要考虑其他 CPU 之间的竞争问题,也不存在同步的问题。注意只有在该变量在各个 CPU 上逻辑独立时才可使用。
xiaoai
2019-05-27 17:51Hi linux:
当然,还有一点要注意,那就是在访问 Per-CPU 变量的时候,不能调度,当然更准确的说法是该 task 不能调度到其他 CPU 上去。
我不太理解这里,即使调度到其他 CPU 上面去也没关系啊,反正访问操作的也是其他 CPU 上的值。我理解是处理好本 CPU 上的同步就好了,关闭调度是为了避免该 CPU 上其他可能存在的并发调度。@xiaoai:因为 Per-CPU 的原则是各个 CPU 只访问属于自己的数据,故在实际访问时要关闭抢占。即当 A CPU 在访问自己的私有变量时,该 task 不能被调度到其他 CPU 上去,不然就造成了 B CPU 在访问 A CPU 的私有变量。
笔者理解:上面这段评论我个人理解 bird 同学的说法是没有问题的,但总觉得没有透彻解释为什么 xiaoai 同学的说法不对。正如上面我用红色字体标明的那部分文字,xiaoai 同学之所以认为正在访问 Per-CPU 变量的 task(为方便表述,记为A) 被调度到其它 CPU 上也没关系,是因为他认为就算 task A 在其它 CPU 上运行,访问操作的也是其它 CPU 上的 Per-CPU 变量。这是不对的!顺着 xiaoai 同学的思路,我们假设 get_cpu_var 没有禁止抢占,那么代码就会变成:
#define get_cpu_var(var) (*({ \
&__get_cpu_var(var); }))假设 task A 最开始欢快地运行在 CPU0 上,那么执行完 get_cpu_var 后 task A 拿到的就是 CPU0 的 Per-CPU 副本。假设此时发生调度,随后 task A 被调度到了 CPU1 上,而 task B 获得 CPU0 的执行权,很不巧,task B 也要访问 CPU0 的 Per-CPU 副本。虽然 task A 被调度到了 CPU1 上,但调度前拿到的却是 CPU0 的 Per-CPU 副本!很显然,task A 和 B 对 CPU0 的 Per-CPU 副本构成了并发访问。如果这种场景允许出现,那 Per-CPU 变量存在的意义是什么? Per-CPU 变量存在的意义不就是为了避免多核 CPU 之间的并发吗?!
speedan
2017-03-08 11:53linuxer:你好,请教个问题:
关于per-cpu变量的接口,只说get_cpu_var()这类接口会处理抢占的问题,但没说明中断互斥的问题。
比如有个per-cpu变量也会在中断处理程序中用到,那在普通进程上下文中使用这个per-cpu变量时,是不是在调用get_cpu_var()之前也应该先通过spin_lock()或关本地中断的方式来进行互斥,防止和中断处理程序的竞争问题?@speedan:另外,下边的用法中,get_cpu()和put_cpu()应该是在for循环之类还是之外,望请指点,谢谢!
int i, curcpu;
curcpu = get_cpu(); // 禁止抢占
for_each_possible_cpu(i) {
sec_info = &per_cpu(my_birthday, i).sec_info; // per_cpu()不会禁止抢占
sec_info->sec++;
}
put_cpu(); // 重新开启抢占@speedan:你这是什么代码?你能不能描述一下临界区中的数据访问情况是怎样的?
@linuxer:不好意思,我之前描述的问题不准确,修改如下:
我的理解,使用per-cpu变量的前提是每个CPU只能访问此变量对应当前CPU的部分,这样就不存在多个CPU并发访问的问题。
但对于需要遍历per-cpu变量的场景,意味着在当前CPU需要访问其他所有CPU对应的部分,这样使用per-cpu变量的前提就不再成立,此时似乎陷入了自我矛盾之中,应该怎么处理?
我看内核的代码在这种情况下好像没有做过多的保护,所以挺奇怪的。
// 下边的代码是我设想的一个遍历的例子
int i, curcpu, sum;
static DEFINE_PER_CPU(int, arr) = {0};
for_each_possible_cpu(i) { // 遍历per-cpu变量时怎么处理多CPU并发访问的问题?
sum += per_cpu(arr, i);
per_cpu(arr, i)++;
}@speedan:其实这个问题仍然是一个基本的临界区保护的问题,per cpu变量并不会让临界区的保护更复杂,只不过是把数据访问分散到各个cpu上而已。
我们来举一个实际的例子好了:有一个per cpu变量A,该变量表示某个事件的count,该变量不会在中断上下文中访问,只会在进程上下文访问。访问的场景有两个:
1、各个cpu上的写进程会对A累加操作,记录各个cpu上事件发生的次数
2、读进程遍历全部cpu,对各个cpu上的A累加,得到一个global count值
在这样的数据访问情况下,读进程需要锁保护吗?如果没有锁保护,那么有可能写进程会插入,但是这又有什么关系?累加后的那个global count值有那么高的精度要求吗?
因此,结论是: 遍历per-cpu变量时需不需要保护,用什么锁机制保护是和per cpu变量的临界区数据访问情况相关的,不同的情况需要不同的分析。@linuxer:你说的这种原子变量的情况我也设想过,只要精度不要求,确实没关系。
我只是看到内核中也有把per cpu变量用到了结构体中,这样就有可能出现结构体部分被修改的情况。
不过如你所说,对于这种情况,如果意识到会出现竞争问题就不应该使用per cpu变量了吧……@speedan:per-cpu变量只是能够保护来自不同cpu的并发访问,并不能保护同一个cpu上,进程上下文和中断上下文中的并发,这时候,往往需要其他的同步原语。
@linuxer:你说得对,我理解得不够本质,赞!!!
这段对话的重点总结如下:
1、如果中断处理程序中也访问到 Per-CPU 变量会是什么情况?根据中断发生在本 CPU 还是其它 CPU 上两种情况。
2、对于遍历每个 CPU 的 Per-CPU 变量的场景,需要锁保护吗?如果需要保证没有误差有什么办法吗?
lamaboy
2016-08-20 11:44你好,我看了Per-CPU变量,还是没有理出Per-CPU变量的使用场景,请指教下了 谢谢了
@lamaboy:千言万语汇成一句话:性能,per-cpu变量就是为了更好的性能而已。或者更通俗的讲:就是用空间换时间的一种优化而已。
Per-CPU 变量的使用场景,非常好的问题!比如,对于不涉及中断的并发场景,原子变量能改成 Per-CPU 变量吗?
puppypyb
2015-04-29 10:07Quote:"当然,还有一点要注意,那就是在访问Per-CPU变量的时候,不能调度,当然更准确的说法是该task不能调度到其他CPU上去。目前的内核的做法是在访问Per-CPU变量的时候disable preemptive"
谈一点感想,有不对的地方请指出:
引入per-cpu变量的原因就是文中所说的,每个cpu都在自己的变量副本上工作,这样每次读写就可以避免锁开销,上下文切换和cache等一系列问题。假设当前task运行在其中一个core cpu0上且获得了var在该cpu上的本地副本,然后该task在操作该副本的过程中因被抢占而转移到另外一个core cpu1上运行,那么接下来cpu1将会继续操作该变量副本(属于cpu0),违反了“每个cpu都工作在自己副本上”这样一个前提,该机制就乱套了。
上面是我所认为的disable preemptive的初衷,就是在操作per-cpu副本的过程中防止task调度到其他cpu上去。但同样的get_cpu_var(var)的时候也disable了本CPU的调度。我想了下,在这样的场景下disable本cpu的调度好像没有必要,我理解为disable preemptive的副作用。@puppypyb:我看到一个代码似乎per-CPU在访问的时候没有禁止抢占。
内核里面对vfsmount这个结构体做计数器增加的时候
struct vfsmount *mntget(struct vfsmount *mnt)
直接就对per-cpu的mnt_count值做this_cpu_add(mnt->mnt_pcp->mnt_count, n);操作了
最后统计vfsmount结构体的count值的时候
在读写锁的保护下,把per-cpu的count值都加到一起了,也没有后面comment里面的阈值的感觉
for_each_possible_cpu(cpu) {
count += per_cpu_ptr(mnt->mnt_pcp, cpu)->mnt_count;
}@scnutiger:this_cpu_add在4.1.10内核中的实现如下:
#define this_cpu_add(pcp, val) __pcpu_size_call(this_cpu_add_, pcp, val)
如果你愿意一层层的看下去,会发现其实this_cpu_add也会内嵌禁止抢占的代码的。@puppypyb:你说的很对,只不过最后一段话我不是非常理解。我是这么看的,用户在使用percpu变量的时候调用场景是这样的:
----------------
get_cpu_var
访问per-cpu变量
put_cpu_var
----------------
用户并不需要显式的调用禁止抢占,per-cpu API已经封装好了,当然,一个好的模块实现必然是这样的。笔者理解:puppypyb 同学在质疑 get_cpu_var(var) 的时候也 disable 了本 CPU 的调度 的必要性,确实,单就 get 来说,没有必要,但就像最后郭大侠所指出的场景一样,get 后一般都会进行访问,一个好的模块实现必然是如此体贴。
xiaoxiao
2015-04-22 09:37Hi
我一直不明白per-CPU的意义。它是为了解决CPU之间同步问题引入的,但到底是怎么解决的呢?
每个CPU都有自己的副本,也没有讲副本和原始变量之间的同步。如果不需要同步,那每个CPU的副本不相当于四个变量了?如果需要同步,那不跟设计per-cpu之前一样的问题?
是否是这样:
每个CPU都可以对自己的副本进行操作,比如+1,-3之类的操作,但是不会实时同步到原始变量中,但是有个阈值,比如30,当你修改超过30后,则必须同步修改到原始变量中。但是这样的话,原始变量中的值是个近似值,不是准确值。(什么情况下可以使用计数器的近似值而不需要准确值呢?)@xiaoxiao:毫无疑问per-CPU变量之间不需要同步,否则per cpu不就没有意义了吗?每个CPU上的执行的代码就是访问属于本cpu的那个变量。原始变量仅仅是作为一个initial data区域,当per cpu变量的副本分配之后,将原始变量的initial data copy到各自per cpu的变量副本中。
@linuxer:Hi linuxer
多谢回复。确实per-cpu之间是不需要同步的,但是原始变量和副本之间还是有同步的需要的(有spinlock)。刚找到在professional linux kernel architecture书中关于per-cpu的说明还算详细(5.2.9章节),但是还是不甚明了。副本中确实存的是修改,不是修改后的值。在需要近似值的地方,直接取原始变量percpu_counter_read()就可以了,但是需要准确值的话,需要调用percpu_counter_sum()来获取。只是什么情况下会只需要近似值就OK了?
真觉得这些设计真的是巧妙啊~@xiaoxiao:你说的近似值是个什么概念噢
重点总结如下:
1、Per-CPU 变量和原始变量之间需要同步吗?为什么?
2、percpu_counter_sum() 能获取到准确值吗?对于 Per-CPU 变量,有近似值和准确值一说吗?
linuxer
2014-10-16 15:49SHIFT_PERCPU_PTR宏定义的含义很简单,不过具体实现还是有些模糊之处,__verify_pcpu_ptr和RELOC_HIDE有谁知道其工作原理吗?
难怪正文中没有对SHIFT_PERCPU_PTR宏进一步分析[笑哭]
Copyright © 2003-2013 www.wpsshop.cn 版权所有,并保留所有权利。