内核代码绝大部分使用C语言编写,只有一小部分使用汇编语言编写,例如与特定体系结构相关的代码和对性能影响很大的代码。GCC提供了内嵌汇编的功能,可以在C代码中直接内嵌汇编语言语句,大大方便了程序设计。
一、基本内嵌汇编 
  GCC提供了很好的内嵌汇编支持,最基本的格式是:__asm__ __volatile__(汇编语句模板);
1、asm  
  __asm__是GCC关键字asm的宏定义:#define __asm__ asm__asm__或asm用来声明一个内嵌汇编表达式,所以任何一个内嵌汇编表达式都是以它开头的,是必不可少的。
2、汇编语句模板 
 “汇编语句模板”是一组插入到C程序中的汇编指令(可以是单个指令,也可以是一组指令)。每条指令都应该由双引号括起,或者整组指令应该由双引号括起。每条指令还应该用一个定界符结尾。有效的定界符为换行符(\n)和分号(;)。\n后可以跟一个制表符(\t)作为格式化符号,增加GCC在汇编文件中生成的指令的可读性。
  上述原则可以归结为:
1 
2 
3 
4 
5 
6 
7 
__asm__(".align 2\n\t"
 "movl %eax, %ebx\n\t"
 "test %ebx, %ecx\n\t"
 "jne error\n\t"
 "sti\n\t"
 "error: popl %edi\n\t"
 "subl %ecx, %ebx"); 
建议大家都使用这种格式来写内嵌汇编代码。
3、volatile  
  __volatile__是GCC关键字volatile的宏定义:#define __volatile__ volatile__volatile__或volatile是可选的。如果不想让GCC的优化改动你的内嵌汇编代码,你最好在前面都加上__volatile__。
二、带C语言表达式的内嵌汇编 
  在内嵌汇编中,可以将C语言表达式指定为汇编指令的操作数,而且不用去管如何将C语言表达式的值读入哪个寄存器,以及如何将计算结果写回C变量,你只要告诉程序中C语言表达式与汇编指令操作数之间的对应关系即可,GCC会自动插入代码完成必要的操作。
  通常嵌入到C代码中的汇编语句很难做到与其它部分没有任何关系,因此更多时候需要用到扩展的内嵌汇编格式:__asm__ __volatile__(汇编语句模板 : 输出部分 : 输入部分 : 破坏描述部分);
  内嵌汇编表达式包含4个部分,各部分由“:”分隔。这4个部分都不是必须的,任何一个部分都可以为空,其规则为:__asm__("mov %%eax, %%ebx" : :);。
②如果“汇编语句模板”为空,则“输出部分”,“输入部分”以及“破坏描述部分”可以不为空,也可以为空。比如:__asm__("" : : : "memory");。
③如果“输出部分”,“输入部分”以及“破坏描述部分”都为空,“输出部分”和“输入部分”之前的“:”既可以省略,也可以不省略。如果都省略,则此汇编退化为一个基本内嵌汇编,否则,仍然是一个带有C语言表达式的内嵌汇编。__asm__("mov %%eax, %%ebx" :)__asm__("mov %eax, %ebx")__asm__("mov %eax, %ebx" :)__asm__("mov %%eax, %%ebx")
1、内嵌汇编举例 
  使用内嵌汇编,要先编写汇编语句模板,然后将C语言表达式与指令的操作数相关联,并告诉GCC对这些操作有哪些约束条件。例如在下面的汇编语句:__asm__("movl %1, %0" : "=r"(result) : "m"(input));__asm__可以用作汇编指令和包含它的C程序之间的接口。
2、汇编语句模板 
◆操作数
◆占位符[name] "constraint"(C expression)
  声明name后,使用%[name]的形式替换内嵌汇编代码中相应的数字型占位符。如下面所示:
1 
2 
3 
__asm__("cmoveq %1, %2, %[result]"
 : [result] "=r"(result)
 : "r"(test), "r"(new), "[result]"(old)); 
  在内嵌汇编中使用占位符表示的操作数,总被视为long型(4个字节) ,但对其施加的操作根据指令可以是字或者字节,当把操作数当作字或者字节使用时,默认为低字或者低字节。对字节操作可以显式的指明是低字节还是高字节。方法是在%和序号之间插入一个字母,“b”代表低字节,“h”代表高字节,例如:%h1。
必须使用占位符的情况:
我们看一看下面这个例子:
1 
2 
3 
__asm__("addl %1, %0"
 : "=a"(out)
 : "m"(in1), "a"(in2)); 
