原子操作

来源:互联网 发布:大数据四大特征包括 编辑:程序博客网 时间: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的所有位
原创粉丝点击