CPU指令集分为两种:
CISC:复杂指令集,指令多,功能多,一条指令可以完成很复杂的逻辑或者算数运算。
RISC:简单指令集,指令少,功能也少,但是基本上都是常用的指令,对于复杂的问题,需要通过N多个条指令才能完成,执行效率没有CISC高。
x86架构基于CISC,arm架构基于RISC。
1.X86架构
1.1 单处理器
在单处理器系统中,中断只会发生在指令与指令之间,能够在单条指令中完成的操作都可以认为是原子操作。X86使用CISC指令集,允许在一条指令里进行两次内存操作,因此,对于i++,i–这类操作在单处理器中可视为原子操作(必须显示使用addl r,%1命令)。
1.2 多处理器
在多处理器系统中,多个处理器独立运行,在单条指令中能完成的操作也可能受到干扰,这种情况下,CPU提供了在指令执行期间对总线加锁的手段,使得同一总线上别的CPU就暂时不能通过总线访问内存了,保证了这条指令在多处理器环境中的原子性。
实现时使用了C内嵌汇编语言。
内嵌汇编语法:
__asm__:内嵌汇编关键字,告知编译器下述语句为汇编代码
__volatile__:告知编译器不要优化(比如重组优化)下述汇编语句
变量列表中常见符号:
“+”:操作数可读可写
“=”: 操作数只写
“&”:常用于输出操作,表示输出操作不能使用输入操作使用过的寄存器,只能+&或=&方式使用
“r”: 操作数是任何可用的通用寄存器
“m”: 操作数是内存变量
“p”: 操作数是一个合法的内存地址
“l”: 操作数是立即数
“Q”: A memory address which uses a single base register with no offset
“V”: 操作数是内存变量,但其寻址方式非偏移量类型
static __inline__ void atomic_ad(int i, atomic_t *v)
{
__asm__ __volatile__(
LOCK “addl %1,%0”
:"+m" (v->counter)
:"ir" (i),"m"(v->counter));
}
0% —–i
1% —–v->counter
2.ARM架构
1.1 单处理器
ARM使用RISC指令集,在一次指令执行期间只能有一次内存操作,因此在单处理器系统中,i++, i–等也不能视为原子操作,ARM使用关中断来实现原子操作。
1.2 多处理器
1.2.1 实现
在ARM多核处理器中,使用专门的指令ldrex和strex实现原子操作。
static inline int atomic_add_return(int i, atomic_t *v)
{
unsigned long tmp;
int result;
__asm__ __volatile__("@ atomic_add_return\n"
"1: ldrex %0, [%3]\n"
" add %0,%0,%4\n"
" strex %1,%0,[%3]\n"
" teq %1, #0\n"
" bne 1b"
: "=&r" (result), "=&r" (tmp), "+Qo" (v->counter)
: "r" (&v->counter), "Ir"(i)
: "cc");
return result;
}
%0 —— result
%1 —— tmp
%3 —— v->counter的地址
%4 —– i
(1)ldrex %0,[%3]
独占式地加载(Load-Exclusive)v->counter的地址,把它的值放到result中,并更新exclusive monitor(s)
用C描述就是:
result = v->counter
(2)add %0,%0,%4
result = result + i
(3) strex %1, %0, [%3]
独占式地保存(Store-Exclusive)数据至v->counter的地址,数据来自result, 操作结果(成功/失败)保存在tmp中
“cc”是一个特殊的参数,用来标明汇报代码会修改标志寄存器(flags register),在某些机器平台上,GCC通过一个特殊的硬件寄存器表征条件类型的代码,“cc”就是这个特殊寄存器的名字,某些机器平台没有上述功能,“cc”会被忽略,不起作用。