關于int全區變量讀寫的原子性
關于int變量的讀寫是否原子性網上有很多讨論,貌似不同平台不同,這裡自己做實作在arm9平台測試。 這裡要注意原子性并非指一條彙編才原子,實際上即使一次指派編譯成幾條彙編依然可以是原子的,隻要保證該記憶體不産生中間值,隻有原值和目标值兩種狀态則就是原子的。對一個int變量指派是否要進入臨界區呢?
以下基于arm920t cpu Sourcery G++ arm-none-eabi-gcc 編譯器測試int原子性:
1、正常四位元組對齊的int變量和非四位元組對齊的char變量
typedef struct {
char c1;
char c2;
// atomic_t i1;
int i1;
} str_t;
volatile str_t Gstr ;
int main(void)
{
Gstr.c1 = 1;
Gstr.c2 = 2;
Gstr.i1 = 0x12345678;
while (1);
return 0;
}
int main(void)
{
8000: e52db004 push {fp} ; (str fp, [sp, #-4]!)
8004: e28db000 add fp, sp, #0
Gstr.c1 = 1;
8008: e59f1030 ldr r1, [pc, #48] ; 8040 <main+0x40>
800c: e3a03001 mov r3, #1
8010: e1a02003 mov r2, r3
8014: e1a03001 mov r3, r1
8018: e5c32000 strb r2, [r3]
Gstr.c2 = 2;
801c: e59f101c ldr r1, [pc, #28] ; 8040 <main+0x40>
8020: e3a03002 mov r3, #2
8024: e1a02003 mov r2, r3
8028: e1a03001 mov r3, r1
802c: e5c32001 strb r2, [r3, #1]
Gstr.i1 = 0x12345678;
8030: e59f3008 ldr r3, [pc, #8] ; 8040 <main+0x40>
8034: e59f2008 ldr r2, [pc, #8] ; 8044 <main+0x44>
8038: e5832004 str r2, [r3, #4]
while (1);
803c: eafffffe b 803c <main+0x3c>
8040: 00010060 .word 0x00010060
8044: 12345678 .word 0x12345678
從以上彙編看,在對齊的int寫操作是原子的( 8038: e5832004 str r2, [r3, #4] ),僅一條str指派指令。 char型可以通過strb對位元組操作的指令一次完成,無論是否對齊都是單指令完成,故也是原子的。(strb的記憶體操作可以以位元組對齊)
2、非四位元組對齊的int型變量指派
</pre><pre name="code" class="cpp">typedef struct {
char c1;
int i1;
} __attribute__((__packed__)) str_t;
volatile str_t Gstr ;
int main(void)
{
Gstr.c1 = 1;
Gstr.i1 = 0x12345678;
while (1);
return 0;
}
Gstr.i1 = 0x12345678;
801c: e59f3024 ldr r3, [pc, #36] ; 8048 <main+0x48>
8020: e5932000 ldr r2, [r3]
8024: e20210ff and r1, r2, #255 ; 0xff
8028: e59f201c ldr r2, [pc, #28] ; 804c <main+0x4c>
802c: e1812002 orr r2, r1, r2
8030: e5832000 str r2, [r3]
8034: e5932004 ldr r2, [r3, #4]
8038: e3c220ff bic r2, r2, #255 ; 0xff
803c: e3822012 orr r2, r2, #18
8040: e5832004 str r2, [r3, #4]
while (1);
8044: eafffffe b 8044 <main+0x44>
8048: 00010068 .word 0x00010068
804c: 34567800 .word 0x34567800
可見當int變量非四位元組對齊時,将無法單指令完成所有指派,分兩步指派,這樣就産生了中間值,即非原子。 ldr str指令要求操作位址是4位元組對齊的。 (PS:從一個非對齊的int的指派看,轉成彙編需要這麼多的操作, 是以平時一些要求高效率的代碼要考慮記憶體對齊問題,預設編譯器都是會定義對齊記憶體的 )
3、非四位元組對齊int變量讀取。
typedef struct {
char c1;
int i1;
} __attribute__((__packed__)) str_t;
volatile str_t Gstr ;
int rd;
int main(void)
{
rd = Gstr.i1;
while (1);
return 0;
}
rd = Gstr.i1;
8008: e59f3024 ldr r3, [pc, #36] ; 8034 <main+0x34>
800c: e5932000 ldr r2, [r3]
8010: e1a02422 lsr r2, r2, #8
8014: e5933004 ldr r3, [r3, #4]
8018: e20330ff and r3, r3, #255 ; 0xff
801c: e1a03c03 lsl r3, r3, #24
8020: e1833002 orr r3, r3, r2
8024: e1a02003 mov r2, r3
8028: e59f3008 ldr r3, [pc, #8] ; 8038 <main+0x38>
802c: e5832000 str r2, [r3]
while (1);
8030: eafffffe b 8030 <main+0x30>
8034: 00010054 .word 0x00010054
8038: 0001005c .word 0x0001005c
從編譯結果看, 非對齊int讀取也是非原子的,i1被分為兩部分,i1的記憶體被通路兩次,這樣中間有可能被修改: 800c: e5932000 ldr r2, [r3] 8014: e5933004 ldr r3, [r3, #4] 讀r3和r3+4 都是i1的記憶體,相當于分兩次通路同一變量就有可能被修改記憶體。
總結:int類型在對齊時讀寫是原子的(編譯預設是對齊的),在非對齊時讀寫不是原子的。 可參考linux中的atomic.h檢視原子操作的實作。可以自己實作一個atomic函數集,當希望一個變量的操作原子性時,使用atomic來操作該變量即可。