您的位置:首页 > 运维架构 > Linux

linux下的线程锁----原子锁

2015-11-30 11:29 417 查看
交换指令XCHG是两个寄存器,寄存器和内存变量之间内容的交换指令,两个操作数的数据类型要相同,可以是一个字节,也可以是一个字,也可以是双字[1] 。其指令格式如下:
XCHG Reg/Mem, Mem/Reg,Reg/Reg
该指令的功能和MOV指令不同,后者是一个操作数的内容被修改,而前者是两个操作数都会发生改变。寄存器不能是段寄存器,两个操作数也不能同时为内存变量。 XCHG指令不影响标志位。
【功能】: 交换两个操作数的数据,(dst)↔(src)
【语法】: XCHG
【格式】: XCHG r1,r2 XCHG m,r XCHG r,m
XCHG指令不允许的情况有以下四种:
1. 不能同时都为内存操作数
2. 任何一个操作数都不能为段寄存器
3. 任何一个操作数不能为立即数
4. 两个操作数的长度不能不相等

----------------------------------------------我是分割线------------------------------------------------


原子操作

  所谓原子操作,就是该操作绝不会在执行完毕前被任何其他任务或事件打断,也就说,它的最小的执行单位,不可能有比它更小的执行单位,因此这里的原子实际是使用了物理学里的物质微粒的概念。

  原子操作需要硬件的支持,因此是架构相关的,其API和原子类型的定义都定义在内核源码树的include/asm/atomic.h文件中,它们都使用汇编语言实现,因为C语言并不能实现这样的操作。

  原子操作主要用于实现资源计数,很多引用计数(refcnt)就是通过原子操作实现的。原子类型定义如下:

typedef struct { volatile int counter; } atomic_t;
  volatile修饰字段告诉gcc不要对该类型的数据做优化处理,对它的访问都是对内存的访问,而不是对寄存器的访问。

  原子操作API包括: 
atomic_read(atomic_t * v);
  该函数对原子类型的变量进行原子读操作,它返回原子类型的变量v的值。
atomic_set(atomic_t * v, int i);
  该函数设置原子类型的变量v的值为i。
void atomic_add(int i, atomic_t *v);
  该函数给原子类型的变量v增加值i。
atomic_sub(int i, atomic_t *v);
  该函数从原子类型的变量v中减去i。
int atomic_sub_and_test(int i, atomic_t *v);
  该函数从原子类型的变量v中减去i,并判断结果是否为0,如果为0,返回真,否则返回假。
void atomic_inc(atomic_t *v);
  该函数对原子类型变量v原子地增加1。
void atomic_dec(atomic_t *v);
  该函数对原子类型的变量v原子地减1。
int atomic_dec_and_test(atomic_t *v);
  该函数对原子类型的变量v原子地减1,并判断结果是否为0,如果为0,返回真,否则返回假。
int atomic_inc_and_test(atomic_t *v);
  该函数对原子类型的变量v原子地增加1,并判断结果是否为0,如果为0,返回真,否则返回假。
int atomic_add_negative(int i, atomic_t *v);
  该函数对原子类型的变量v原子地增加I,并判断结果是否为负数,如果是,返回真,否则返回假。
int atomic_add_return(int i, atomic_t *v);
  该函数对原子类型的变量v原子地增加i,并且返回指向v的指针。
int atomic_sub_return(int i, atomic_t *v);
  该函数从原子类型的变量v中减去i,并且返回指向v的指针。
int atomic_inc_return(atomic_t * v);
  该函数对原子类型的变量v原子地增加1并且返回指向v的指针。 

int atomic_dec_return(atomic_t * v);
  该函数对原子类型的变量v原子地减1并且返回指向v的指针。 

  原子操作通常用于实现资源的引用计数,在TCP/IP协议栈的IP碎片处理中,就使用了引用计数,碎片队列结构struct ipq描述了一个IP碎片,字段refcnt就是引用计数器,它的类型为atomic_t,当创建IP碎片时(在函数ip_frag_create中),使用atomic_set函数把它设置为1,当引用该IP碎片时,就使用函数atomic_inc把引用计数加1。 

  当不需要引用该IP碎片时,就使用函数ipq_put来释放该IP碎片,ipq_put使用函数atomic_dec_and_test把引用计数减1并判断引用计数是否为0,如果是就释放IP碎片。函数ipq_kill把IP碎片从ipq队列中删除,并把该删除的IP碎片的引用计数减1(通过使用函数atomic_dec实现)。
