关于内存屏障

zkuang82 2009-09-24 10:18:56
Ldd上说内存屏障的编译效果是

把当前CPU寄存器中所有修改的数值保存到内存中,需要这些数据的时候再重新读出来。

我看到了某些代码上只有一句:
#define barrier() __asm__ __volatile__("": : :"memory")
查了一下GCC的说明:
If your assembler instructions access memory in an unpredictable fashion, add ‘memory’
to the list of clobbered registers. This will cause GCC to not keep memory values cached in
registers across the assembler instruction and not optimize stores or loads to that memory.
You will also want to add the volatile keyword if the memory affected is not listed in the
inputs or outputs of the asm, as the ‘memory’ clobber does not count as a side-effect of the
asm. If you know how large the accessed memory is, you can add it as input or output but
if this is not known, you should add ‘memory’.
似乎只完成了后半部分的功能,就是说不把数据cache在寄存器里面,用到的时候再去读。这里好像隐喻了在寄存器中的值会被写到内存中,不然以后的读取还是会出问题的,但不是很确定。

大家有没有什么资料可以证实GCC是怎么做的?
...全文
1347 15 打赏 收藏 转发到动态 举报
写回复
用AI写文章
15 条回复
切换为时间正序
请发表友善的回复…
发表回复
Joe_vv 2012-01-17
  • 打赏
  • 举报
回复
#define barrier() __asm__ __volatile__("": : :"memory")
是干什么用的?

cloudluo 2010-05-27
  • 打赏
  • 举报
回复
今天也是研究LKD中的第二版,对内存屏障有点兴趣,所以搜索到楼主的这个帖子,也看到了楼主得出结论的
wenxy1提供的资料(pdf文件)
引用pdf中的内容:
4)memory 强制 gcc 编译器假设 RAM 所有内存单元均被汇编指令修改,这样 cpu 中的 registers 和 cache 中已缓存的内存单元中的数据将作废。cpu 将不得不在需要
的时候重新读取内存中的数据。这就阻止了 cpu 又将 registers,cache 中的数据用于去优化指令,而避免去访问内存。

始终觉得这些PDF中所描速的内容和楼主的结论相反

于是仔细的做了如下实验,代码如下:

#include <stdio.h>
#define barrier() __asm__ __volatile__("": : :"memory")
int
test()
{
int a = 3;
scanf("%d",&a);
int b,c,d,e;
b = a;
//barrier();
c = a;
//barrier();
e = a;
//barrier();
d = a;
printf("b:%d,a:%d,e:%d,c:%d\n",b,a,e,c);
}

第一次在注释了barrier的代码使用gcc编译得到的汇编关键内容如下,编译命令为gcc -O2 -S mb.c -o mb.s

pushl %ebp
movl %esp, %ebp
subl $56, %esp
leal -12(%ebp), %eax
movl $3, -12(%ebp)
movl %eax, 4(%esp)
movl $.LC0, (%esp)
call __isoc99_scanf
movl -12(%ebp), %eax
movl $.LC1, (%esp)
movl %eax, 16(%esp)
movl %eax, 12(%esp)
movl %eax, 8(%esp)
movl %eax, 4(%esp)
call printf
leave
ret
第二次,只是取消barrier的注释,即使用内存屏障,编译命令同为:gcc -O2 -S mb.c -o mb.s,汇编文件如下:
pushl %ebp
movl %esp, %ebp
subl $56, %esp
leal -12(%ebp), %eax
movl $3, -12(%ebp)
movl %eax, 4(%esp)
movl $.LC0, (%esp)
call __isoc99_scanf
movl -12(%ebp), %eax
movl -12(%ebp), %ecx
movl -12(%ebp), %edx
movl %edx, 12(%esp)
movl -12(%ebp), %edx
movl %ecx, 16(%esp)
movl %eax, 4(%esp)
movl $.LC1, (%esp)
movl %edx, 8(%esp)
call printf
leave
ret
从这两个结果中,明显得出了与pdf中描速的相同,但是和楼主的结论不同的结果。

