【Linux基础系列之】同步机制介绍

  当多核CPU同时执行一段代码的时候,就容易发生抢占,这段代码可以叫做临界区,其他内核控制路径能够进入临界区前,进入临界区前的内核控制路径必须全部执行完这段代码,为了避免这种共享数据发生竞争,就需要采用同步技术,本文就简单介绍linux内核当中的一些同步原语;



(一) per-cpu变量

  最简单的同步技术就是把内核变量申明为per-cpu变量,这个变量只会在本地CPU操作时调用,就不用考虑其他CPU抢占的情况;

  将一个共享memory变成Per-CPU memory本质上是一个耗费更多memory来解决performance的方法。当一个在多个CPU之间共享的变量变成每个CPU都有属于自己的一个私有的变量的时候,我们就不必考虑来自多个CPU上的并发,仅仅考虑本CPU上的并发就可以了;

注意:

  1. per-cpu变量为来自不同的CPU的并发访问提供保护,但对来自异步函数(中断函数和可延迟函数)的访问不提供保护;

  2. 内核抢占可能使CPU变量产生竞争条件,内核控制路径应该在禁用抢占的情况下访问per-cpu变量;

per-cpu API:

声明和定义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,不需要对齐到cachine 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变量不能象普通变量那样进行访问,需要使用特定的接口函数,具体如下:

233 #define this_cpu_ptr(ptr)                       \
234 ({                                  \
235     __verify_pcpu_ptr(ptr);                     \
236     SHIFT_PERCPU_PTR(ptr, my_cpu_offset);               \                                                                 

//SHIFT_PERCPU_PTR: 原始的per cpu变量的地址,经过shift转成实际的percpu 副本的地址;  


237 })

260 /*              
261  * Must be an lvalue. Since @var must be a simple identifier,
262  * we force a syntax error here if it isn't.
263  */             
264 #define get_cpu_var(var)                        \
265 (*({                                    \
266     preempt_disable();                      \
267     this_cpu_ptr(&var);                     \
268 }))             
269                 
270 /*              
271  * The weird & is necessary because sparse considers (void)(var) to be
272  * a direct dereference of percpu variable (var).
273  */             
274 #define put_cpu_var(var)                        \
275 do {                                    \
276     (void)&(var);                           \
277     preempt_enable();                       \
278 } while (0)

  上面这两个接口函数已经内嵌了锁的机制(preempt disable),用户可以直接调用该接口进行本CPU上该变量副本的访问。如果用户确认当前的执行环境已经是preempt disable(或者是更厉害的锁,例如关闭了CPU中断),那么可以使用lock-free版本的Per-CPU变量的API:__get_cpu_var :

258 #define __get_cpu_var(var)  (*this_cpu_ptr(&(var)))

  只有Per-CPU变量的原始变量还是不够的,必须为每一个CPU建立一个副本,怎么建?直接静态定义一个NR_CPUS的数组?NR_CPUS定义了系统支持的最大的processor的个数,并不是实际中系统processor的数目,这样的定义非常浪费内存对于NUMA系统,每个CPU上的Per-CPU变量的副本应该位于它访问最快的那段memory上,也就是说Per-CPU变量的各个CPU副本可能是散布在整个内存地址空间的,而这些空间之间是有空洞的。

(二)原子操作

  那些有多个内核控制路径进行read-modify-write的变量,内核提供了一个特殊的类型atomic_t来避免竟态,申明的变量就叫原子变量,这样的行为我们可以叫做原子操作;

定义:atomic_t val_name = ATOMIC_INIT(val);

typedef struct {
    int counter;
} atomic_t; 

原子操作API

接口函数 描述
static inline void atomic_add(int i, atomic_t *v) 给一个原子变量v增加i
static inline int atomic_add_return(int i, atomic_t *v) 同上,只不过将变量v的最新值返回
static inline void atomic_sub(int i, atomic_t *v) 给一个原子变量v减去i
static inline int atomic_cmpxchg(atomic_t *ptr, int old, int new) 比较old和原子变量ptr中的值,如果相等,那么就把new值赋给原子变量。返回旧的原子变量ptr中的值
atomic_read 获取原子变量的值
atomic_set 设定原子变量的值
atomic_inc(v) 原子变量的值加1
atomic_dec(v) 原子变量的值减去1
atomic_sub_and_test(i, v) 给一个原子变量v减去i,并判断变量v的最新值是否等于0
atomic_add_negative(i,v) 给一个原子变量v增加i,并判断变量v的最新值是否是负数
static inline int atomic_add_unless(atomic_t *v, int a, int u) 只要原子变量v不等于u,那么就执行原子变量v加a的操作,如果v不等于u,返回非0值,否则返回0值

代码分析:

TODO



(三) memory barrier


  编译器可以在将c翻译成汇编的时候进行优化(例如内存访问指令的重新排序),让产出的汇编指令在CPU上运行的时候更快;然而,这种优化产出的结果未必符合程序员原始的逻辑;

  程序员需通过内嵌在c代码中的memory barrier来指导编译器的优化行为(这种memory barrier又叫做优化屏障,Optimization barrier),让编译器产出即高效,又逻辑正确的代码;

  Memory barrier 包括两类:

1.编译器 barrier
2.CPU Memory barrier

  Memory barrier 能够让 CPU 或编译器在内存访问上有序。一个 Memory barrier 之前的内存访问操作必定先于其之后的完成。
Linux 内核提供函数 barrier() 用于让编译器保证其之前的内存访问先于其之后的完成;

memory barrier相关的API列表:

接口名称 描述
barrier() 优化屏障,阻止编译器为了进行性能优化而进行的memory access smp_wmb()reorder;
mb() 内存屏障(包括读和写),用于SMP和UP;
rmb() 读内存屏障,用于SMP和UP;
wmb()
  • 0
    点赞
  • 1
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值