Linux内核开发过程中,会遇到很到内联汇编的代码段。因为内联汇编在其性能上的优势,导致即使它生涩枯燥也依旧被看重。
GCC支持在C/C++代码中嵌入汇编代码,这些代码被称作是"GCC Inline ASM"(GCC内联汇编);
一、基本内联汇编
GCC中基本的内联汇编非常易懂,格式如下:
__asm__ [__volatile__] ("instruction list");
其中,
1.__asm__:
它是GCC定义的关键字asm的宏定义(#define __asm__ asm),它用来声明一个内联汇编表达式,所以,任何一个内联汇编表达式都以它开头,它是必不可少的;如果要编写符合ANSI C标准的代码(即:与ANSI C兼容),那就要使用__asm__;
2.__volatile__:
它是GCC关键字volatile的宏定义;这个选项是可选的;它向GCC声明"不要动我所写的instruction list,我需要原封不动地保留每一条指令";如果不使用__volatile__,则当你使用了优化选项-O进行优化编译时,GCC将会根据自己的判断来决定是否将这个内联汇编表达式中的指令优化掉;如果要编写符合ANSI C标准的代码(即:与ANSI C兼容),那就要使用__volatile__;
3.instruction list:
它是汇编指令列表;它可以是空列表,比如:__asm__ __volatile__("");或__asm__("");都是合法的内联汇编表达式,只不过这两条语句什么都不做,没有什么意义;但并非所有"instruction list"为空的内联汇编表达式都是没意义的,比如:__asm__("":::"memory");就是非常有意义的,它向GCC声明:"我对内存做了改动",这样,GCC在编译的时候,就会将此因素考虑进去;
例如:
__asm__("movl %esp,%eax");
或者是
__asm__("movl $1,%eax
xor %ebx,%ebx
int $0x80");
或者是
__asm__("movl $1,%eax\n\t"\
"xor %ebx,%ebx\n\t"\
"int $0x80");
instruction list的编写规则:当指令列表里面有多条指令时,可以在一对双引号中全部写出,也可将一条或多条指令放在一对双引号中,所有指令放在多对双引号中;如果是将所有指令写在一对双引号中,那么,相邻俩条指令之间必须用分号";"或换行符(\n)隔开,如果使用换行符(\n),通常\n后面还要跟一个\t;或者是相邻两条指令分别单独写在两行中;
规则1:任意两条指令之间要么被分号(;)或换行符(\n)或(\n\t)分隔开,要么单独放在两行;
规则2:单独放在两行的方法既可以通过\n或\n\t的方法来实现,也可以真正地放在两行;
规则3:可以使用1对或多对双引号,每1对双引号里面可以放1条或多条指令,所有的指令都要放在双引号中;
例如,下面的内联汇编语句都是合法的:
__asm__("movl %eax,%ebx
sti
popl %edi
subl %ecx,%ebx");
__asm__("movl %eax,%ebx; sti
popl %edi; subl %ecx,%ebx");
__asm__("movl %eax,%ebx; sti\n\t popl %edi
subl %ecx,%ebx");
如果将指令放在多对双引号中,则,除了最后一对双引号之外,前面的所有双引号里的最后一条指令后面都要有一个分号(;)或(\n)或(\n\t);比如,下面的内联汇编语句都是合法的:
__asm__("movl %eax,%ebx
sti\n"
"popl %edi;"
"subl %ecx,%bx");
__asm__("movl %eax,%ebx; sti\n\t"
"popl %edi; subl %ecx,%ebx");
__asm__("movl %eax,%ebx; sti\n\t popl %edi\n"
"subl %ecx,%ebx");
二、带有C/C++表达式的内联汇编
GCC允许你通过C/C++表达式指定内联汇编中"instruction list"中的指令的输入和输出,你甚至可以不关心到底使用哪些寄存器,完全依靠GCC来安排和指定;这一点可以让程序员免去考虑有限的寄存器的使用,也可以提高目标代码的效率;
1.带有C/C++表达式的内联汇编语句的格式:
__asm__ [__volatile__]("instruction list":Output:Input:Clobber/Modify);
圆括号中的内容被冒号":"分为四个部分:
A.如果第四部分的"Clobber/Modify"可以为空;如果"Clobber/Modify"为空,则其前面的冒号(:)必须省略;比如:语句__asm__("movl %%eax,%%ebx":"=b"(foo):"a"(inp):);是非法的,而语句__asm__("movl %%eax,%%ebx":"=b"(foo):"a"(inp));则是合法的;
B.如果第一部分的"instruction list"为空,则input、output、Clobber/Modify可以为空,也可以不为空;比如,语句__asm__("":::"memory");和语句__asm__(""::);都是合法的写法;
C.如果Output、Input和Clobber/Modify都为空,那么,Output、Input之前的冒号(:)可以省略,也可以不省略;如果都省略,则此汇编就退化为一个基本汇编,否则,仍然是一个带有C/C++表达式的内联汇编,此时"instruction list"中的寄存器的写法要遵循相关规定,比如:寄存器名称前面必须使用两个百分号(%%);基本内联汇编中的寄存器名称前面只有一个百分号(%);比如,语句__asm__("movl %%eax,%%ebx"::);__asm__("movl %%eax,%%ebx":);和语句__asm__("movl %%eax,%%ebx");都是正确的写法,而语句__asm__("movl %eax,%ebx"::);__asm__("movl %eax,%ebx":);和语句__asm__("movl %%eax,%%ebx");都是错误的写法;
D.如果Input、Clobber/Modify为空,但Output不为空,则,Input前面的冒号(:)可以省略,也可以不省略;比如,语句__asm__("movl %%eax,%%ebx":"=b"(foo):);和语句__asm__("movl %%eax,%%ebx":"=b"(foo));都是正确的;
E.如果后面