Chinaunix

标题: volatile 和 内存屏障 [打印本页]

作者: zylthinking    时间: 2011-11-01 15:47
标题: volatile 和 内存屏障
本帖最后由 zylthinking 于 2011-11-01 17:25 编辑

事隔多日, 又在这里开始弄不清楚了:
内存屏障似乎不影响编译器怎么编译吧;
那么如果一下伪码:

int a = 0;
mov eax,  a
-------------------> 发生切换
mb();
-------------------> 或者这里发生切换
test eax, ebx

在另一个线程中
a = 10;
wmb();

那么执行 test eax, ebx 中, eax 中能保证为 10 吗?
内存屏障影响的是高速缓存, 那么影响已经 load 进 CPU 寄存器中的值吗?

我的理解:
在单CPU下, 寄存器中的值在切换时会被保存到内存, 切换回后再次从内存中恢复
多CPU下, CPU1 发出的 invalid request 影响的是高速缓存, 但进入寄存器中的值怎么知道自己对应的内存已经变了? 不可能为每一个寄存器中的值保留一份相对应的内存地址吧

那么, 执行到 test eax, ebx 中, 说不定这个还真是 0
也就是说, 内存屏障影响的是 mov eax, a, 如果 wmb() 执行了, 那么CPU高速缓存中的数据保证失效, 从而读内存, wmb 同时保证内存中已经是新值了, 从而可以保证完整性;
但如果执行到 mov eax, a 之后再发生 a = 10, wmb(), 那么只会导致CPU 高速缓存失效, 内存被更新; 但 test eax, ebx 确实寄存器间的操作, 会不会导致数据错误???

而 voaltile, 正是约束编译器的, 也就是说它在编译层面保证 mov eax, a   test eax, ebx 不会被编译为2条指令, 而时编译为 test *(&a), ebx???

现在再接着推理:
1。 在UP下, a = 10;
wmb();
执行时, int a = 0;
mov eax,  a
-------------------> 发生切换
mb();
-------------------> 或者这里发生切换
test eax, ebx
中所有的寄存器内容其实都保存在 task 相关结构中, 那么 wmb时, CPU作为操作系统下层, 肯定没能力自己去找 task结构做更新; 因此, 等很久以后执行 test eax, ebx 时, 我基本肯定 eax中是 旧值了

2。 在SMP下, 假设两个CPU分贝同时在执行以下代码
int a = 0;

CPU1:                              CPU2:
a += 1;                           a += 2;
wb();                               wb();

如果发生了这种情况, 两个CPU在相同的时间内执行了不同的赋值语句, 那么 CPU1 和 CPU2谁向谁同步呢???
估计是个随机情况了吧???
作者: 瀚海书香    时间: 2011-11-01 16:53
回复 1# zylthinking
前段时间看这块内容,看的都快恶心了。。。
简单点说就是:
volatile应该是优化屏障 保障编译的时候不会乱序
内存屏障是保证CPU执行的时候不会乱序
作者: zylthinking    时间: 2011-11-01 16:55
回复  zylthinking
前段时间看这块内容,看的都快恶心了。。。
简单点说就是:
volatile应该是优化屏障 ...
瀚海书香 发表于 2011-11-01 16:53


你说的这两条, 之前我已经总结出来了; 现在是新情况
作者: 瀚海书香    时间: 2011-11-01 16:59
回复 1# zylthinking

内存屏障默认都是有优化屏障的.
作者: zylthinking    时间: 2011-11-01 17:05
回复  zylthinking

内存屏障默认都是有优化屏障的.
瀚海书香 发表于 2011-11-01 16:59


你还是没明白我在想什么, 你说的这三条, 我都知道, 然而我还是有1楼的疑问。
作者: 瀚海书香    时间: 2011-11-01 17:07
回复 1# zylthinking

内存屏障似乎不影响编译器怎么编译吧;

如果内存屏障包含优化屏障,怎么会不影响编译器编译呢。
作者: zylthinking    时间: 2011-11-01 17:28
本帖最后由 zylthinking 于 2011-11-01 17:40 编辑
回复  zylthinking


如果内存屏障包含优化屏障,怎么会不影响编译器编译呢。
瀚海书香 发表于 2011-11-01 17:07


原来纠结这个, 我说的影响不是影响指令重排, 而是会不会 a += 1 这样的代码会被编译成3条指令; 而 voaltile int a; a += 1 只编译成一条指令
或者事实是
volatile int a = 0;

// 这个语句可以被编译成任意多个宏指令, 这些宏指令之间还可以进行任务切换, 这些指令之间, 仍然使用寄存器值, 因此切换回来后, 依旧使用旧值 0 进行运算, 最后写到内存的还是 1; 如果在切换期间被改变, 比如 10, 则10 最终被丢失
a += 1;
             // 如果在这里被切换, 并被改为 10, 如果任务切换时已经反应到内存, 则由于volatile性质, 切换回来执行完最后一句后,  a == 11;
                  
// 这里必然重新从内存取
a += 1;
作者: tempname2    时间: 2011-11-01 18:18
这都乱七八糟的什么啊。

