查看“Inline Assembly”的源代码
←
Inline Assembly
跳到导航
跳到搜索
因为以下原因,您没有权限编辑本页:
您请求的操作仅限属于该用户组的用户执行:
用户
您可以查看和复制此页面的源代码。
“内联汇编”背后的思想是在除使用[[Assembly|汇编]]语言之外别无选择的情况下,使用<tt>asm</tt>关键字在C/C++代码中嵌入汇编指令。 == 概述 == 有时,即使C/C++是您选择的语言,您也“需要”在操作系统中使用一些汇编代码。 无论是因为极端的优化需求,还是因为您正在实现的代码是高度特定于硬件的(比如说,通过端口输出数据),结果都是一样的:没有办法绕过它。 你必须要使用汇编语言。 您可以选择编写一个汇编函数并调用它,但是有时甚至“调用”开销对您来说都太大了。 在这种情况下,您需要的是内联汇编技术,这意味着使用<tt>asm()</tt>关键字插入代码中间的任意程序集片段。 这个关键字的工作方式是特定于编译器的。 本文描述了它在GCC中的工作方式,因为它是操作系统世界中使用最多的编译器。 == 语法 == 这是在C/C++代码中使用<tt>asm()</tt>关键字的语法: <source lang="c"> asm ( assembler template : output operands (optional) : input operands (optional) : clobbered registers list (optional) ); </source> 汇编程序模板基本上是[[GAS]]兼容的代码,除非有限制,在这种情况下,寄存器名必须以%%而不是%开头。 这意味着以下两行都是将<tt>eax</tt>寄存器的内容移动到<tt>ebx</tt>的代码: <source lang="c"> asm ("movl %eax, %ebx"); asm ("movl %%eax, %%ebx" : ); </source> 现在,您可能想知道为什么这%%会出现。 这是因为内联汇编的一个有趣特性:您可以在汇编代码中使用一些C变量。 因为,为了简化该机制的实现,GCC在汇编代码中命名这些变量%0、%1等等,从输入/输出操作数部分中提到的第一个变量开始。 您需要使用此%%语法来帮助GCC区分寄存器和参数。 该操作的具体工作原理将在后面的章节中详细解释。 现在,下面这个例子可以先提供一个演示: <source lang="c"> int a=10, b; asm ("movl %1, %%eax; movl %%eax, %0;" :"=r"(b) /* output */ :"r"(a) /* input */ :"%eax" /* clobbered register */ ); </source> 这里您已经设法使用汇编代码将“a”的值复制到“b”中,在汇编代码中有效地使用了一些C变量。 最后一个“clobbered register”部分用于告诉GCC您的代码正在使用处理器的一些寄存器,并且在执行asm代码段之前,它应该将正在运行的程序中的所有活动数据移出该寄存器。 在上面的例子中,我们在第一条指令中将<tt>a</tt>移动到eax,有效地擦除了它的内容,因此我们需要要求GCC在操作之前从未保存的数据中清除该寄存器。 === 汇编程序模板 === 汇编器模板定义要内联的汇编器指令。 默认情况下,此处使用AT&T语法。 如果要使用Intel语法,应将-masm=Intel</tt>指定为命令行选项。 例如,要停止CPU,只需使用以下命令: <source lang="c"> asm( "hlt" ); </source> === 输出Operands === 本节“输出Operands”部分用于告诉编译器/汇编程序如何处理存储ASM代码输出的C变量。 输出Operands是一组Pairs,每个Operands由一个字符串文字组成,称为“constraint”,说明C变量应该映射到哪里(寄存器通常用于最佳性能),以及要映射到哪里的C变量(在括号中)。 假设您正在为IA32体系结构编码,在constraint中,“a”表示EAX,“b”表示EBX,“c”表示ECX,“d”表示EDX,“S”表示ESI,“d”表示EDI(请阅读GCC手册中的完整列表)。 等式符号表示汇编代码不关心映射变量的初始值(这允许一些优化)。 考虑到所有这些,现在下面的代码将EAX设置为0就很清楚了。 <source lang="c"> int EAX; asm( "movl $0, %0" : "=a" (EAX) ); </source> 请注意,编译器将枚举以%0开头的操作数,如果将寄存器用于存储输出操作数,则无需将其添加到已删除的寄存器列表中。 GCC足够聪明,能够自己决定做什么。 从GCC 3.1开始,您可以使用更可读的标签,而不是容易出错的枚举: <source lang="c"> int current_task; asm( "str %[output]" : [output] "=r" (current_task) ); </source> 这些标签位于它们自己的名称空间中,不会与任何C标识符冲突。 对于输入Operands也可以这样做。 === 输入Operands === 虽然输出Operands通常只用于输出,输入操作数还允许参数化ASM代码; 将只读参数从C代码传递到ASM块。 同样,字符串文字用于指定详细信息。 如果您想将某个值移动到EAX,可以通过以下方式执行(即使这样做,而不是直接将该值映射到EAX肯定是毫无用处的): <source lang="c"> int randomness = 4; asm( "movl %0, %%eax" : : "b" (randomness) : "eax" ); </source> 请注意,GCC将始终假定输入Operands是只读的(不更改的)。 写入输入Operands时,正确的做法是将它们列为输出,但不使用等式符号,因为这一次它们的原始值很重要。 下面是一个简单的例子: <source lang="c"> asm("mov %%eax,%%ebx": : "a" (amount));//useless but it gets the idea </source> Eax将包含“amount”,并移动到ebx中。 === 删除寄存器列表 === 请记住一件事很重要:“C/C++编译器对汇编器一无所知”。 对于编译器来说,asm语句是不透明的,如果您没有指定任何输出,它甚至可能得出结论,认为这是一个不可操作的语句,并对其进行优化。 一些第三方文档指出,使用asm volatile将导致关键字不被移动。 然而,根据GCC文档,“volatile关键字表示指令有重要的副作用。 如果可以访问易失性asm,GCC将不会删除该asm。'',这只表示它不会被删除 (也就是说,它是否仍然可以移动是一个尚未回答的问题)。 一种可行的方法是使用asm(volatile)并将“内存”放入缓冲寄存器中,如下所示: <source lang="c"> __asm__("cli": : :"memory"); // 将导致语句不移动,但可能会将其优化。 __asm__ __volatile__("cli": : :"memory"); // 将导致语句不会被移动或优化。 </source> 由于编译器使用CPU寄存器对C/C++变量进行内部优化,并且不知道ASM操作码, 您必须警告它,任何寄存器可能会因为副作用而被破坏, 因此,编译器可以在进行ASM调用之前保存它们的内容。 Clobbered Registers列表是一个以逗号分隔的寄存器名列表,以字符串文本形式显示。 === 通配符:如何让编译器选择 === 您不需要告诉编译器在每个操作中应该使用哪个特定寄存器,一般来说 除非你有充分的理由特别喜欢一个寄存器, 你最好让编译器替你决定。 例如,强制在任何其他寄存器上使用EAX可能会迫使编译器代码报错,将以前在EAX中的内容保存在其他寄存器中,或者可能会在操作之间引入不必要的依赖关系(管道优化被破坏) 通配符constraints允许您在输入/输出映射方面给予GCC更多自由: {| {{wikitable}} |- | The "g" constraint : <source lang="c">"movl $0, %0" : "=g" (x)</source> | x can be whatever the compiler prefers: a register, a memory reference. It could even be a literal constant in another context. |- | The "r" constraint : <source lang="c">"movl %%es, %0" : "=r" (x)</source> | you want x to go through a register. If x wasn't optimized as a register, the compiler will then move it to the place it should be. This means that <code>"movl %0, %%es" : : "r" (0x38)</code> is enough to load a segment register. |- | The "N" constraint : <source lang="c">"outl %0, %1" : : "a" (0xFE), "N" (0x21)</source> | tells the value '0x21' can be used as a constant in the out or in operation if ranging from 0 to 255 |} 当然,您可以对operand选择施加更多约束,无论是否依赖于机器,这些constraints都列在GCC的手册中 (see [http://gcc.gnu.org/onlinedocs/gcc-4.4.4/gcc/Simple-Constraints.html#Simple-Constraints], [http://gcc.gnu.org/onlinedocs/gcc-4.4.4/gcc/Modifiers.html#Modifiers], [http://gcc.gnu.org/onlinedocs/gcc-4.4.4/gcc/Multi_002dAlternative.html#Multi_002dAlternative], and [http://gcc.gnu.org/onlinedocs/gcc-4.4.4/gcc/Machine-Constraints.html#Machine-Constraints]). == 使用C99 == 当使用<tt>gcc-std=c99</tt>时,<tt>asm</tt>不是关键字。 只需使用<tt>gcc -std=gnu99</tt>即可使用带有GNU扩展的C99。 或者,您可以使用 <tt >__asm__</tt> 作为备用关键字,即使编译器严格遵守标准,该关键字也可以使用。 == 分配标签 == 可以将所谓的ASM标签指定给C/C++关键字。 您可以通过对变量定义使用<tt>asm</tt>命令来执行此操作,如本例所示: <source lang="c"> int some_obscure_name asm("param") = 5; //“param”将可在内联程序集中访问。 void foo() { asm("mov param, %%eax"); } </source> 下面是一个示例,说明如果不显式声明名称,如何访问这些变量: <source lang="c"> int some_obscure_name = 5; void foo() { asm("mov some_obscure_name, %%eax"); } </source> 请注意,根据您的链接选项,您可能还必须使用'''_some_obscure_name'''(带前导下划线)。 == asm goto == 在GCC4.5之前,不支持跨内联汇编跳转。编译器无法跟踪正在发生的事情, 因此,只能生成错误的代码。 <br>你可能会被告知“gotos是邪恶的”。 如果你相信这是真的,那么asm gotos就是你最可怕的噩梦。 然而,它们确实提供了一些有趣的代码优化选项。 asm goto没有很好的文档记录,但其语法如下: <source lang="c"> asm goto( "jmp %l[labelname]" : /* no outputs */ : /* inputs */ : "memory" /* clobbers */ : labelname /* any labels used */ ); </source> CMPXCHG指令就是一个很有用的例子(参见[http://en.wikipedia.org/wiki/Compare-and-swap Compare and Swap),Linux内核源代码对其定义如下: <source lang="c"> /* TODO: You should use modern GCC atomic instruction builtins instead of this. */ #include <stdint.h> #define cmpxchg( ptr, _old, _new ) { \ volatile uint32_t *__ptr = (volatile uint32_t *)(ptr); \ uint32_t __ret; \ asm volatile( "lock; cmpxchgl %2,%1" \ : "=a" (__ret), "+m" (*__ptr) \ : "r" (_new), "0" (_old) \ : "memory"); \ ); \ __ret; \ } </source> 除了返回EAX中的当前值,CMPXCHG在成功时设置零标志(Z)。 如果没有ASM GOTOS,您的代码将必须检查返回值;可以按如下方式避免此CMP指令: <source lang="c"> /* TODO: You should use modern GCC atomic instruction builtins instead of this. */ // Works for both 32 and 64 bit #include <stdint.h> #define cmpxchg( ptr, _old, _new, fail_label ) { \ volatile uint32_t *__ptr = (volatile uint32_t *)(ptr); \ asm goto( "lock; cmpxchg %1,%0 \t\n" \ "jnz %l[" #fail_label "] \t\n" \ : /* empty */ \ : "m" (*__ptr), "r" (_new), "a" (_old) \ : "memory", "cc" \ : fail_label ); \ } </source> 然后可以按如下方式使用此新宏: <source lang="c"> struct Item { volatile struct Item* next; }; volatile struct Item *head; void addItem( struct Item *i ) { volatile struct Item *oldHead; again: oldHead = head; i->next = oldHead; cmpxchg( &head, oldHead, i, again ); } </source> == Intel语法 == 通过在内联汇编中启用选项,您可以让GCC使用Intel语法,如下所示: <source lang="c"> asm(".intel_syntax noprefix"); asm("mov eax, ebx"); </source> 类似地,您可以使用以下代码段切换回AT&T语法: <source lang="c"> asm(".att_syntax prefix"); asm("mov %ebx, %eax"); </source> 通过这种方式,您可以将Intel语法和AT&T语法内联汇编结合起来。 请注意,一旦触发其中一种语法类型,源文件中命令下面的所有内容都将使用此语法进行汇编, 因此,在必要时不要忘记切换回来,否则可能会出现大量编译错误! 还有一个命令行选项<tt>-masm=intel</tt>,用于全局触发Intel语法。 == 另见 == ===文章=== * [[Inline Assembly/Examples]] - 有用且常用的函数 === 论坛主题 === * [http://forum.osdev.org/viewtopic.php?f=11&t=24168&p=196655&hilit=asm+volatile+moved asm volatile being moved] === 外部链接 === * [http://gcc.gnu.org/onlinedocs/ GCC Manuals] * [http://web.archive.org/web/20041210030000/http://www-106.ibm.com/developerworks/library/l-ia.html Inline assembly for x86 in Linux (by IBM)] * [http://msdn.microsoft.com/en-us/library/26td21ds(VS.80).aspx Visual C++ Compiler Intrinsics] [[Category:Assembly]] [[de:Inline-Assembler_mit_GCC]]
本页使用的模板:
模板:Wikitable
(
查看源代码
)
返回至“
Inline Assembly
”。
导航菜单
个人工具
登录
命名空间
页面
讨论
变体
已展开
已折叠
查看
阅读
查看源代码
查看历史
更多
已展开
已折叠
搜索
导航
首页
最近更改
随机页面
MediaWiki帮助
工具
链入页面
相关更改
特殊页面
页面信息