温暖的电波  

说明:内核版本号为3.10.101

一、ARM架构中的原子操作实现

  在原子操作(一)中我们已经提到,各个架构组织为“复仇者”联盟,统一了基本的原子变量操作,这里我们就拿atomic_dec(v)来看看通天ARM的实现。

首先是atomic_dec(v)原子减一操作的宏定义。这个宏的定义在文件arch/arm/include/asm/atomic.h中:

#define atomic_dec(v)        atomic_sub(1, v)

  对于ARM架构不同的版本,stomic_sub(i,v)的实现是不一样的。具体而言,在ARMv6之前的版本定义如下:

#define atomic_sub(i, v)    (void) atomic_sub_return(i, v)

  而ARMv6以后的版本则将atomic_sub()实现为static 内联函数,具体见第二节。

二、ARMv6以前的版本的实现

  ARMv6之前的版本将atomic_sub()宏定义为atomic_sub_return()函数,其实现也在文件arch/arm/include/asm/atomic.h中。而这个atomic_sub_return()函数根据版本的不同也有两个不同的实现。我们这里只关注ARMv6之前版本的实现。

/* ARMv6 以前的版本:不支持SMP */
static inline int atomic_sub_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;
}

 

  可以看到,对v->counter的减一操作是一个临界区,指令的执行不能被打断,内存的访问也需要保持没有干扰。

ARMv6以前的版本通过关本地中断来保护这块临界区,看起来相当简单,其奥秘就在于ARMv6以前的版本不支持SMP。

在系统不支持SMP的情况下,我们关掉本地中断可以防止下面几种意外:

  1) 关掉本地中断后,在对v->counter实施"减一"的过程中不会被外部中断打断;

  2) 系统不支持SMP,可以保证在本地cpu访问v->counter和val变量内存的过程中不会有其他cpu访问这些内存。

问题:

  1. 虽然在临界区内不会有其他cpu访问 v->counter和val,但是能够保证不会有DMA操作这些内存么?

  2. 虽然禁止了中断,但是可以保证期间此cpu不会被抢占或者因为其他原因放弃调度么?

答:

  1. DMA在操作内存前会通过DMA中断、总线仲裁来与cpu的内存访问进行协调。这里已经关掉本地中断,且是UP系统,所以不会干扰。

  2. 在UP系统中没有内核抢占;从代码上来看,临界区这一段没有主动放弃cpu;另外,我们禁止了本地中断,也就是禁止了时钟中断,这样在开中断前就不会有机会进行调度检查,保证临界区在开中断前一直运行。

三. ARMv7以后的架构

  从ARMv6T2以后的版本中,ARM和Thumb指令集开始采用了新一代"独占访问"指令"Load-Exclusive and Store-Exclusive "来实现原子操作。独占访问的秘诀就在于系统中通过exclusive monitor来实现独占访问的监控。内核中atomic_sub()的具体实现如下所示:

/*
 * ARMv6 UP and SMP safe atomic ops.  We use load exclusive and
 * store exclusive to ensure that these are atomic.  We may loop
 * to ensure that the update happens.
 */
static inline void atomic_sub(int i, atomic_t *v)
{
       unsigned long tmp;
       int result;
       __asm__ __volatile__("@ atomic_sub\n"              /* 优化屏障,防止编译器优化 */
"1:   ldrex       %0, [%3]\n"                              /*【1】独占方式加载v->counter到result*/
"      sub  %0, %0, %4\n"                                  /*【2】result减一*/
"      strex       %1, %0, [%3]\n"                            /*【3】独占方式将result值写回v->counter*/
"      teq   %1, #0\n"                                       /*【4】判断strex更新内存是否成*/
"      bne  1b"                                              /*【5】不成功跳转到1:*/
       : "=&r" (result), "=&r" (tmp), "+Qo" (v->counter)       /*输出部*/
       : "r" (&v->counter), "Ir" (i)                           /*输入部*/
       : "cc");                                                /*损坏部*/
}

 

  访存指令LDREX/STREX和普通的LDR/STR访存指令不一样,它是“独占”访存指令。这对指令访存过程由一个称作“exclusive monitor”的部件来监视是否可以进行独占访问。

  先看看这对独占访存指令:

  (1)LDREX R1 ,[R0] 指令是以独占的方式从R0所指的地址中取一个字存放到R0中;

  (2)STREX R2,R1,[R0] 指令是以独占的方式用R1来更新内存,如果独占访问条件允许,则更新成功并返回0到R2,否则失败返回1到R2。

  了解LDREX和STREX的基本原理后,理一理上面atomic_sub()原子更新(减一)atomic_t * v的实现流程:

  (1) 从内存中读取v->counter值到一个寄存器中(result),并更新exclusive monitor状态为独占访问;

  (2)result减一操作;

  (3)尝试将result的值写入v->counter地址,如果exclusive monitor允许独占写存,则修改内存成功并将tmp设置为0 ,否则tmp设置1;

  (4)查看tmp的值是否为0 ,如果不为0表示上面的更新v->counter失败,再次跳转会(1)执行。

  一旦STREX指令执行成功,就表示这次内存访问没有受到其他干扰,保证了内存更新操作的原子性。

posted on 2016-08-07 08:36  温暖的电波  阅读(2619)  评论(0编辑  收藏  举报