volatile最一般的作用是:告诉编译器,每次引用变量时都访问内存,而不要把内存中的值读到寄存器后续对变量的引用直接用寄存器里的值。因为memory-mapped寄存器可以能异步变化。

自己的经历:某个寄存器的值会随着CPU时钟加1,准备在一个时间点读取该寄存器值,再另一个时间点再读一次,从而得到两点间时间间隔。結果算出的时间间隔总是0。后来加volatile就好了,道理就是上面所说。

由于看不懂LZ所说,LZ纠结的可能是volitale其它用法。
作者: kouu    时间: 2011-11-01 18:19
但如果执行到 mov eax, a 之后再发生 a = 10, wmb(), 那么只会导致CPU 高速缓存失效, 内存被更新; 但 test eax, ebx 确实寄存器间的操作, 会不会导致数据错误???

我认为这样的代码本身就没有同步逻辑,test eax, ebx的时候,eax里面装是是a的新值还是a的旧值根本就没关系。
换个说法,这个地方的代码没有考虑test a的时候是否执行过a = 10,那么CPU或者编译器自然也不需要考虑这个问题。

而 voaltile, 正是约束编译器的, 也就是说它在编译层面保证 mov eax, a   test eax, ebx 不会被编译为2条指令, 而时编译为 test *(&a), ebx???

我不知道X86里面test指令是否支持取内存,但是RISC机器应该是不支持这样做的。所以不管怎样,test a的总有1条指令不能搞定的时候。
在这个问题上,我觉得voaltile跟优化屏障并没有区别。

如果发生了这种情况, 两个CPU在相同的时间内执行了不同的赋值语句, 那么 CPU1 和 CPU2谁向谁同步呢???
估计是个随机情况了吧???

我觉得这个跟第一个场景是类似的,代码里面根本就没有同步逻辑,谁先发生谁后发后都是有可能的。那么最终的结果如何,CPU或者编译器当然也不会去保证。
作者: zylthinking    时间: 2011-11-01 18:32
本帖最后由 zylthinking 于 2011-11-01 18:33 编辑
我认为这样的代码本身就没有同步逻辑,test eax, ebx的时候,eax里面装是是a的新值还是a的旧值根本就没关 ...
kouu 发表于 2011-11-01 18:19



是这样的, 我最终的结论也就是因为代码本身无同步, 那么一切自然而然的显得很优雅的就没问题了。
另一个结论就是我猜测的, volatile 保证的是C语句之间的严格从内存读, 但如果一条C语句编译成若干汇编语句, volatile 也不能保证这几个汇编语句之间使用寄存器。

一个比较纠结的是这样

volatile int a = 0;

THREAD 1:
for(int i = 0; i < 4; ++i){
    ++a;
}

THREAD 2:
a = 10;

main(){
    start thread 1;
    start thread 2;

    wait both thread terminate:
    printf("%d\n", a);
}

得到的值很可能是 4;
这个如果我对volatile的结论是正确的, 那么这个明显在C层面显得不可能的结果却称为事实输出(只要理想的认为线程切换不会导致任何情况的内存屏障)
作者: sonicling    时间: 2011-11-02 00:28
本帖最后由 sonicling 于 2011-11-02 00:45 编辑

volatile号称保证单CPU单个对象的访问顺序,但是Cache、乱序都有可能使它成为一句空话,结果volatile只剩下一个保证了,那就是保证读写不会被优化掉,这只是它的号称的一个附加效果。

内存屏障跟切换没关系,单x86下用不用内存屏障都没什么区别。内存屏障针对多CPU下,一个CPU等待另外一个CPU的结果。
作者: 瀚海书香    时间: 2011-11-02 08:11
回复 11# sonicling

单x86下用不用内存屏障都没什么区别。内存屏障针对多CPU下,一个CPU等待另外一个CPU的结果。

   
个人认为这句话应该是不正确的。在单CPU的情况下,也是需要内存屏障的。比如CPU和外设交互的情形。
作者: tempname2    时间: 2011-11-02 08:48
线程共用寄存器?
x86上cache需要软件担心一致性问题?
内存屏障?write buffer?
作者: tempname2    时间: 2011-11-02 09:32
回复 11# sonicling


   
Some devices present their control interfaces as collections of memory
locations, but the order in which the control registers are accessed is very
important.  For instance, imagine an ethernet card with a set of internal
registers that are accessed through an address port register (A) and a data
port register (D).  To read internal register 5, the following code might then
be used:

        *A = 5;
        x = *D;

but this might show up as either of the following two sequences:

        STORE *A = 5, x = LOAD *D
        x = LOAD *D, STORE *A = 5

the second of which will almost certainly result in a malfunction, since it set
the address _after_ attempting to read the register.

作者: sonicling    时间: 2011-11-02 10:33
回复 12# 瀚海书香
回复 14# tempname2


    的确。涉及到乱序,还是要靠内存屏障。




欢迎光临 Chinaunix (http://bbs.chinaunix.net/) Powered by Discuz! X3.2