分析楼主的代码,以为是该代码中没有涉及到从内存中取值,因为从一开始的时候参数就进入到了eax和edx这两个通用寄存器中。自然也就不会在加入barrier之后从内存中取新的值,相反在我的实验中,恰恰使用了ebp的堆栈取值,证明了memory命令的作用。

csan 2009-09-25
  • 打赏
  • 举报
回复
好问题,收藏!
zkuang82 2009-09-25
  • 打赏
  • 举报
回复
先不结贴,看看大牛什么时候出没,赏光过来踩一脚。
zkuang82 2009-09-25
  • 打赏
  • 举报
回复
感谢wenxy1提供的资料。
从资料看来,memory barrier的含义大概是:
1. 保证编译器不会对代码做优化,打乱指令的执行顺序。
2. 向CPU发一条指令,保证硬件不会打乱在barrier两边的内存访问指令不会被打乱。
3. 在CPU寄存器里面的值不会失效,也就是说,这些值不会在barrier之后重新从内存读取,所以也不必把寄存器中的值写回内存当中(这个跟Ldd3的描述不一致,不知道是我搞错了还是作者搞错了)。

验证:
把内核bnx2的drvier改一下(linux-2.6.31, gcc version 4.3.2):
static u32 bnx2_tx_avail(struct bnx2 *bp, struct bnx2_tx_ring_info *txr)
//原来声明的inline去掉了,保证符号还在。
{
u32 diff;

diff = txr->tx_prod - txr->tx_cons;//提前到barrier之前,以便观测。

smp_mb();

/* The ring uses 256 indices for 255 entries, one of them
* needs to be skipped.
*/
//diff = txr->tx_prod - txr->tx_cons; 原行在这里。
if (unlikely(diff >= TX_DESC_CNT)) {
diff &= 0xffff;
if (diff == TX_DESC_CNT)
diff = MAX_TX_DESC_CNT;
}
return (bp->tx_ring_size - diff);
}
EXPORT_SYMBOL(bnx2_tx_avail);//保证符号还在,如果没有这个符号bnx2_tx_avail还是找不到。

用gdb反汇编:
GNU gdb Fedora (6.8-32.fc10)
Copyright (C) 2008 Free Software Foundation, Inc.
License GPLv3+: GNU GPL version 3 or later <http://gnu.org/licenses/gpl.html>
This is free software: you are free to change and redistribute it.
There is NO WARRANTY, to the extent permitted by law. Type "show copying"
and "show warranty" for details.
This GDB was configured as "i386-redhat-linux-gnu"...
(gdb) disassemble bnx2_tx_avail
Dump of assembler code for function bnx2_tx_avail:
0x00000000 <bnx2_tx_avail+0>: push %ebp
0x00000001 <bnx2_tx_avail+1>: movzwl 0x4(%edx),%ecx
0x00000005 <bnx2_tx_avail+5>: mov %esp,%ebp
0x00000007 <bnx2_tx_avail+7>: push %ebx
0x00000008 <bnx2_tx_avail+8>: mov %eax,%ebx
0x0000000a <bnx2_tx_avail+10>: movzwl 0x18(%edx),%eax
0x0000000e <bnx2_tx_avail+14>: sub %eax,%ecx
0x00000010 <bnx2_tx_avail+16>: lock addl $0x0,(%esp) //barrier在这里
0x00000015 <bnx2_tx_avail+21>: cmp $0xff,%ecx
0x0000001b <bnx2_tx_avail+27>: jbe 0x31 <bnx2_tx_avail+49>
0x0000001d <bnx2_tx_avail+29>: and $0xffff,%ecx
0x00000023 <bnx2_tx_avail+35>: mov $0xff,%eax
0x00000028 <bnx2_tx_avail+40>: cmp $0x100,%ecx
0x0000002e <bnx2_tx_avail+46>: cmove %eax,%ecx
0x00000031 <bnx2_tx_avail+49>: mov 0xb80(%ebx),%eax
0x00000037 <bnx2_tx_avail+55>: pop %ebx
0x00000038 <bnx2_tx_avail+56>: pop %ebp
0x00000039 <bnx2_tx_avail+57>: sub %ecx,%eax
0x0000003b <bnx2_tx_avail+59>: ret
End of assembler dump.

