问题
qemu tcg模拟中会遇到两类barrier相关的问题,第一类是qemu tcg需要直接支持guest barrier
指令的模拟,在支持一个构架时,qemu的前端翻译需要把guest的barrier指令先翻译成qemu
barrier相关的中间码(INDEX_op_mb),后端翻译再在host机器上支持中间码的语意。
第二类问题有点隐晦: 不同构架的内存模型定义是不一样的,比如X86的内存模型是TSO,这
个模型下只有不同地址的WAR是可以乱序的,但是ARM64是弱内存模型,不同地址的读写都是
可以乱序的。这样,在qemu tcg模拟多核的时候就可能出现host/guest内存模型不一致带来
的问题,这个问题在guest是强内存模型,host是弱内存模型时会变得尤为凸显,需要注意
的是,反过来guest是弱内存模型,host是强内存模型这里也会有问题。
qemu的官方文档在讲到多核模拟的时候也提到了这个问题,具体可以参考这里的Memory Consistency小节。
具体上看,这个问题是很好理解的,比如guest X86上的两个不同地址的写被翻译到一个tb里,
guest X86认为这两个写指令是不会乱序的,这两个X86上的写指令翻译到ARM64也是写指令,
但是在ARM64 host机器上执行相关的写指令时,ARM64上这两条写指令是可能会乱序的,如果
翻译的时候不加入barrier指令维持X86上的内存序语意,最后在多核模拟的时候可能就会出错。
模拟逻辑
如上qemu中已经解决了相关的问题,qemu提供了一个barrier相关的中间码INDEX_op_mb,这
个中间码可以带上各种参数表示各种barrier的语意:
1 | typedef enum { |
比如,上面TCG_MO_LD_LD表示load/load之间要保序,TCG_BAR_LDAQ表示acquire语意的barrier。
单独使用LDAQ/STRL的语意是明确的,怎么使用LD/ST这组barrier需要找下具体的位置,SC
的使用场景也需要找下。
如上,对于第一类问题,qemu前端翻译用qemu barrier中间码支持guest barrier的语意,
注意,相关的支持可能会把barrier的语意增强,保证模拟功能正确即可。ARM64上DSB/DMB
的qemu支持是这样的:
1 | /* qemu/target/arm/tcg/translate-a64.c */ |
可以看到,上面支持了DMB ST/LD的定义,比如ARM的定义中,DMB LD就是要保证LD/LD以及LD/ST
之间的顺序。但是,这里没有搞清楚TCG_BAR_SC的语意,DMB LD不是保证LD/LD以及LD/ST的
顺序就可以了么?DSB的支持也没有搞明白,DSB是保证非访存指令和访存指令之间的顺序,
难道TCG_BAR_SC还有这个语意?
对于第二类情况,qemu会在load/store翻译里隐式的插入必要的barrier中间码。qemu怎么
知道guest/host之间的具体指令翻译时是否需要插入barrier来保证guest访存指令的语意呢?
qemu定义了各个构架下的memory order上的约束,比如ARM64上是:
1 | /* qemu/target/arm/cpu.h */ |
X86上是:
1 | #define TCG_TARGET_DEFAULT_MO (TCG_MO_ALL & ~TCG_MO_ST_LD) |
TARGET表示TCG的目标,所以是host,GUEST表示guest。所以TCG_GUEST_DEFAULT_MO和
TCG_GUEST_DEFAULT_MO在一个qemu tcg模拟中是确定的值,比如在ARM64上模拟X86,那么
TCG_GUEST_DEFAULT_MO取X86的定义(TCG_MO_ALL & ~TCG_MO_ST_LD),TCG_TARGET_DEFAULT_MO
取ARM64的定义。TCG_MO_ALL & ~TCG_MO_ST_LD表示X86下的memory order是放松了ST/LD之间
的顺序,放松了WAR的顺序,ARM64是弱内存序,对不同地址的load/store都没有约束,所以
这里是0。没有搞清X86这里的写法,如果是放松store-after-load,应该是~TCG_MO_LD_ST?
qemu在load/store中间码的支持里,用tcg_gen_req_mo函数判断是否需要插入barrier,如果
需要插入,就直接插入对应barrier的中间码:
1 | /* 这里以st_i32_int举例,其他load/store的实现类似 */ |
qemu tcg中load/store指令的中间码的支持会调到如上的一组函数中,具体load/store中间码
的支持可以参考这里。
tcg_gen_req_mb的输入表示要支持guest barrier的语意,tcg_ctx->guest_mo是TCG_GUEST_DEFAULT_MO,
第一个&操作表示guest上要求内存序才有必要继续要求host去支持,第二个&表示guest上要
求但是host上不支持的内存序才有必要插入barrier指令支持。在最后插入barrier中间码的
时候只插入对应约束的barrier就好,所以不清楚这里为什么要加强到TCG_BAR_SC?
注意,这里st_i32_int需要保证和它之前的load/store指令保序,和他store指令保序,这个
看起来是这里默认是TSO的约束,如果guest是ARM64,第一个&操作会直接过滤掉tcg_gen_req_mo
输入中的默认约束。
对于X86 guest/ARM64 host的场景,第一个&操作后type为TCG_MO_LD_ST | TCG_MO_ST_ST,
第二个&操作后type为TCG_MO_LD_ST | TCG_MO_ST_ST,插入mb的type是LD_ST/ST_ST/SC。
如上的两种情况生成的barrier中间码在后端翻译时被翻译成host上的barrier指令,ARM64
后端对barrier的支持如下:
1 | /* qemu/tcg/aarch64/tcg-target.c.inc */ |
可以看到X86 guest/ARM64 host的场景下,st_i32_int中ARM64 host上插入的barrier指令是
DMB,作用范围是inner域,DMB_LD | DMB_ST形成的DMB编码的CRm是0b1011,汇编表示是DMB ISH,
语意是拦住DMB前后的store和load。
可以看到X86到ARM64的翻译,其实只要实现ST_ST/LD_LD/ST_LD的语意就好,但是这里因为
翻译的缘故,最后的翻译加强到store/load之间都保序了,实际上这里降低了程序的性能。