①首先,我们看一看上例中的第1个输入操作表达式"m"(in1),它被GCC替换之后,表现为addl address_of_in1, %%eax,in1的地址是什么?编译时才知道。所以我们完全无法直接在指令中去写出in1的地址,这时使用占位符,交给GCC在编译时进行替代,就可以解决这个问题。所以这种情况下,我们必须使用占位符。
3、输出部分 
  “输出部分”用来指定当前内嵌汇编语句的输出。我们看一看这个例子:__asm__("movl %%cr0, %0" : "=a"(cr0));
4、输入部分 
  “输入部分”的内容用来指定当前内嵌汇编语句的输入。我们看一看这个例子:__asm__("movl %0, %%db7" : : "a"(cpu->db7));
1 
2 
3 
__asm__("movl %0, %%db7" : : "a"(foo));
 __asm__("movl %0, %%db7" : : "a"(0x1000));
 __asm__("movl %0, %%db7" : : "a"(x*y/z)); 
  双引号中的部分是约束部分,和输出操作表达式约束不同的是,它不允许指定加号(+)约束和等号(=)约束,也就是说它只能是默认的Read-Only的。约束中必须指定一个寄存器约束,例中的"a"表示当前输入变量cpu->db7要通过寄存器%eax输入到当前内嵌汇编中。
5、操作约束 
  前面提到过,在内嵌汇编中的每个操作数都应该由操作数约束字符串描述,后面跟着用圆括号括起来的C语言表达式。操作数约束主要是确定指令中操作数的寻址方式。约束也可以指定:
  约束字符必须与指令对操作数的要求相匹配,否则产生的汇编代码将会有错,在这个例子中:__asm__("movl %1,%0" : "=r"(result) : "r"(input));
  每一个输入和输出操作表达式都必须指定自己的操作约束,下面是在80x86平台上可能使用的操作约束:__asm__("movl %0, %%cr0" : : "eax"(cr0));__asm__("movl %0, %%cr0" : : "a"(cr0));
1 
2 
unsigned short shrt;
 __asm__("mov %0,%%bx" : : "a"(shrt)); 
由于变量shrt是16-bit short类型,则编译出来的汇编代码中,会让此变量使用%ax寄存器。
◆内存约束__asm__("lidt %0" : "=m"(idt_addr));
◆立即数约束__asm__("movl %0, %%eax" : : "i"(100));
◆匹配约束
  在某些情况下,一个变量既要充当输入操作数,也要充当输出操作数。可以通过使用匹配约束在内嵌汇编中的“输入部分”指定这种情况。__asm__("incl %0" : "=a"(var) : "0"(var));
该约束可以用于以下情况:
  i386指令集中许多指令的操作数是读写型的,例如:__asm__("addl %1, %0" : "=r"(result) : "r"(input));__asm__("addl %2, %0" : "=r"(result) : "r"(result), "m"(input));__asm__("addl %2,%0" : "=r"(result) : "0"(result), "m"(input));
1 
2 
3 
4 
5 
6 
7 
8 
9 
  movl $0, _result
   movl $1, _input
   movl _result, %edx
   movl %edx, %eax
 #APP
   addl _input, %eax
 #NO_APP
   movl %eax, %edx
   movl %edx, _result 
可以看到与result相关的寄存器是%edx,在执行指令addl之前先从%edx将result读入%eax,执行之后需要将结果从%eax读入%edx,最后存入result中。这里我们可以看出GCC处理内嵌汇编中输出操作数的一点点信息:addl并没有使用%edx,可见它不是简单的用result对应的寄存器%edx去替换%0,而是先分配一个寄存器,执行运算,最后才将运算结果存入对应的变量,因此GCC是先看该占位符对应的变量的约束符,发现是一个输出型寄存器变量,就为它分配一个寄存器,此时没有去管对应的C变量,最后GCC知道还要将寄存器的值写回变量,与此同时,它发现该变量与%edx关联,因此先存入%edx,再存入变量。
  在新版本的GCC中增加了一个约束字符“+”,它表示操作数是读写型的,GCC知道应将变量值先读入寄存器,然后计算,最后写回变量,而无需在输入部分再去描述该变量。__asm__("addl %1, %0" : "+r"(result) : "m"(input));
1 
2 
3 
4 
5 
6 
7 
8 
9 
  movl $0,_result
   movl $1,_input
   movl _result,%eax
 #APP
   addl _input,%eax
 #NO_APP
   movl %eax,_result
 L2:
   movl %ebp,%esp 
