原子操作
来源:互联网 发布:大数据四大特征包括 编辑:程序博客网 时间:2024/04/28 17:58
若干汇编语言指令都具有“读-修改-写”特点 —— 也就是说,它们访问存储器单元两次,第一次读原值,第二次写新值。
假定运行在两个CPU上的两个内核控制路径试图通过执行非原子操作来同时“读-修改-写”同一存储器单元,如n++。首先,两个CPU都试图读同一单元,比如n=5。但是存储器仲裁器(对访问RAM芯片的操作进行串行化的硬件电路)插手,只允许其中的一个访问而让另一个延迟。然而,当第一个读操作已经完成后得到的是n=5,延迟的CPU从那个存储器单元正好读到同一个值(n=5)。然后,两个CPU都试图向那个存储器单元写一新值。总线存储器访问再一次被存储器仲裁器串行化,第一次n++为6,第二次n++还是6(因为他的CPU中寄存器保留的n得值仍然是5),最终,两个写操作都成功。但是,全局的结果是不对的,因为两个CPU向内存写入同一值(n=6)。因此,两个交错的“读-修改-写”操作成了一个单独的操作。
避免由于“读-修改-写”指令引起的竞争条件的最容易的办法,就是确保这样的操作在芯片级是原子的。任何一个这样的操作都必须以单个指令执行,中间不能中断,且避免其他的CPU访问同一存储器单元。这些很小的原子操作(atomic operations)可以建立在其他更灵活机制的基础之上以创建临界区。
让我们来看看80x86指令的特点:
(1)进行零次或一次对齐内存访问的汇编指令是原子的。
(2)如果在读操作之后、写操作之前没有其他处理器占用内存总线,那么从内存中读取数据、更新数据并把更新后的数据写回内存中的这些“读-修改-写”汇编语言指令(例如inc或dec)是原子的(注意,这里就会有竞争条件了,考虑到如果写操作之前突然有其他处理器占用内存总线)。当然,在单处理器系统中,永远都不会发生内存总线窃用的情况。
(3)操作码前缀是lock字节(Oxf0)的“读-修改-写”汇编语言指令即使在多处理器系统中也是原子的(与第2条对比)。当控制单元检测到这个前缀时,就“锁定”内存总线,直到这条指令执行完成为止。因此,当加锁的指令执行时,其他处理器不能访问这个内存单元。
(4)操作码前缀是一个rep字节(0xf2,Oxf3)的汇编语言指令不是原子的,这条指令强行让控制单元多次重复执行相同的指令。控制单元在执行新的循环之前要检查挂起的中断。
因此,在你编写C代码程序时,并不能保证编译器会为a=a+1或甚至像a++这样的操作使用一个原子指令。因此,Linux内核提供了一个专门的atomic_t类型(一个原子访问计数器)和一些专门的函数和宏,这些函数和宏作用于atomic_t类型的变量,并当作单独的、原子的汇编语言指令来使用。在多处理器系统中,每条这样的指令都有一个lock字节的前缀(LOCK_PREFIX)。 C编译器中还有一个关键字叫做volatile,它起一个避免指令乱序的作用,下一讲内存壁垒中会提到。
typedef struct { volatile int counter; } atomic_t;
下面,我们就来专门讲解这些函数和宏,其中的v就是atomic_t类型,而i就是一个简单的int数字:
atomic_read(v):
返回 v:
#define atomic_read(v) ((v)->counter)
atomic_set(v,i):
将 v 置为 i:
#define atomic_set(v,i) (((v)->counter) = (i))
atomic_add(i,v):
给 v 增加 i:
static __inline__ void atomic_add(int i, atomic_t *v)
{
__asm__ __volatile__(
LOCK_PREFIX "addl %1,%0"
:"+m" (v->counter)
:"ir" (i));
}
atomic_sub(i,v) :
从 v 中减去 i:
static __inline__ void atomic_sub(int i, atomic_t *v)
{
__asm__ __volatile__(
LOCK_PREFIX "subl %1,%0"
:"+m" (v->counter)
:"ir" (i));
}
atomic_sub_and_test(i, v):
从 v 中减去 i,如果结果为0,则返回1,否则,返回0
static __inline__ int atomic_sub_and_test(int i, atomic_t *v)
{
unsigned char c;
__asm__ __volatile__(
LOCK_PREFIX "subl %2,%0; sete %1"
:"+m" (v->counter), "=qm" (c)
:"ir" (i) : "memory");
return c;
}
atomic_inc(v):
把1加到 v:
static __inline__ void atomic_inc(atomic_t *v)
{
__asm__ __volatile__(
LOCK_PREFIX "incl %0"
:"+m" (v->counter));
}
atomic_dec(v):
从 v 减 1:
static __inline__ void atomic_dec(atomic_t *v)
{
__asm__ __volatile__(
LOCK_PREFIX "decl %0"
:"+m" (v->counter));
}
atomic_dec_and_test(v):
从 v 减 1,如果结果为0,则返回1;否则,返回0:
static __inline__ int atomic_dec_and_test(atomic_t *v)
{
unsigned char c;
__asm__ __volatile__(
LOCK_PREFIX "decl %0; sete %1"
:"+m" (v->counter), "=qm" (c)
: : "memory");
return c != 0;
}
atomic_inc_and_test(v):
把 1 加到 v,如果结果为0,则返回1;否则,返回0:
static __inline__ int atomic_inc_and_test(atomic_t *v)
{
unsigned char c;
__asm__ __volatile__(
LOCK_PREFIX "incl %0; sete %1"
:"+m" (v->counter), "=qm" (c)
: : "memory");
return c != 0;
}
atomic_add_negative(i, v):
把 I 加到 v,如果结果为负,则返回1;否则,返回0:
static __inline__ int atomic_add_negative(int i, atomic_t *v)
{
unsigned char c;
__asm__ __volatile__(
LOCK_PREFIX "addl %2,%0; sets %1"
:"+m" (v->counter), "=qm" (c)
:"ir" (i) : "memory");
return c;
}
atomic_inc_return(v):
把1加到 v,返回 v 的新值:
#define atomic_inc_return(v) (atomic_add_return(1,v))
atomic_dec_return(v):
从 v 减1,返回 v 的新值:
#define atomic_dec_return(v) (atomic_sub_return(1,v))
atomic_add_return(i, v):
把i加到 *v,返回 *v 的新值:
static __inline__ int atomic_add_return(int i, atomic_t *v)
{
int __i;
#ifdef CONFIG_M386
unsigned long flags;
if(unlikely(boot_cpu_data.x86==3))
goto no_xadd;
#endif
/* Modern 486+ processor */
__i = i;
__asm__ __volatile__(
LOCK_PREFIX "xaddl %0, %1;"
:"=r"(i)
:"m"(v->counter), "0"(i));
return i + __i;
#ifdef CONFIG_M386
no_xadd: /* Legacy 386 processor */
local_irq_save(flags);
__i = atomic_read(v);
atomic_set(v, i + __i);
local_irq_restore(flags);
return i + __i;
#endif
}
atomic_sub_return(i, v):
从 *v 减i,返回 *v 的新值:
static __inline__ int atomic_sub_return(int i, atomic_t *v)
{
return atomic_add_return(-i,v);
}
另一类原子函数操作作用于位掩码,这些函数稍微复杂了点,我们就不详细说明了。
test_bit(nr, addr):返回addr的第nr位的值
set_bit(nr, addr):设置addr的第nr位
clear_bit(nr, addr):清addr的第nr位
change_bit(nr, addr):转换addr的第nr位
test_and_set_bit(nr, addr):设置addr的第nr位,并返回它的原值
test_and_clear_bit(nr, addr):清addr的第nr位,并返回它的原值
test_and_change_bit(nr, addr):转换addr的第nr位,并返回它的原值
atomic_clear_mask(mask, addr):清mask指定的addr的所有位
atomic_set_mask(mask, addr):设置mask指定的addr的所有位
假定运行在两个CPU上的两个内核控制路径试图通过执行非原子操作来同时“读-修改-写”同一存储器单元,如n++。首先,两个CPU都试图读同一单元,比如n=5。但是存储器仲裁器(对访问RAM芯片的操作进行串行化的硬件电路)插手,只允许其中的一个访问而让另一个延迟。然而,当第一个读操作已经完成后得到的是n=5,延迟的CPU从那个存储器单元正好读到同一个值(n=5)。然后,两个CPU都试图向那个存储器单元写一新值。总线存储器访问再一次被存储器仲裁器串行化,第一次n++为6,第二次n++还是6(因为他的CPU中寄存器保留的n得值仍然是5),最终,两个写操作都成功。但是,全局的结果是不对的,因为两个CPU向内存写入同一值(n=6)。因此,两个交错的“读-修改-写”操作成了一个单独的操作。
避免由于“读-修改-写”指令引起的竞争条件的最容易的办法,就是确保这样的操作在芯片级是原子的。任何一个这样的操作都必须以单个指令执行,中间不能中断,且避免其他的CPU访问同一存储器单元。这些很小的原子操作(atomic operations)可以建立在其他更灵活机制的基础之上以创建临界区。
让我们来看看80x86指令的特点:
(1)进行零次或一次对齐内存访问的汇编指令是原子的。
(2)如果在读操作之后、写操作之前没有其他处理器占用内存总线,那么从内存中读取数据、更新数据并把更新后的数据写回内存中的这些“读-修改-写”汇编语言指令(例如inc或dec)是原子的(注意,这里就会有竞争条件了,考虑到如果写操作之前突然有其他处理器占用内存总线)。当然,在单处理器系统中,永远都不会发生内存总线窃用的情况。
(3)操作码前缀是lock字节(Oxf0)的“读-修改-写”汇编语言指令即使在多处理器系统中也是原子的(与第2条对比)。当控制单元检测到这个前缀时,就“锁定”内存总线,直到这条指令执行完成为止。因此,当加锁的指令执行时,其他处理器不能访问这个内存单元。
(4)操作码前缀是一个rep字节(0xf2,Oxf3)的汇编语言指令不是原子的,这条指令强行让控制单元多次重复执行相同的指令。控制单元在执行新的循环之前要检查挂起的中断。
因此,在你编写C代码程序时,并不能保证编译器会为a=a+1或甚至像a++这样的操作使用一个原子指令。因此,Linux内核提供了一个专门的atomic_t类型(一个原子访问计数器)和一些专门的函数和宏,这些函数和宏作用于atomic_t类型的变量,并当作单独的、原子的汇编语言指令来使用。在多处理器系统中,每条这样的指令都有一个lock字节的前缀(LOCK_PREFIX)。 C编译器中还有一个关键字叫做volatile,它起一个避免指令乱序的作用,下一讲内存壁垒中会提到。
typedef struct { volatile int counter; } atomic_t;
下面,我们就来专门讲解这些函数和宏,其中的v就是atomic_t类型,而i就是一个简单的int数字:
atomic_read(v):
返回 v:
#define atomic_read(v) ((v)->counter)
atomic_set(v,i):
将 v 置为 i:
#define atomic_set(v,i) (((v)->counter) = (i))
atomic_add(i,v):
给 v 增加 i:
static __inline__ void atomic_add(int i, atomic_t *v)
{
__asm__ __volatile__(
LOCK_PREFIX "addl %1,%0"
:"+m" (v->counter)
:"ir" (i));
}
atomic_sub(i,v) :
从 v 中减去 i:
static __inline__ void atomic_sub(int i, atomic_t *v)
{
__asm__ __volatile__(
LOCK_PREFIX "subl %1,%0"
:"+m" (v->counter)
:"ir" (i));
}
atomic_sub_and_test(i, v):
从 v 中减去 i,如果结果为0,则返回1,否则,返回0
static __inline__ int atomic_sub_and_test(int i, atomic_t *v)
{
unsigned char c;
__asm__ __volatile__(
LOCK_PREFIX "subl %2,%0; sete %1"
:"+m" (v->counter), "=qm" (c)
:"ir" (i) : "memory");
return c;
}
atomic_inc(v):
把1加到 v:
static __inline__ void atomic_inc(atomic_t *v)
{
__asm__ __volatile__(
LOCK_PREFIX "incl %0"
:"+m" (v->counter));
}
atomic_dec(v):
从 v 减 1:
static __inline__ void atomic_dec(atomic_t *v)
{
__asm__ __volatile__(
LOCK_PREFIX "decl %0"
:"+m" (v->counter));
}
atomic_dec_and_test(v):
从 v 减 1,如果结果为0,则返回1;否则,返回0:
static __inline__ int atomic_dec_and_test(atomic_t *v)
{
unsigned char c;
__asm__ __volatile__(
LOCK_PREFIX "decl %0; sete %1"
:"+m" (v->counter), "=qm" (c)
: : "memory");
return c != 0;
}
atomic_inc_and_test(v):
把 1 加到 v,如果结果为0,则返回1;否则,返回0:
static __inline__ int atomic_inc_and_test(atomic_t *v)
{
unsigned char c;
__asm__ __volatile__(
LOCK_PREFIX "incl %0; sete %1"
:"+m" (v->counter), "=qm" (c)
: : "memory");
return c != 0;
}
atomic_add_negative(i, v):
把 I 加到 v,如果结果为负,则返回1;否则,返回0:
static __inline__ int atomic_add_negative(int i, atomic_t *v)
{
unsigned char c;
__asm__ __volatile__(
LOCK_PREFIX "addl %2,%0; sets %1"
:"+m" (v->counter), "=qm" (c)
:"ir" (i) : "memory");
return c;
}
atomic_inc_return(v):
把1加到 v,返回 v 的新值:
#define atomic_inc_return(v) (atomic_add_return(1,v))
atomic_dec_return(v):
从 v 减1,返回 v 的新值:
#define atomic_dec_return(v) (atomic_sub_return(1,v))
atomic_add_return(i, v):
把i加到 *v,返回 *v 的新值:
static __inline__ int atomic_add_return(int i, atomic_t *v)
{
int __i;
#ifdef CONFIG_M386
unsigned long flags;
if(unlikely(boot_cpu_data.x86==3))
goto no_xadd;
#endif
/* Modern 486+ processor */
__i = i;
__asm__ __volatile__(
LOCK_PREFIX "xaddl %0, %1;"
:"=r"(i)
:"m"(v->counter), "0"(i));
return i + __i;
#ifdef CONFIG_M386
no_xadd: /* Legacy 386 processor */
local_irq_save(flags);
__i = atomic_read(v);
atomic_set(v, i + __i);
local_irq_restore(flags);
return i + __i;
#endif
}
atomic_sub_return(i, v):
从 *v 减i,返回 *v 的新值:
static __inline__ int atomic_sub_return(int i, atomic_t *v)
{
return atomic_add_return(-i,v);
}
另一类原子函数操作作用于位掩码,这些函数稍微复杂了点,我们就不详细说明了。
test_bit(nr, addr):返回addr的第nr位的值
set_bit(nr, addr):设置addr的第nr位
clear_bit(nr, addr):清addr的第nr位
change_bit(nr, addr):转换addr的第nr位
test_and_set_bit(nr, addr):设置addr的第nr位,并返回它的原值
test_and_clear_bit(nr, addr):清addr的第nr位,并返回它的原值
test_and_change_bit(nr, addr):转换addr的第nr位,并返回它的原值
atomic_clear_mask(mask, addr):清mask指定的addr的所有位
atomic_set_mask(mask, addr):设置mask指定的addr的所有位
- 原子操作
- 原子操作
- 原子操作
- 原子操作
- 原子操作
- 原子操作
- 原子操作
- 原子操作
- 原子操作
- 原子操作
- 原子操作
- 原子操作
- 原子操作
- 原子操作
- 原子操作
- 原子操作
- 原子操作
- 原子操作
- How do I install a file in my local repository along with a generic POM?
- 漾七夕
- 通过Socket来模拟Http交互过程
- 人件--读书笔记8
- java连接mysql中文乱码解决之道
- 原子操作
- 百度地图中绘制自定义的用户位置,以及范围圈
- 七夕,染红了我的相思
- 利用HTML显示QQ的几个界面
- 解决ftp_put()函数0字节问题
- QT4最佳学习指南
- OSG结点的父子索引变化
- 抽象工厂模式
- 读侯俊杰的《深入浅出MFC》小记