c++的CAS与内存屏障: c/c++的内联汇编(S0)
多线程编程中偶尔需要接触一些底层的东西,如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)
.m
映射到汇编语句中起的作用类似于解引用*
.
int x, a = 10; asm( "movl %1,%0":"=m"(x):"r"(a):); //编译成类似 movl %edx,-32(%rbp), `%0`被`-32(%rbp)`替换
所以,如果传入内联汇编的东西是地址(如函数需要修改传入参数的值,受制于值传递,需要传入地址),想在内联汇编中修改某个地址,有2种做法:
- 传入地址值给寄存器并在汇编中自行解引用,以r或abc等方式关联,加上().
- 传入地址的解引用值,以m方式关联,汇编中对应的操作数不自行解引用.
-
其他的注意点.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.
- 内存屏障.
网友评论