
我们的程序逻辑经常遇到这样的操作序列:
1、读一个位于memory中的变量的值到寄存器中
2、修改该变量的值(也就是修改寄存器中的值)
3、将寄存器中的数值写回memory中的变量值
如果这个操作序列是串行化的操作(在一个thread中串行执行),那么一切OK,然而,世界总是不能如你所愿。在多CPU体系结构中,运行在两个CPU上的两个内核控制路径同时并行执行上面操作序列,有可能发生下面的场景:
多个CPUs和memory chip是通过总线互联的,在任意时刻,只能有一个总线master设备(例如CPU、DMA controller)访问该Slave设备(在这个场景中,slave设备是RAM chip)。因此,来自两个CPU上的读memory操作被串行化执行,分别获得了同样的旧值。高性能体系结构smp完成修改后,两个CPU都想进行写操作,把修改的值写回到memory。但是,硬件arbiter的限制使得CPU的写回必须是串行化的,因此CPU1首先获得了访问权,进行写回动作,随后,CPU2完成写回动作。在这种情况下,CPU1的对memory的修改被CPU2的操作覆盖了,因此执行结果是错误的。
不仅是多CPU,在单CPU上也会由于有多个内核控制路径的交错而导致上面描述的错误。一个具体的例子如下:
系统调用的控制路径上,完成读操作后,硬件触发中断,开始执行中断handler。这种场景下,中断handler控制路径的写回的操作被系统调用控制路径上的写回覆盖了,结果也是错误的。
二、对策
对于那些有多个内核控制路径进行read-modify-write的变量,内核提供了一个特殊的类型atomic_t,具体定义如下:
typedef struct {
int counter;
} atomic_t;
从上面的定义来看,atomic_t实际上就是一个int类型的counter,不过定义这样特殊的类型atomic_t是有其思考的:内核定义了若干atomic_xxx的接口API函数,这些函数只会接收atomic_t类型的参数。这样可以确保atomic_xxx的接口函数只会操作atomic_t类型的数据。同样的,如果你定义了atomic_t类型的变量(你期望用atomic_xxx的接口API函数操作它),这些变量也不会被那些普通的、非原子变量操作的API函数接受。
具体的接口API函数整理如下:
只要原子变量v不等于u,那么就执行原子变量v加a的操作。
如果v不等于u,返回非0值,否则返回0值
三、ARM中的实现
我们以atomic_add为例,描述linux kernel中原子操作的具体代码实现细节:
#if __LINUX_ARM_ARCH__ >= 6 ----------------------(1)
static inline void atomic_add(int i, atomic_t *v)
{
unsigned long tmp;

int result;
prefetchw(&v->counter); -------------------------(2)
__asm__ __volatile__("@ atomic_add\n" ------------------(3)
"1: ldrex %0, [%3]\n" --------------------------(4)
" add %0, %0, %4\n" --------------------------(5)
" strex %1, %0, [%3]\n" -------------------------(6)
本文来自电脑杂谈,转载请注明本文网址:
http://www.pc-fly.com/a/jisuanjixue/article-76780-1.html
速度高