C语言嵌入汇编代码

写在前面

在靠近底层方面的编程,C语言具有Java、Python都不具有的优势——直接操作内存。即使这样,仍然有一些操作需要“汇编代码”的帮助,例如对具体寄存器的操作,实现指令的原子性等。因此,C和汇编的混合编程在实际程序中很重要(也成为了C语言项目移植的难题)。

参考文章

arm 嵌入汇编 - 知乎 (zhihu.com)包含起来即可,对应的汇编指令就会被执行。)

(34条消息) 原子操作与 x86 上的 lock 指令前缀_x86 lock前缀_zacklin的博客-CSDN博客

6. 并发控制基础 (jyywiki.cn)

C语言嵌入汇编代码

asm代码

在C语言中嵌入汇编代码由gcc编译器实现,gcc提供了asm__asm__关键字,asm__asm__的别名。最简单的使用方法:

1
asm("mov r1, r2")

上面的代码实现了将寄存器r2的值复制给寄存器r1

然而,寄存器r1, r2可能已经被使用,我们需要更安全、更有意义地使用汇编语言。

例如将变量val2的值赋值给val1:

1
2
3
4
5
6
7
8
void func() {
int val1 = 3, val2 = 5;
asm("mov %0, %1"
: "=r"(val1)
: "r"(val2)
:);
printf("val1 = %d\n", val1);
}

指令分为四个部分

  • 汇编操作代码:多条指令需要在指令间使用\n\t 隔开。使用第二和第三部分提供的操作数时,使用%n来代替操作数:%0表示输入输出列表中的第一个操作数,以此类推。
  • 输出操作数列表:输出的操作数,一般为C语言中的变量,多个用,隔开。

  • 输入操作数列表:输入的操作数,一般为C语言中的变量必须提供表示操作数的属性如"r"

  • 被破坏的列表,避免C语言代码和汇编代码访问同一个寄存器,编译器会对数据进行保存。例如:asm("mov lr,#1":::"lr");

    • 两个特殊的参数:“cc” 对应的并非是普通寄存器,而是 CPU 的状态寄存器,如果某些指令将状态寄存器修改了,需要在 clobber list 中添加 “cc” 来声明这个事情。

      “memory” 对应内存操作,这从名称也可以看出,当 clobber list 中包含 “memory” 时,表示嵌入汇编代码会对内存进行一些操作,gcc 生成的代码会将特定的寄存器的值写回到内存中以保证内存中的值是最新的,这样做的原因是 gcc 经常会将内存数据缓存在寄存器中,如果不及时写回,嵌入汇编代码读到的内存就是原来的值

操作数属性

对于输出操作数

  • "=" 表示只写,通常用于所有输出操作数的属性
  • "+"表示读写,只能被列为输出操作数的属性,否则编译会报错

常用属性:"r"表示通用寄存器,"f"表示浮点寄存器,"m"表示内存地址空间

volatile

volatile关键字用于避免编译器对程序和数据进行优化

编译器可能自作主张地认为没有输出的语句可以删除,或者认为两条语句的顺序可以交换

对于 CPU 内部状态寄存器的操作,就不会有输入输出,通常都会加上

并发代码案例sum.c

使用了南京大学jyy老师的课堂代码,线程库thread.h见“附录”

因为sum++;不是原子指令,所以下面的代码有并发问题(sum.c):

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
#include "thread.h"

#define N 100000000

long sum = 0;

void Tsum() {
for (int i = 0; i < N; i++) {
sum++;
}
}

int main() {
create(Tsum);
create(Tsum);
join();
printf("sum = %ld\n", sum);
}
1
gcc sum.c -lpthread

运行程序产生的结果:

1
2
3
4
$ ./a.out
sum = 113467324
$ ./a.out
sum = 123753811

使用嵌入汇编代码实现原子性(sum-atomic.c):

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
#include "thread.h"

#define N 100000000

long sum = 0;

void atomic_inc(long *ptr) {
asm volatile(
"lock incq %0" // Atomic + memory fence
: "+m"(*ptr)
:
: "memory"
);
}

void Tsum() {
for (int i = 0; i < N; i++) {
atomic_inc(&sum);
}
}

int main() {
create(Tsum);
create(Tsum);
join();
printf("sum = %ld\n", sum);
}

注:lock 指令前缀能确保在多处理器系统或多线程竞争的环境下互斥地使用这个内存地址

附录

jyy线程库thread.h

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
#include <stdlib.h>
#include <stdio.h>
#include <string.h>
#include <stdatomic.h>
#include <assert.h>
#include <unistd.h>
#include <pthread.h>

#define NTHREAD 64
enum { T_FREE = 0, T_LIVE, T_DEAD, };
struct thread {
int id, status;
pthread_t thread;
void (*entry)(int);
};

struct thread tpool[NTHREAD], *tptr = tpool;

void *wrapper(void *arg) {
struct thread *thread = (struct thread *)arg;
thread->entry(thread->id);
return NULL;
}

void create(void *fn) {
assert(tptr - tpool < NTHREAD);
*tptr = (struct thread) {
.id = tptr - tpool + 1,
.status = T_LIVE,
.entry = fn,
};
pthread_create(&(tptr->thread), NULL, wrapper, tptr);
++tptr;
}

void join() {
for (int i = 0; i < NTHREAD; i++) {
struct thread *t = &tpool[i];
if (t->status == T_LIVE) {
pthread_join(t->thread, NULL);
t->status = T_DEAD;
}
}
}

__attribute__((destructor)) void cleanup() {
join();
}