Linux内核中的内存屏障

编译器有时会对代码做一些优化,例如尝试在保证程序执行正确的前提下修改指令顺序或优化ldr/str指令,让程序执行地更快。但是编译器毕竟不能完全猜透人的心思,有时候它做的优化会导致程序运行不符我们的预期。

因此,内核中提供了一些额外的函数,可以插在某段代码里,告诉编译器不要在这里做指令优化。这些函数分为两种:

内存屏障:rmb(), wmb(), mb(),可以防止硬件上的指令重排。除了编译器,有的CPU也支持对指令进行重排来优化程序执行效率,这几个函数就是去防止CPU去做这些事情。rmb()是读访问内存屏障,它保证在屏障(调用rmb()的位置处)之后的任何读操作在执行之前,屏障之前的所有读操作都已经完成。wmb()对应写操作,意思同上。mb()就同时包含读和写操作,意思同上。
优化屏障:barrier(),防止编译器对内存访问的优化,类似volatile关键字对于访问变量的作用。它告诉编译器,在插入barrier()的位置处,内存中的内容都被更新了,你想读变量、映射到内存的寄存器等内容都需要真正到内存里去读,这样就能保证barrier之后的读指令不会被优化掉。

上面第1种屏障是和硬件即CPU特性相关的,那么,如果你的CPU没有指令重排的能力,也就没有必要防止指令重排了。例如,一款CPU不支持写指令重排,那么系统中的wmb()就直接被定义成了barrier()。

还有SMP系统中使用的smp_rmb(), smp_wmb(), smp_mb(),它们只用于SMP系统。在单处理器上它们被定义成barrier()。

当然,防止优化后,受影响的代码执行效率会降低,但为了保证正确性,牺牲一点性能是值得的。

优化屏障的一个特定应用是内核的抢占机制。我们看到preempt_disable()/preempt_enable()并不是简单的修改抢占计数:

#define preempt_disable() \
do { \
    inc_preempt_count(); \
    barrier(); \
} while (0)

#define preempt_enable_no_resched() \
do { \
    barrier(); \
    dec_preempt_count(); \
} while (0)

#define preempt_enable() \
do { \
    preempt_enable_no_resched(); \
    barrier(); \
    preempt_check_resched(); \
} while (0)

正常的用法我们都很熟悉:

preempt_disable()
//临界区,不能被抢占。
preempt_enable()

但如果不加屏障,谁也不知道编译器是否会优化成这样:

//临界区,不能被抢占。
preempt_disable()
preempt_enable()

preempt_disable()
preempt_enable()
//临界区,不能被抢占。

这样临界区代码就没有受到保护。因此,需要在关抢占时增加preempt_count之后增加一个屏障,告诉编译器在这之前要完成写请求,接着再执行临界区代码,在开抢占时递减preempt_count之前增加一个屏障,告诉编译器要真正去内存里获取这个值,不能偷懒。

volatile:
上面提到了volatile,我也简单说一下,volatile的作用是防止编译器对访存指令做优化,例如,在一个线程的一段代码里要定期读一个变量a,根据读到的不同值做不同事情,但这个a的修改是在另一个线程里做的,那编译器可能就认为a没有被改过从而不是去每次从内存里去读新的a(把a放在一个临时寄存器里,每次读寄存器)。

用volatile关键字修饰a的作用就是让使用a的代码每次都真正从内存里去读。volatile的作用仅此而已(没有保证原子性之类的作用,保证原子性该加锁还是要加锁)。

  • 0
    点赞
  • 2
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值