介绍set_task_state(),mb()以及内存屏障。
#define __set_task_state(tsk, state_value) \
do { (tsk)->state = (state_value); } while (0)
#define set_task_state(tsk, state_value) \
set_mb((tsk)->state, (state_value))
...
#define set_mb(var, value) do { var = value; mb(); } while (0)
...
#define mb() __asm__ __volatile__ ("" ::: "memory")
语句解释:
1.set_mb(), mb(), barrier()函数追踪到底,就是__asm__ __volatile__("" ::: "memory"),而这行代码就是内存屏障。
2.__asm__:用于指示编译器在此插入汇编语句
3.volatile/__volatile__:基本可以做到两件事情:
(1).阻止编译器为了提高速度将一个变量缓存到寄存器内而不写回内存。
(2).阻止编译器调整操作volatile变量的指令顺序。
4.memory强制gcc编译器假设RAM所有内存单元均被汇编指令修改,这样cpu中的registers和cache中已缓存的内存单元中的数据将作废。cpu将不得不在需要的时候重新读取内存中的数据。这就阻止了cpu又将registers,cache中的数据用于去优化指令,而避免去访问内存。
这是一条无用的汇编指令。可以利用这条无价值的汇编指令来配合lock指令,在__asm__,__volatile__,memory的作用下,用作cpu的内存屏障。
volatile关键字来让编译器保证变量计算结果写回到内存之中,而没有用volatile关键字约束的变量的计算结果可能保存在寄存器之中,这种限制完全在我们的代码之中控制;语句中的memory强制gcc编译器假设RAM所有内存单元均被汇编指令修改,cpu将不得不在需要的时候重新读取内存中的数据。这两条关键字保证在多线程的环境下数据的一致性。
__set_task_state(tsk, state_value) 这个函数有别于 set_task_state(tsk,state_value) ,因为前者没有使用mb()这样的一个函数,而仅仅是设置了state这个变量值,对于保护内存事件发生的次序根本就没有执行。所以,后者更加具有安全性。
内存屏障主要解决的问题是编译器的优化和CPU的乱序执行。
编译器在优化的时候,生成的汇编指令可能和c语言程序的执行顺序不一样,在需要程序严格按照c语言顺序执行时,需要显式的告诉编译不需要优化,这在linux下是通过barrier()宏完成的,它依靠volidate关键字和memory关键字,前者告诉编译barrier()周围的指令不要被优化,后者作用是告诉编译器汇编代码会使内存里面的值更改,编译器应使用内存里的新值而非寄存器里保存的老值。
同样,CPU执行会通过乱序以提高性能。汇编里的指令不一定是按照我们看到的顺序执行的。linux中通过mb()系列宏来保证执行的顺序。具体做法是通过mfence/lfence指令以及x86指令中带有串行特性的指令(这样的指令很多,例如linux中实现时用到的lock指令,I/O指令,操作控制寄存器、系统寄存器、调试寄存器的指令、iret指令等等)。
简单的说,如果在程序某处插入了mb()/rmb()/wmb()宏,则宏之前的程序保证比宏之后的程序先执行,从而实现串行化。wmb的实现和barrier()类似,是因为在x86平台上,写内存的操作不会被乱序执行。
本文部分参考自网络,如有侵权请私信联系我删除。