首页 > 代码库 > 嵌入式编程:C与汇编的混用

嵌入式编程:C与汇编的混用

主要参考:《深度探索嵌入式操作系统》 5.4节

??

为什么要嵌入汇编语言?

开关CPU中断、

读取CPU的一些特殊寄存器、

设置CPU模式等功能无法用C语言实现(因为C语言属于高级语言,而高级语言是屏蔽底层硬件的)。

提高速度?好像是有这么一种目的,还不太清楚。

??

【代码模板】

__asm__ __volatile__ (

"代码部分" 冒号是分隔符,如果是有汇编代码部分,则冒号可以省略

:输出部分列表 如果没有输入列表,冒号也是必须加的

:输入部分列表

:损坏部分列表);

asm volatile();

??

C语言中嵌入汇编代码的实例:

__asm__ __volatile__( : : : "memory");

// 告诉GCC内存中的数据可能发生了改变了,要回写寄存器了,这是为了达到防止GCC过度优化带来问题的目的

??

C语言中嵌入汇编代码的加法函数:

1 int add(int a1, int a2)

2 {

3 ????????int sum;

4 ????????__asm__ __volatile__(

5 ????????????????"add %[sv1], %[av1], %[av2] \n\t"

6 ????????????????: [sv1] "=r" [sum]

7 ????????????????: [av1] "r" (a1), [av2] "r" (a2)

8 ????????????????: "r4", "cc", "memory"

9 ????????);

10 ????????return sum;

11 }

输入列表用逗号分隔:相当于管理C语言变量和汇编语言变量

[条目名称] "存储类型"(C语言表达式) 因为C语言里的变量此时要用汇编语言来操作,所以要指定分配的类似,存储类型

"r" 通用寄存器

"m" 表示后面的C表达式是内存地址

"I" 表示后面的C表达式是常数

更多见GCC手册

??

输出列表:

[条目名称] "=限定符"(C语言表达式)

"=r"(sum) 表示让GCC给sum分配一个寄存器,只不过是用于输出计算的

??

损坏列表部分:告诉GCC这些寄存器需要生成代码来保存和恢复,还有内存中的数据可能发生改变

"寄存器"

"cc" CPU标志寄存器

"memory"

??

指令部分:

指令部分由引号包含的,"\n\t"即换行符和制表符,完全是为了让GCC格式化输出汇编代码的。

add %[sv1], %[av1], %[av2],为什么不直接使用寄存器呢?当然是可以的,但这里使用条目名主要是为了让GCC能动态分配寄存器、优化代码,也就是好比在汇编语言里面我们可以定义变量了,而原来我们写汇编语言的时候,是直接使用寄存器的,此处相当于通过GCC使汇编编程更加高级。

比如,代码 add %[sv1], %[av1], %[av2], 一般情况下会被GCC处理成代码 add r0, r0, r1,这是不是比直接写原始的纯汇编代码高级得多了。

??

【例子解读】

读取CPU标志寄存器:也就是在C语言编程过程中,我们可以像调用C函数来使用只有汇编语言才能完成的功能了:

1 cpuflg_t hal_read_cpuflg()

2 {

3 ????cpuflg_t cpuflg;

4 ????__asm__ __volatile__(

5 ????????"mrs %[retcpr], cpsr \n\t"

6 ????????: [retcpr] "=r" (cpuflg) 有一个输出条目[retcpr],关联一个分配一个通用寄存器给他的变量,这个条目本质上其实C语言变量在汇编世界里的代言人,最后C语言直接问cpuflg取值,而[retcpr]就负责在汇编世界管理它

7 ????????:

8 ????????: "cc", "memory"

9 ????);

10 ????return cpuflg

11 }

理想情况下这行代码"mrs %[retcpr], cpsr \n\t"会被GCC处理成"mrs r0, cpsr \n\t",而r0寄存器正是GCC用来存放函数返回值的,

最后嵌入式汇编代码模板的损坏部分高速gcc,CPU的标志寄存器可能损坏,内存中的值可能发生改变(但是这部分的处理方式就不需要C程序员关心了,暂时也不必关系)

??

开关ARM920T CPU中断的函数:读(到某个寄存器中) > 改 > 写(把寄存器回写)

技术分享

??

CPU中开关中断是通过设置CPU上的CPSR寄存器中的相关位来实现的,代码中的CIRQFIQ是个宏,会被GCC预处理时,替换成常数0xc0.

从上面的代码的输入条目中它前面的限定符"I"可以告诉GCC,CIRQFIQ是一个常数。GCC会根据这个常数的大小决定把它分配在寄存器(寄存器本质上其实和内存差不多,区别在于访问速度极快以及可能有约定的存储作用)中或者直接放在指令编码数据(要满足存放立即数的条件)中。并且会在__asm__ __volatile__()中的指令执行之前,生成相关代码处理好这个问题。

损坏部分:这里C语言其实没有输出变量的,也没有中间变量,所以汇编代码部分之间使用寄存器了r0,因为代码中用到了r0、CPSR寄存器,所以要在代码模板的损坏部分写上r0(让GCC在__asm__ __volatile__()中的指令执行之前,生成保存r0寄存器中的值的相关代码,一遍在其后恢复到r0寄存器中,可见GCC是先整体分析嵌入部分,然后在处理汇编代码部分)。

??

??

嵌入式编程:C与汇编的混用