处理的比使用匹配约束符的情况还要好,省去了好几条汇编代码。
◆修饰符
  等号(=)和加号(+)用于对输出操作表达式的修饰,一个输出操作表达式要么被等号(=)修饰,要么被加号(+)修饰,二者必居其一。使用等号(=)说明此输出操作表达式是Write-Only的,使用加号(+)说明此输出操作表达式是Read-Write的。它们必须是输出操作表达式约束字符串中的第一个字符。比如:"a=“(var)是非法的,而”+g"(var)则是合法的。__asm__("incl %0" : "+a"(var));__asm__("incl %0" : "=a"(var) : "0"(var));
  像等号(=)和加号(+)修饰符一样,符号(&)也只能用于对输出操作表达式的修饰。__asm__("call foo; movl %%edx, %1" : "=a"(ret) : "r"(bar));
1 
2 
3 
4 
5 
6 
  movl bar, %eax
 #APP
   call foo
   movl %ebx, %eax
 #NO_APP
   movl %eax, ret 
结果显然不对,原因是GCC并不知道%eax中的值是我们所要的。避免这种情况的方法是使用“&”修饰符,这样bar就不会再使用%eax寄存器,因为已被ret指定使用。__asm__("call foo; movl %%edx, %1" : "=&a"(ret) : "r"(bar));
6、破坏描述部分 
  有时在进行某些操作时,除了要用到进行数据输入和输出的寄存器外,还要使用多个寄存器来保存中间计算结果,这样就难免会破坏原有寄存器的内容。如果希望GCC在编译时能够将这一点考虑进去。那么你就可以在“破坏描述部分”声明这些寄存器或内存。
  这种情况一般发生在一个寄存器出现在“汇编语句模板”,但却不是由输入或输出操作表达式所指定的,也不是在一些输入或输出操作表达式使用"r"、"g"约束时由GCC为其选择的,同时此寄存器被“汇编语句模板”中的指令修改,而这个寄存器只是供当前内嵌汇编临时使用的情况。比如:__asm__("movl %0, %%ebx" : : "a"(foo) : "%ebx");
  因为你在输入或输出操作表达式所指定的寄存器,或当你为一些输入或输出操作表达式使用"r"、"g"约束,让GCC为你选择一个寄存器时,GCC对这些寄存器是非常清楚的——它知道这些寄存器是被修改的,你根本不需要在“破坏描述部分”再声明它们。但除此之外,GCC对剩下的寄存器中哪些会被当前的内嵌汇编修改一无所知。所以如果你真的在当前内嵌汇编语句中修改了它们,那么就最好“破坏描述部分”中声明它们,让GCC针对这些寄存器做相应的处理。否则有可能会造成寄存器的不一致,从而造成程序执行错误。
  在“破坏描述部分”中指定这些寄存器的方法很简单,你只需要将寄存器的名字使用双引号引起来。如果有多个寄存器需要声明,你需要在任意两个声明之间用逗号隔开。比如:__asm__("movl %0, %%ebx; popl %%ecx" : : "a"(foo) : "%ebx", "%ecx" );__asm__("movl %0, %%ebx" : : "a"(foo) : "%eax", "%ebx");
  除了寄存器的内容会被改变,内存的内容也可以被修改。如果一个“汇编语句模板”中的指令对内存进行了修改,或者在此内嵌汇编出现的地方内存内容可能发生改变,而被改变的内存地址你没有在其输出操作表达式使用"m"约束,这种情况下你需要在“破坏描述部分”使用字符串"memory"向GCC声明:“在这里,内存发生了或可能发生了改变”。例如:
1 
2 
3 
4 
5 
6 
7 
8 
9 
10 
11 
void * memset(void * s, char c, size_t count)
 {
 	__asm__("cld\n\t"
 	"rep\n\t"
 	"stosb"
 	: /* no output */
 	: "a"(c), "D"(s), "c"(count)
 	: "%ecx", "%edi", "memory");
 
 	return s;
 } 
  此例实现了标准函数库memset,其内嵌汇编中的stosb对内存进行了改动,而其被修改的内存地址s被指定装入%edi,没有任何输出操作表达式使用了"m"约束,以指定内存地址s处的内容发生了改变。所以在其“破坏描述部分”使用"memory"向GCC声明:内存内容发生了变动。
  如果一个内嵌汇编语句的“破坏描述部分”存在"memory",那么GCC会保证在此内嵌汇编之前,如果某个内存的内容被装入了寄存器,那么在这个内嵌汇编之后,如果需要使用这个内存处的内容,就会直接到这个内存处重新读取,而不是使用被存放在寄存器中的拷贝。因为这个时候寄存器中的拷贝已经很可能和内存处的内容不一致了。
  当一个“汇编语句模板”中包含影响eflags寄存器中的条件标志,那么需要在“破坏描述部分”中使用"cc"来声明这一点。这些指令包括adc,div,popfl,btr,bts等等,另外,当包含call指令时,由于你不知道你所call的函数是否会修改条件标志,为了稳妥起见,最好也使用"cc"。