天天看點

關于int全區變量讀寫的原子性

關于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來操作該變量即可。