KVM模块中切入Guest模式的代码使用GCC的内联汇编编写,为了理解这段代码,我们需要简要地介绍一下这段内联汇编涉及的语法,其基本语法模板如下:
- asm volatile ( assembler template
- : output operands /* optional */
- : input operands /* optional */
- : list of clobbered registers /* optional */
- );
1. 关键字asm和volatile
asm为GCC关键字,表示接下来要嵌入汇编代码,如果asm与程序中其他命名冲突,可以使用__asm__。
volatile为可选关键字,表示不需要GCC对下面的汇编代码做任何优化,类似的,GCC也支持__volatile__。
2. 汇编指令(assembler template)
这部分即要嵌入的汇编指令,由于是在C语言中内联汇编代码,因此须用双引号将命令括起来。如果内嵌多行汇编指令,则每条指令占用1行,每行指令使用双引号括起来,以后缀\n\t结尾,其中\n为newline的缩写,\t为tab的缩写。由于GCC将每条指令以字符串的形式传递给汇编器AS,所以我们使用\n\t分隔符来分隔每一条指令,示例代码如下:
- __asm__ ("movl %eax, %ebx \n\t"
- "movl $56, %esi \n\t"
- "movl %ecx, $label(%edx,%ebx,$4) \n\t"
- "movb %ah, (%ebx) \n\t");
当使用扩展模式,即包含output、input和clobber list部分时,汇编指令中需要使用两个“%”来引用寄存器,比如%%rax;使用一个“%”来引用输入、输出操作数,比如%1,以便帮助GCC区分寄存器和由C语言提供的操作数。
3. 输出操作数(output operands)
内联汇编有零个或多个输出操作数,用来指示内联汇编指令修改了C代码中的变量。如果有多个输出参数,则需要对每个输出参数进行分隔。每个输出操作数的格式为:
- [[asmSymbolicName]] constraint (cvariablename)
我们可以为输出操作数指定一个名字asmSymbolicName,汇编指令中可以使用这个名字引用输出操作数。
除了使用名字引用操作数外,还可以使用序号引用操作数。比如输出操作数有两个,那么可以用%0引用第1个输出操作数,%1引用第2个操作数,以此类推。
输出操作数的约束部分必须以“=”或者“+”作为前缀,“=”表示只写,“+”表示读写。在前缀之后,就可以是各种约束了,比如“=a”表示先将结果输出至rax/eax寄存器,然后再由rax/eax寄存器更新相应的输出变量。
cvariablename为代码中的C变量名字,需要使用括号括起来。
4. 输入操作数(input operands)
内联汇编可以有零个或多个输入操作数,输入操作数来自C代码中的变量或者表达式,作为汇编指令的输入,每个输入操作数的格式如下:
- [[asmSymbolicName]] constraint (cexpression)
同输出操作数相同,也可以为每个输入操作数指定名字asmSymbolicName,汇编指令中可以使用这个名字引用输入操作数。
除了使用名字引用输入操作数外,还可以使用序号引用输入操作数。输入操作数的序号以最后一个输出操作数的序号加1开始,比如输出操作数有两个,输入操作数有3个,那么需要使用%2引用第1个输入操作数,%3引用第2个输入操作数,以此类推。
除了不必以“=”或者“+”前缀开头外,输入操作数的前缀与输出操作数基本相同。除了寄存器约束外,在后面的代码中我们还会看到“i”这个约束,表示这个输入操作数是个立即数(immediate integer)。
cexpression为代码中的C变量或者表达式,需要使用括号括起来。