c++的CAS与内存屏障: c/c++的内联汇编(S0)
- update note
- 20180710 更新m描述
多线程编程中偶尔需要接触一些底层的东西,如CAS,原子操作,内存屏障甚至有时需要自己包裹系统调用,这边从abc开始,即内联汇编的语法,总结一下目前工作中遇到的相关东西。
语法
- 基本语法
asm(
汇编语句
汇编语句
...
[:输出参数关联列表]
[:输入参数关联列表]
[:破坏寄存器列表]
)
例子1:
/// add
int a, b;
/// b = a + b
asm(
"movl %1,%%eax\n\t"
"addl %0,%%eax\n\t"
"movl %%eax,%1\n\t"
:"+r"(b)
:"r"(a)
:"%eax"
)
关联列表语法
"=" : 标识该寄存器是只写的
"+" : 标识该寄存器既读又写
"r" : 由编译器分配合适的寄存器,在指令中以
%0,%1,...
引用.0,1,2...
以出现的顺序编号.如上面变量b关联的是0,而a关联的是1.-
"m" : 关联时会取关联变量的地址,在汇编代码中对应的
%n
会是1个地址加上内存寻址修饰(AT&T汇编中即为这种形式:ADDRESS_OR_OFFSET(%BASE_OR_OFFSET,%INDEX,MULTIPLIER)
.这理解起来有点绕,但个人觉得这样设计的原因应该是想统一关联变量的传递是值传递的形式.
可以查看下面的AsmAdd1和AsmAdd2int x, a = 10; asm( "movl %1,%0":"=m"(x):"r"(a):); //编译成类似 movl %edx,-32(%rbp), `%0`被`-32(%rbp)`替换
所以,如果传入内联汇编的东西是地址(如函数需要修改传入参数的值,受制于值传递,需要传入地址),想在内联汇编中修改某个地址,有2种做法:
- 传入地址值给寄存器并在汇编中自行解引用,以r或abc等方式关联,加上().如
"r"(ptr) 汇编中使用(%0)
- 传入地址的解引用值,以m方式关联,汇编中对应的操作数不自行解引用.如
"m"(*ptr) 汇编中使用%0
- 传入地址值给寄存器并在汇编中自行解引用,以r或abc等方式关联,加上().如
其他的注意点.asm
- 汇编语句中的寄存器需要2个百分号,
r
关联的操作数只需1个百分号.原因是该内联汇编语句是以字符串的形式被编译器消费的.同理,语句间分隔符需要显式地给\n\t
也是同样的道理. 注意:不能给;
,否则后面的语句在汇编中将被注释! - gcc默认是at&t语法.也就是源操作数跟目的操作数位置与上学时学的
masm汇编
相反. - AT&T解引用操作符是小括号而不是中括号.
-
movl
不允许两个操作数都为内存地址.
-
AT&T语法,常用简单汇编指令
- movl s,d
- addl s,d
下面给出几个例子实现asm_add和asm_swap
1 #include <iostream>
2
3
4 // test for inline asm
5 /*
6 * asm(
7 * "汇编语句1;\n"
8 * "汇编语句2;\n"
9 * "汇编语句3;\n"
10 * :"输出列表"
11 * :"输入列表"
12 * :"破坏寄存器列表"
13 * )
14 *
15 */
16 int AsmAdd0(int a, int b) {
17 asm(
18 "movl %1,%%eax\n\t"- 练习
- asm_add
- asm_swap
19 "addl %0,%%eax\n\t"
20 "movl %%eax,%0\n\t"
21 :"+r"(b)
22 :"r"(a)
23 :"%eax"
24 );
25 return b;
26 }
27
28 void AsmAdd1(int a, int b, int* c) {
29 asm (
30 "addl %1, %2\n\t"
31 "movl %2, (%0)\n\t"
32 :"=r"(c)
33 :"r"(a), "r"(b)
34 );
35 }
36
37 void AsmAdd2(int a, int b, int* c) {
38 asm (
39 "addl %1, %2\n\t"
40 "movl %2, %0\n\t"
41 :"=m"(*c)
42 :"r"(a), "r"(b)
43 );
44 }
45
46 /// *b <----- a
47 ///
48 void AsmSet(int a, int *b) {
49 asm(
50 "movl %1, %%eax\n\t"
51 "movl %%eax, %0\n\t"
52 :"=m"(*b)
53 :"r"(a)
54 :"%eax"
55 );
56 }
57
58
59
60 void testM() {
61 int x;
62 int a = 10;
63 asm( "addl %1, %%ebx\n\t"
64 "movl %1,%0\n\t"
65 "addl %%eax, %%ebx"
66 :"=m"(x):"r"(a):"%eax","%ebx"); //%0会被替换为`(x的偏移量)`
67 std::cout << x << std::endl;
68 }
69
70 template<typename T>
71 void SwapByAsm(T* a, T* b) {
72 asm(
73 "movq (%0), %%rax\n\t"
74 "movq (%1), %%rbx\n\t"
75 "movq %%rbx, (%0)\n\t"
76 "movq %%rax, (%1)\n\t"
77 :"+r"(a), "+r"(b)
78 :
79 :"%rax","%rbx"
80 );
81 }
82 template<>
83 void SwapByAsm(char* a, char* b) {
84 asm(
85 "movb (%0), %%al\n\t"
86 "movb (%1), %%bl\n\t"
87 "movb %%bl, (%0)\n\t"
88 "movb %%al, (%1)\n\t"
89 :"+r"(a), "+r"(b)
90 :
91 :"%al","%bl"
92 );
93 }
94
95 int main() {
96 int a = 20;
97 int b = 10;
98 int c = -1;
99 testM();
100 std::cout << "--------AsmAdd--------" << std::endl;
101
102 c = AsmAdd0(a, b);
103 std::cout << "c:" << c << std::endl;
104
105 c = -1;
106 AsmAdd1(a, b, &c);
107 std::cout << "c:" << c << std::endl;
108
109 c = -1;
110 AsmAdd2(a, b, &c);
111 std::cout << "c:" << c << std::endl;
112
113 std::cout << "-------SwapByAsm---------" << std::endl;
114 double x0 = 0.91, x1 = 10.24;
115 char c0 = 'X', c1 = 'a';
116
117 std::cout << "before: x0:" << x0 << ", x1: " << x1 << std::endl;
118 SwapByAsm(&x0, &x1);
119 std::cout << "after : x0:" << x0 << ", x1: " << x1 << std::endl;
120
121 std::cout << "before: c0:" << c0 << ", c1:" << c1 << std::endl;
122 SwapByAsm(&c0, &c1);
123 std::cout << "after : c0:" << c0 << ", c1:" << c1 << std::endl;
124
125 return 0;
126 }
- 实际用途
- 自己封装非posix系统调用,如gittid(2)
- 自己封装CAS汇编指令,如nginx中的CAS.
- 内存屏障.