12.21 x86和arm在原子操作上的差別

x86和arm在原子操作上有些差別,下面一代碼的形式來說明區別:

首先比較單核:

由於x86是CISC指令集,允許在一條指令裡進行兩次內存操作,所以對i++,i__這些操作在單核條件下是原子,當然必須得是顯示使用addl r,%1這種,就可在一條指令裡完成讀,寫操作。

而arm屬於RISC指令集,在一次指令執行期間只能有一次內存操作,所以像i++,i--這些需要先讀取內存值然後賦值的操作,在arm架構下沒法一條指令完成,所以就不滿足原子操作,這時怎樣實現原子操作呢:

我們通過代碼來看;

對於atomic_add

x86的實現很簡單:

static __inline__ void atomic_add(int i, atomic_t *v)

{

__asm__ __volatile__(

LOCK "addl %1,%0"

:"=m" (v->counter)

:"ir" (i), "m" (v->counter));

}

單核情況下LOCK是空。

而對於arm這需要更多工作:

#define atomic_add(i, v) (void) atomic_add_return(i, v)

static inline int atomic_add_return(int i, atomic_t *v)

{

unsigned long flags;

int val;

local_irq_save(flags);

val = v->counter;

v->counter = val += i;

local_irq_restore(flags);

return val;

}

上面可以看出是通過關中斷來實現的,為什麼要關中斷來實現原子操作:

分析下:

arm對於i++會生成如下代碼:

1 ldr r0,=i

2 mov [r0],r1 //這個讀內存操作

3 inc r1 //如果在這個時候發生中斷,然後在中斷處理程序中也執行i++操作就不是原子操作

4 mov r1,[r0] //這個寫內存操作

假設I=0。如果進程1執行i++,執行到3時被中斷打斷,然後中斷中也執行了i++,當兩個i++執行完了,i=1,而不是我們所要的2,這就是非原子操作的結果。

怎麼解決,就是說2-4這段代碼要麼不執行,要麼執行完才能保證原子,這個在單核上通過關中斷就可以實現,這也是上面關中斷的原因。

2.多核情況;

x86架構下:

單指令也不是原子操作了,比如addl r,%1這種有兩次內存操作的也不是原子操作,有可能在執行下一次內存操作的時候,另一個核心也讀取了這個內存,也會造成兩次i++操作為1的錯誤結果。

解決方法是家LOCK標識,這個標識的作用是在一條指令執行時,鎖住總線,其他核心沒法讀取,從而得到了原子操作。

arm架構下:

arm只有v6系列後的才有多核,也才有專門的內存原子操作機制就是ldrex,strex指令。

其源碼如下:

static inline int atomic_add_return(int i, atomic_t *v)

{

unsigned long tmp;

int result;

__asm__ __volatile__("@ atomic_add_return\\n"

"1: ldrex %0, [%2]\\n"

" add %0, %0, %3\\n"

" strex %1, %0, [%2]\\n"

" teq %1, #0\\n"

" bne 1b"

: "=&r" (result), "=&r" (tmp)

: "r" (&v->counter), "Ir" (i)

: "cc");

return result;

}


分享到:


相關文章: