原子操作与 x86 上的 lock 指令前缀

原子操作是不可分割的操作,在执行完毕时它不会被任何事件中断。在单处理器系统(UniProcessor,简称 UP)中,能够在单条指令中完成的操作都可以认为是原子操作,因为中断只能发生在指令与指令之间。


在多处理器系统(Symmetric Multi-Processor,简称 SMP)中情况有所不同,由于系统中有多个处理器在独立的运行,即使在能单条指令中完成的操作也可能受到干扰。


在所有的 X86 CPU 上都具有锁定一个特定内存地址的能力,当这个特定内存地址被锁定后,它就可以阻止其他的系统总线读取或修改这个内存地址。这种能力是通过 LOCK 指令前缀再加上下面的汇编指令来实现的。当使用 LOCK 指令前缀时,它会使 CPU 宣告一个 LOCK# 信号,这样就能确保在多处理器系统或多线程竞争的环境下互斥地使用这个内存地址。当指令执行完毕,这个锁定动作也就会消失。

能够和 LOCK 指令前缀一起使用的指令如下所示:

BT, BTS, BTR, BTC   (mem, reg/imm)
XCHG, XADD  (reg, mem / mem, reg)
ADD, OR, ADC, SBB   (mem, reg/imm)
AND, SUB, XOR   (mem, reg/imm)
NOT, NEG, INC, DEC  (mem)


注意:XCHG 和 XADD (以及所有以 'X' 开头的指令)都能够保证在多处理器系统下的原子操作,它们总会宣告一个 "LOCK#" 信号,而不管有没有 LOCK 前缀。


使用原子操作的一个简单示例如:
void __fastcall atomic_inc (volatile int* pNum)
{
    __asm
    {
        lock inc dword ptr [ECX]
        ret
    }
}

上面, __fastcall 关键字确保参数是通过寄存器来传递的,这样就能够提升原子指令的性能。


另外,在 linux 内核中有一套原子操作函数,比如其中之一:
static __inline__ void atomic_add(int i, atomic_t *v)
{
    __asm__ __volatile__(
        LOCK_PREFIX "addl %1,%0"
        :"+m" (v->counter)
        :"ir" (i));
}

上面,LOCK_PREFIX 宏定义了 LOCK 指令前缀,如:
#ifdef CONFIG_SMP
#define LOCK_PREFIX \
        ".section .smp_locks,\"a\"\n"    \
        "  .align 4\n"            \
"  .long 661f\n" /* address */    \
".previous\n"            \
"661:\n\tlock; "

#else /* ! CONFIG_SMP */
#define LOCK_PREFIX ""
#endif

由上可见,在单处理器系统下,LOCK_PREFIX 宏为空,因为此时并不需要 LOCK 指令前缀,处理器只要有可能,原子操作就会被编译成单个机器指令。


在一些处理器,包括 P6 家族,奔腾4(Pentium4)系列,至强(Xeon)处理器,lock 操作可能不会宣告一个 LOCK# 信号。从 P6 家族处理器开始,当使用 LOCK 指令访问的内存已经被处理器加载到缓存中时,LOCK# 信号通常不会被宣告。取而代之的是,仅是锁定了处理器的缓存。这里,处理器缓存的相干性(coherency)机制确保了可以原子性的对内存进行操作。

没有更多推荐了,返回首页

私密
私密原因:
请选择设置私密原因
  • 广告
  • 抄袭
  • 版权
  • 政治
  • 色情
  • 无意义
  • 其他
其他原因:
120
出错啦
系统繁忙,请稍后再试