--------------------------------------------------------我是分割线-----------------------------------------------------------

在任何处理器平台下,都会有一些原子性操作,供操作系统使用,我们这里只讲x86下面的。在单处理器情况下,每条指令的执行都是原子性的,但在多处理器情况下,只有那些单独的读操作或写操作才是原子性的。为了弥补这一缺点,x86提供了附加的lock前缀,使带lock前缀的读修改写指令也能原子性执行。带lock前缀的指令在操作时会锁住总线,使自身的执行即使在多处理器间也是原子性执行的。xchg指令不带lock前缀也是原子性执行,也就是说xchg执行时默认会锁内存总线。原子性操作是线程间同步的基础,linux专门定义了一种只进行原子操作的类型atomic_t,并提供相关的原子读写调用API。本节就来分析这些原子操作在x86下的实现。

typedef struct {  
    volatile int counter;  
} atomic_t;  

原子类型其实是int类型,只是禁止寄存器对其暂存。

#define ATOMIC_INIT(i)  { (i) }  

原子类型的初始化。32位x86平台下atomic API在arch/x86/include/asm/atomic_32.h中实现。

static inline int atomic_read(const atomic_t *v)  
{  
    return v->counter;  
}  
  
static inline void atomic_set(atomic_t *v, int i)  
{  
    v->counter = i;  
}  

单独的读操作或者写操作,在x86下都是原子性的。

static inline void atomic_add(int i, atomic_t *v)  
{  
    asm volatile(LOCK_PREFIX "addl %1,%0"  
             : "+m" (v->counter)  
             : "ir" (i));  
}  
  
static inline void atomic_sub(int i, atomic_t *v)  
{  
    asm volatile(LOCK_PREFIX "subl %1,%0"  
             : "+m" (v->counter)  
             : "ir" (i));  
}  

atomic_add和atomic_sub属于读修改写操作,实现时需要加lock前缀。

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_sub_and_test执行完减操作后检查结果是否为0。

static inline void atomic_inc(atomic_t *v)  
{  
    asm volatile(LOCK_PREFIX "incl %0"  
             : "+m" (v->counter));  
}  
  
static inline void atomic_dec(atomic_t *v)  
{  
    asm volatile(LOCK_PREFIX "decl %0"  
             : "+m" (v->counter));  
}  

atomic_inc和atomic_dec是递增递减操作。

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_dec_and_test在递减后检查结果是否为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_inc_and_test在递增后检查结果是否为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_add_negative在加操作后检查结果是否为负数。

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)  
             : : "memory");  
    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_add_return 不仅执行加操作,而且把相加的结果返回。它是通过xadd这一指令实现的。

static inline int atomic_sub_return(int i, atomic_t *v)  
{  
    return atomic_add_return(-i, v);  
}  

atomic_sub_return 不仅执行减操作,而且把相减的结果返回。它是通过atomic_add_return实现的。

static inline int atomic_cmpxchg(atomic_t *v, int old, int new)  
{  
    return cmpxchg(&v->counter, old, new);  
}  
  
#define cmpxchg(ptr, o, n)                      \
  
    ((__typeof__(*(ptr)))__cmpxchg((ptr), (unsigned long)(o),   \  
                       (unsigned long)(n),      \  
                       sizeof(*(ptr))))  
  
static inline unsigned long __cmpxchg(volatile void *ptr, unsigned long old,  
                      unsigned long newint size)  
{  
    unsigned long prev;  
    switch (size) {  
    case 1:  
        asm volatile(LOCK_PREFIX "cmpxchgb %b1,%2"  
                 : "=a"(prev)  
                 : "q"(new), "m"(*__xg(ptr)), "0"(old)  
                 : "memory");  
        return prev;  
    case 2:  
        asm volatile(LOCK_PREFIX "cmpxchgw %w1,%2"  
                 : "=a"(prev)  
                 : "r"(new), "m"(*__xg(ptr)), "0"(old)  
                 : "memory");  
        return prev;  
    case 4:  
        asm volatile(LOCK_PREFIX "cmpxchgl %k1,%2"  
                 : "=a"(prev)  
                 : "r"(new), "m"(*__xg(ptr)), "0"(old)  
                 : "memory");  
        return prev;  
    case 8:  
        asm volatile(LOCK_PREFIX "cmpxchgq %1,%2"  
                 : "=a"(prev)  
                 : "r"(new), "m"(*__xg(ptr)), "0"(old)  
                 : "memory");  
        return prev;  
    }  
    return old;  
}  

