关于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来操作该变量就可以。