Linux内核原子(atomic)操作源码分析(基于ARM)

一. 原子数据类型:

typedef struct {
    int counter;
}atomic_t;

二. 基本的原子操作函数接口:

接口函数描述
static inline void atomic_add(int i, atomic_t *v)给一个原子变量v增加i
static inline int atomic_add_return(int i, atomic_t *v)同上,只不过将变量v的最新值返回
static inline void atomic_sub(int i, atomic_t *v)给一个原子变量v减去i
static inline int atomic_sub_return(int i, atomic_t *v)同上,只不过将变量v的最新值返回
static inline int atomic_cmpxchg(atomic_t *ptr, int old, int new)比较old和原子变量ptr中的值,如果相等,那么就把new值赋给原子变量。
返回旧的原子变量ptr中的值
atomic_read获取原子变量的值
atomic_set设定原子变量的值
atomic_inc(v)原子变量的值加一
atomic_inc_return(v)同上,只不过将变量v的最新值返回
atomic_dec(v)原子变量的值减去一
atomic_dec_return(v)同上,只不过将变量v的最新值返回
atomic_sub_and_test(i, v)给一个原子变量v减去i,并判断变量v的最新值是否等于0
atomic_add_negative(i,v)给一个原子变量v增加i,并判断变量v的最新值是否是负数
static inline int atomic_add_unless(atomic_t *v, int a, int u)只要原子变量v不等于u,那么就执行原子变量v加a的操作。
如果v不等于u,返回非0值,否则返回0值

三. 以atomic_add_return函数实现为例:

代码在Linux内核中的路径: /arch/arm/include/asm/atomic.h

#if __LINUX_ARM_ARCH__ >= 6

.....

static inline int atomic_add_return(int i, atomic_t *v)
{
    unsigned long tmp;
    int result;

    smp_mb();

    __asm__ __volatile__("@ atomic_add_return\n"
"1:    ldrex    %0, [%3]\n"
"    add    %0, %0, %4\n"
"    strex    %1, %0, [%3]\n"
"    teq    %1, #0\n"
"    bne    1b"
    : "=&r" (result), "=&r" (tmp), "+Qo" (v->counter)
    : "r" (&v->counter), "Ir" (i)
    : "cc");

    smp_mb();

    return result;
}

#else /* ARM_ARCH_6 */

.....

#ifdef CONFIG_SMP
#error SMP not supported on pre-ARMv6 CPUs
#endif

static inline int atomic_add_return(int i, atomic_t *v)
{
    unsigned long flags;
    int val;

    raw_local_irq_save(flags);
    val = v->counter;
    v->counter = val += i;
    raw_local_irq_restore(flags);

    return val;
}
#define atomic_add(i, v)    (void) atomic_add_return(i, v)

.....

#endif

[0]. 在ARMv6(含v6)架构有了多核的CPU, 为了在多核之间同步数据和控制并发, ARM在内存访问上
增加了独占监测(Exclusive Monitiors)机制, 并增加了相关的ldrex/strex指令, (local monitor & global monitor);

[1]. 对于ARMv6以前的架构是单核的CPU, 对于并发和同步, 对变量的原子访问只需要关闭CPU中断就可以保证原子性;eg.

static inline int atomic_add_return(int i, atomic_t *v) {
    unsigned long tmp;
    int result;

    smp_mb();                       // 内存屏障操作 此处暂时不细究

    __asm__ __volatile__(           // __asm__->嵌套汇编代码, __volatile__->告知编译器不必优化处理
    "@ atomic_add_return\n"         // @ 在ARM汇编语言中是注释的意思
    "1: ldrex %0, [%3]\n"           // 1: -> 是一个symbol, 用于下面的"bne 1b"回跳至该处执行,
    "   add %0, %0, %4\n"
    "   strex %1, %0, [%3]\n"
    "   teq %1, #0\n"
    "   bne 1b"
    : "=&r" (result), "=&r" (tmp), "+Qo" (v->counter))
    : "r" (&r->counter), "Ir" (i)
    : "cc");                        // "cc"是一个特殊的参数,用来标明汇编代码会修改标志寄存器(flags register)

    smp_mb();                       // 内存屏障操作 此处不必细究
    return result;
}

针对下面的代码进行分析
-------------------------------------------------------------
[line0] __asm__ __volatile__(  
[line1]    "@ atomic_add_return\n" 
[line2]    "1: ldrex %0, [%3]\n" 
[line3]    "   add %0, %0, %4\n"
[line4]    "   strex %1, %0, [%3]\n"
[line5]    "   teq %1, #0\n"
[line6]    "   bne 1b"
[line7]    : "=&r" (result), "=&r" (tmp), "+Qo" (v->counter))
[line8]    : "r" (&r->counter), "Ir" (i)
[line9]    : "cc");
-------------------------------------------------------------

[0]: line7 和 line8 可以得到用到的变量
%0 -> result
%1 -> tmp
%2 -> v->counter
%3 -> &(v->counter)
%4 -> i
[1]. line2 独占式加载v->counter数值, 并标记对该段内存的独占访问;
[2]. line3 ==> result = result + i;
[3]. line4 独占式保存v->counter数值, 操作结果(成功/失败)保存在tmp中;
[4]. line5 检测strex的操作是否成功
[5]. line6 strex的操作失败的话,向后跳转到指定标号(jump to 1 label backward)处重新执行
 

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值