atomic_cmpxchg是由cmpxchg指令完成的。它把旧值同atomic_t类型的值相比较,如果相同,就把新值存入atomic_t类型的值中,返回atomic_t类型变量中原有的值。

static inline int atomic_xchg(atomic_t *v, int new)  
{  
    return xchg(&v->counter, new);  
}  
  
#define xchg(ptr, v)                            \
  
    ((__typeof__(*(ptr)))__xchg((unsigned long)(v), (ptr), sizeof(*(ptr))))  
  
static inline unsigned long __xchg(unsigned long x, volatile void *ptr,  
                   int size)  
{  
    switch (size) {  
    case 1:  
        asm volatile("xchgb %b0,%1"  
                 : "=q" (x)  
                 : "m" (*__xg(ptr)), "0" (x)  
                 : "memory");  
        break;  
    case 2:  
        asm volatile("xchgw %w0,%1"  
                 : "=r" (x)  
                 : "m" (*__xg(ptr)), "0" (x)  
                 : "memory");  
        break;  
    case 4:  
        asm volatile("xchgl %k0,%1"  
                 : "=r" (x)  
                 : "m" (*__xg(ptr)), "0" (x)  
                 : "memory");  
        break;  
    case 8:  
        asm volatile("xchgq %0,%1"  
                 : "=r" (x)  
                 : "m" (*__xg(ptr)), "0" (x)  
                 : "memory");  
        break;  
    }  
    return x;  
}  

atomic_xchg则是将新值存入atomic_t类型的变量,并将变量的旧值返回。它使用xchg指令实现。

/** 
 * atomic_add_unless - add unless the number is already a given value 
 * @v: pointer of type atomic_t 
 * @a: the amount to add to v... 
 * @u: ...unless v is equal to u. 
 * 
 * Atomically adds @a to @v, so long as @v was not already @u. 
 * Returns non-zero if @v was not @u, and zero otherwise. 
 */  
static inline int atomic_add_unless(atomic_t *v, int a, int u)  
{  
    int c, old;  
    c = atomic_read(v);  
    for (;;) {  
        if (unlikely(c == (u)))  
            break;  
        old = atomic_cmpxchg((v), c, c + (a));  
        if (likely(old == c))  
            break;  
        c = old;  
    }  
    return c != (u);  
}  

atomic_add_unless的功能比较特殊。它检查v是否等于u,如果不是则把v的值加上a,返回值表示相加前v是否等于u。因为在atomic_read和atomic_cmpxchg中间可能有其它的写操作,所以要循环检查自己的值是否被写进去。

#define atomic_inc_not_zero(v) atomic_add_unless((v), 1, 0)
  
  
#define atomic_inc_return(v)  (atomic_add_return(1, v))
  
#define atomic_dec_return(v)  (atomic_sub_return(1, v))  

atomic_inc_not_zero在v值不是0时加1。

atomic_inc_return对v值加1,并返回相加结果。

atomic_dec_return对v值减1,并返回相减结果。

#define atomic_clear_mask(mask, addr)               \
  
    asm volatile(LOCK_PREFIX "andl %0,%1"           \  
             : : "r" (~(mask)), "m" (*(addr)) : "memory")  

atomic_clear_mask清除变量某些位。

#define atomic_set_mask(mask, addr)             \
  
    asm volatile(LOCK_PREFIX "orl %0,%1"                \  
             : : "r" (mask), "m" (*(addr)) : "memory")  

atomic_set_mask将变量的某些位置位。

/* Atomic operations are already serializing on x86 */  
#define smp_mb__before_atomic_dec() barrier()
  
#define smp_mb__after_atomic_dec()  barrier()
  
#define smp_mb__before_atomic_inc() barrier()
  
#define smp_mb__after_atomic_inc()  barrier()  

因为x86的atomic操作大多使用原子指令或者带lock前缀的指令。带lock前缀的指令执行前会完成之前的读写操作,对于原子操作来说不会受之前对同一位置的读写操作,所以这里只是用空操作barrier()代替。barrier()的作用相当于告诉编译器这里有一个内存屏障,放弃在寄存器中的暂存值,重新从内存中读入。
内容来自用户分享和网络整理,不保证内容的准确性,如有侵权内容,可联系管理员处理 点击这里给我发消息
标签: