Board logo

标题: Linux 中 x86 的内联汇编 将各个部分组合起来-1 [打印本页]

作者: look_w    时间: 2018-4-15 18:53     标题: Linux 中 x86 的内联汇编 将各个部分组合起来-1

如果您是 Linux内核的开发人员,您会发现自己经常要对与体系结构高度相关的功能进行编码或优化代码路径。您很可能是通过将汇编语言指令插入到C语句的中间(又称为内联汇编的一种方法)来执行这些任务的。让我们看一下Linux 中内联汇编的特定用法。(我们将讨论限制在 IA32 汇编。)
GNU汇编程序简述让我们首先看一下 Linux 中使用的基本汇编程序语法。GCC(用于 Linux 的GNU C 编译器)使用 AT&T汇编语法。下面列出了这种语法的一些基本规则。(该列表肯定不完整;只包括了与内联汇编相关的那些规则。)
寄存器命名
寄存器名称有 % 前缀。即,如果必须使用 eax,它应该用作 %eax。      
源操作数和目的操作数的顺序
在所有指令中,先是源操作数,然后才是目的操作数。这与将源操作数放在目的操作数之后的Intel 语法不同。      
1
mov %eax, %ebx, transfers the contents of eax to ebx.




操作数大小
根据操作数是字节 (byte)、字 (word) 还是长型(long),指令的后缀可以是 b、w 或 l。这并不是强制性的;GCC会尝试通过读取操作数来提供相应的后缀。但手工指定后缀可以改善代码的可读性,并可以消除编译器猜测不正确的可能性。      
1
2
3
movb %al, %bl -- Byte move
    movw %ax, %bx -- Word move
    movl %eax, %ebx -- Longword move




立即操作数
通过使用 $ 指定直接操作数。      
1
movl $0xffff, %eax -- will move the value of 0xffff into eax register.




间接内存引用
任何对内存的间接引用都是通过使用 ( ) 来完成的。      
1
2
3
movb (%esi), %al -- will transfer the byte in the memory
pointed by esi into al
register




内联汇编GCC 为内联汇编提供特殊结构,它具有以下格式:
GCG 的 "asm" 结构
1
2
3
4
5
6
7
8
9
10
   asm ( assembler template
     
: output operands               (optional)
     
: input operands                (optional)
     
: list of clobbered registers   
    (optional)
     
);




本例中,汇编程序模板由汇编指令组成。输入操作数是充当指令输入操作数使用的C 表达式。输出操作数是将对其执行汇编指令输出的 C 表达式。
内联汇编的重要性体现在它能够灵活操作,而且可以使其输出通过 C变量显示出来。因为它具有这种能力,所以 "asm"可以用作汇编指令和包含它的 C 程序之间的接口。
一个非常基本但很重要的区别在于         简单内联汇编只包括指令,而        扩展内联汇编包括操作数。要说明这一点,考虑以下示例:      
内联汇编的基本要素
1
2
3
4
5
6
7
8
9
{
    int a=10, b;
    asm ("movl %1, %%eax;
     
movl %%eax, %0;"
        :"=r"(b)  /* output */   
        :"r"(a)       /* input */
        :"%eax"); /* clobbered register */
}




在上例中,我们使用汇编指令使 "b" 的值等于"a"。请注意以下几点:
现在让我们更详细的了解每一项的含义。
汇编程序模板汇编程序模板是一组插入到 C程序中的汇编指令(可以是单个指令,也可以是一组指令)。每条指令都应该由双引号括起,或者整组指令应该由双引号括起。每条指令还应该用一个定界符结尾。有效的定界符为新行(\n) 和分号 (;)。 '\n' 后可以跟一个 tab(\t) 作为格式化符号,增加GCC 在汇编文件中生成的指令的可读性。 指令通过数 %0、%1 等来引用 C表达式(指定为操作数)。
如果希望确保编译器不会在 "asm" 内部优化指令,可以在 "asm"后使用关键字 "volatile"。如果程序必须与 ANSI C 兼容,则应该使用__asm__ 和 __volatile__,而不是 asm 和 volatile。
操作数C 表达式用作 "asm" 内的汇编指令操作数。在汇编指令通过对 C 程序的 C表达式进行操作来执行有意义的作业的情况下,操作数是内联汇编的主要特性。
每个操作数都由操作数约束字符串指定,后面跟用括弧括起的 C表达式,例如:"constraint" (Cexpression)。操作数约束的主要功能是确定操作数的寻址方式。
可以在输入和输出部分中同时使用多个操作数。每个操作数由逗号分隔开。
在汇编程序模板内部,操作数由数字引用。如果总共有        n个操作数(包括输入和输出),那么第一个输出操作数的编号为0,逐项递增,最后那个输入操作数的编号为        n -1。总操作数的数目限制在10,如果机器描述中任何指令模式中的最大操作数数目大于10,则使用后者作为限制。      
修饰寄存器列表如果 "asm" 中的指令指的是硬件寄存器,可以告诉 GCC我们将自己使用和修改它们。这样,GCC就不会假设它装入到这些寄存器中的值是有效值。通常不需要将输入和输出寄存器列为clobbered,因为 GCC 知道 "asm"使用它们(因为它们被明确指定为约束)。不过,如果指令使用任何其它的寄存器,无论是明确的还是隐含的(寄存器不在输入约束列表中出现,也不在输出约束列表中出现),寄存器都必须被指定为修饰列表。修饰寄存器列在第三个冒号之后,其名称被指定为字符串。
至于关键字,如果指令以某些不可预知且不明确的方式修改了内存,则可能将"memory" 关键字添加到修饰寄存器列表中。这样就告诉 GCC不要在不同指令之间将内存值高速缓存在寄存器中。
操作数约束前面提到过,"asm"中的每个操作数都应该由操作数约束字符串描述,后面跟用括弧括起的 C表达式。操作数约束主要是确定指令中操作数的寻址方式。约束也可以指定:
约束还要求两个操作数匹配。
常用约束在可用的操作数约束中,只有一小部分是常用的;下面列出了这些约束以及简要描述。有关操作数约束的完整列表,请参考GCC 和 GAS 手册。
寄存器操作数约束 (r)
使用这种约束指定操作数时,它们存储在通用寄存器中。请看下例:      
1
asm ("movl %%cr3, %0\n" :"=r"(cr3val));




这里,变量 cr3val 保存在寄存器中,%cr3的值复制到寄存器上,cr3val 的值从该寄存器更新到内存中。指定 "r"约束时,GCC 可以将变量 cr3val 保存在任何可用的 GPR中。要指定寄存器,必须通过使用特定的寄存器约束直接指定寄存器名。
1
2
3
4
5
6
a   %eax
b   %ebx
c   %ecx
d   %edx
S   %esi
D   %edi




内存操作数约束 (m)
当操作数位于内存中时,任何对它们执行的操作都将在内存位置中直接发生,这与寄存器约束正好相反,后者先将值存储在要修改的寄存器中,然后将它写回内存位置中。但寄存器约束通常只在对于指令来说它们是绝对必需的,或者它们可以大大提高进程速度时使用。当需要在"asm" 内部更新 C变量,而您又确实不希望使用寄存器来保存其值时,使用内存约束最为有效。例如,idtr的值存储在内存位置 loc 中:      
1
("sidt %0\n" : :"m"(loc));




匹配(数字)约束
在某些情况下,一个变量既要充当输入操作数,也要充当输出操作数。可以通过使用匹配约束在"asm" 中指定这种情况。      
1
asm ("incl %0" :"=a"(var):"0"(var));




在匹配约束的示例中,寄存器 %eax既用作输入变量,也用作输出变量。将 var 输入读取到%eax,增加后将更新的 %eax 再次存储在 var 中。这里的 "0" 指定第 0个输出变量相同的约束。即,它指定 var 的输出实例只应该存储在 %eax中。该约束可以用于以下情况:
使用匹配约束最重要的意义在于它们可以导致有效地使用可用寄存器。




欢迎光临 电子技术论坛_中国专业的电子工程师学习交流社区-中电网技术论坛 (http://bbs.eccn.com/) Powered by Discuz! 7.0.0