原子操作与 x86 上的 lock 指令前缀
2012-04-10 14:51
253 查看
原子操作是不可分割的操作,在执行完毕时它不会被任何事件中断。在单处理器系统(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)机制确保了可以原子性的对内存进行操作。
在多处理器系统(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)机制确保了可以原子性的对内存进行操作。
相关文章推荐
- Linux的原子操作以及LOCK前缀
- java 原子操作在x86下的实现
- x86平台读取cpu支持sse2指令集的代码,以及原子操作的代码
- x86和arm架构原子操作的区别
- 深入浅出 Java Concurrency (4): 原子操作 part 3 指令重排序与happens-before法则
- 深入浅出 Java Concurrency (4): 原子操作 part 3 指令重排序与happens-before法则
- 深入浅出 Java Concurrency (4): 原子操作 part 3 指令重排序与happens-before法则
- linux原子操作实现lock锁功能
- x86平台原子操作API的实现原理
- 深入浅出 Java Concurrency (4): 原子操作 part 3 指令重排序与happens-before法则
- Java单例的由浅入深----懒汉式的升级(2 原子操作、指令重排)懒汉式的最终版本(volatile)
- x86和arm架构原子操作的区别
- 深入浅出 Java Concurrency (4): 原子操作 part 3 指令重排序与happens-before法则
- lock 原子操作
- C#原子操作 Lock用法
- mips架构中的原子操作指令(llsc)
- x86下的原子操作实现
- x86和arm在原子操作上的差别
- arm架构的独占读写指令ldrex和strex的使用详解(原子操作和自旋锁实现的基本原理)
- Linux并发与同步(一)原子操作/spinlock/mutex