原子操作--ARM架構


說明:內核版本號為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指令執行成功,就表示這次內存訪問沒有受到其他干擾,保證了內存更新操作的原子性。


免責聲明!

本站轉載的文章為個人學習借鑒使用,本站對版權不負任何法律責任。如果侵犯了您的隱私權益,請聯系本站郵箱yoyou2525@163.com刪除。



 
粵ICP備18138465號   © 2018-2025 CODEPRJ.COM