• 关于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来操作该变量就可以。
  • 相关阅读:
    “正则表达式”实践日志
    一种存储数据的方法
    计划
    位运算
    ACM
    ACM-括号匹配问题
    [转]昆明长水机场安防系统应用案例分析
    Request.ServerVariables详细说明
    大数据平台的秘密
    【转】MongoDB资料汇总专题
  • 原文地址:https://www.cnblogs.com/zfyouxi/p/4466865.html
Copyright © 2020-2023  润新知