结论:
在barrier的前后没有出现把寄存器内容写到内存的操作,也没有重新读取的操作。
zkuang82 2009-09-24
  • 打赏
  • 举报
回复
[Quote=引用 6 楼 jiazhen 的回复:]
#define barrier() __asm__ __volatile__("": : :"memory")

CPU越过内存屏障后,将刷新自己对存储器的缓冲状态。这条语句实际上不生成任何代码,但可使gcc在barrier()之后刷新寄存器对变量的分配。
也就是说,barrier()宏只约束gcc编译器,不约束运行时的CPU行为。
[/Quote]

我的理解是并不完全不控制CPU行为。barrier也不总是不生成任何代码。看看X86的代码就知道barrier用的是fence类指令(我不太确定这个指令的实际用途)。
barrier()估计能够完成三件事:
1. 告诉GCC不要优化代码
2. 处理寄存器中的数据,把它回写到内存
3. 告诉GCC寄存器里的东西不能用,要重新读取。

2在文档里没有直接说明,但推理应该是成立的。如果不把寄存器中的数据写到内存,那这些数据的改动不会反应在内存中,下次读取的时候会是旧的值。
GCC的manual对1、3都说明的很清楚,关键是2没有明确提出。有些平台对2似乎有指令直接支持(可能是之前说的fence),但对于没有这种支持的CPU是否需要手工去做?还是只需要用““:::”memory?有没有具体文档说明。
  • 打赏
  • 举报
回复
#define barrier() __asm__ __volatile__("": : :"memory")

CPU越过内存屏障后,将刷新自己对存储器的缓冲状态。这条语句实际上不生成任何代码,但可使gcc在barrier()之后刷新寄存器对变量的分配。
也就是说,barrier()宏只约束gcc编译器,不约束运行时的CPU行为。
zkuang82 2009-09-24
  • 打赏
  • 举报
回复
[Quote=引用 3 楼 jiazhen 的回复:]
先来点背景知识的介绍。

内存屏障:为了防止编译器和硬件的不正确优化,使得对存储器的访问顺序(其实就是变量)和书写程序时的访问顺序不一致而提出的一种解决办法。

[/Quote]

这个我明白。不单止是防止编译器对代码的优化,关键寄存器的东西怎么处理。如果需要写回内存,对于没有fence类指令的CPU,单一个#define barrier() __asm__ __volatile__("": : :"memory") 是不是就能够让gcc做正确的事。
  • 打赏
  • 举报
回复
由于编译器的优化和缓存的使用,导致对内存的写入操作不能及时地反映出来,也就是说当完成对内存的写入操作之后,读取出来的有可能是旧的内容。这种现象称为内存屏障(Memory Barrier) 。

  • 打赏
  • 举报
回复
先来点背景知识的介绍。

内存屏障:为了防止编译器和硬件的不正确优化,使得对存储器的访问顺序(其实就是变量)和书写程序时的访问顺序不一致而提出的一种解决办法。
zkuang82 2009-09-24
  • 打赏
  • 举报
回复
可惜已经是分数上限了,不然会毫不犹豫的加分引牛
steptodream 2009-09-24
  • 打赏
  • 举报
回复
好难的问题 等待高手帮你解决吧
Wenxy1 2009-09-24
  • 打赏
  • 举报
回复
http://linux.chinaunix.net/bbs/attachments/month_0601/xNq05sbB1c+7+g==_gV0saMDl5bfZ.pdf

http://blog.csdn.net/zhangf/archive/2006/07/28/992021.aspx
zkuang82 2009-09-24
  • 打赏
  • 举报
回复
自己up一下
steptodream 2009-09-24
  • 打赏
  • 举报
回复
[Quote=引用 2 楼 elfirex 的回复:]
可惜已经是分数上限了,不然会毫不犹豫的加分引牛
[/Quote]
你已经给的很多了 只要会的都会回答你的

4,436

社区成员

发帖
与我相关
我的任务
社区描述
Linux/Unix社区 内核源代码研究区
社区管理员
  • 内核源代码研究区社区
加入社区
  • 近7日
  • 近30日
  • 至今
社区公告
暂无公告

试试用AI创作助手写篇文章吧