`

嵌入式汇编

 
阅读更多

转自<http://blog.csdn.net/slvher/article/details/8864996>

在阅读Linux内核源码或对代码做性能优化时,经常会有在C语言中嵌入一段汇编代码的需求,这种嵌入汇编在CS术语上叫做inline assembly。本文的笔记试图说明Inline Assembly的基本语法规则和用法(建议英文阅读能力较强的同学直接阅读本文参考资料中推荐的技术文章 ^_^)。
       注意:由于gcc采用AT&T风格的汇编语法(与Intel Syntax相对应,二者的区别参见这里),因此,本文涉及到的汇编代码均以AT&T Syntax为准。
1. 基本语法规则
          内联汇编(或称嵌入汇编)的基本语法模板比较简单,如下所示(为使结构更清晰,这里特意做了换行,其实完全可以全部写到单行中):

  1. asm [ volatile ] (    
  2.         assembler template  
  3.         [ : output operands ]                /* optional */  
  4.         [ : input operands  ]                /* optional */  
  5.         [ : list of clobbered registers ]    /* optional */  
  6.         );  

       备注:本文遵从linux系统的统一风格,以[ ]来表示其对应的内容为可选项。
       由代码模板可以看到,基本语法规则由5部分组成,下面分别进行说明。
       1)关键字asm和volatile
       asm为gcc关键字,表示接下来要嵌入汇编代码。为避免keyword asm与程序中其它部分产生命名冲突,gcc还支持__asm__关键字,与asm的作用等价。
       volatile为可选关键字,表示不需要gcc对下面的汇编代码做任何优化。同样出于避免命名冲突的原因,__volatile__也是gcc支持的与volatile等效的关键字。
       BTW: C语言中也经常用到volatile关键字来修饰变量(不熟悉的同学,请参考这里

       2)assembler template
       这部分即我们要嵌入的汇编命令,由于我们是在C语言中内联汇编代码,故需用双引号""将命令括起来,以便gcc以字符串形式将这些命令传给汇编器AS。例如可以写成这样:"movl %eax, %ebx"
       有时候,汇编命令可能有多个,则通常分多行写,每行的命令都用双引号括起来,命令后紧跟"\n\t"之类的分隔符(当然,也可以只用1对双引号将多行命令括起来,从语法来说,两种写法均有效,我们可自行决定用哪种格式来写)。示例代码如下所示:

  1. __asm__ __volatile__ ( "movl %eax, %ebx\n\t"  
  2.                        "movl %ecx, 2(%edx, %ebx, $8)\n\t"  
  3.                        "movb %ah, (%ebx)"  
  4.                      );  

          还有时候,根据程序上下文,嵌入的汇编代码中可能会出现一些类似于魔数(Magic Number )的操作数,比如下面的代码:

  1. int a=10, b;  
  2. asm ("movl %1, %%eax;   /* NOTICE: 下面会说明此处用%%eax引用寄存器eax的原因 
  3.       movl %%eax, %0;" 
  4.       :"=r"(b)          /* output 该字段的语法后面会详细说明,此处可无视,下同 */  
  5.       :"r"(a)           /* input   */  
  6.       :"%eax"           /* clobbered register */  
  7.     );     

       我们看到,movl指令的操作数(operand)中,出现了%1、%0,这往往让新手摸不着头脑。其实只要知道下面的规则就不会产生疑惑了:
       在内联汇编中,操作数通常用数字来引用,具体的编号规则为:若命令共涉及n个操作数,则第1个输出操作数(the first output operand)被编号为0,第2个output operand编号为1,依次类推,最后1个输入操作数(the last input operand)则被编号为n-1。
       具体到上面的示例代码中,根据上下文,涉及到2个操作数变量a、b,这段汇编代码的作用是将a的值赋给b,可见,a是input operand,而b是output operand,那么根据操作数的引用规则,不难推出,a应该用%1来引用,b应该用%0来引用。
       还需要说明的是:当命令中同时出现寄存器和以%num来引用的操作数时,会以%%reg来引用寄存器(如上例中的%%eax),以便帮助gcc来区分寄存器和由C语言提供的操作数。  
          3)output operands
       该字段为可选项,用以指明输出操作数,典型的格式为:
           : "=a" (out_var)
       其中,"=a"指定output operand的应遵守的约束(constraint),out_var为存放指令结果的变量,通常是个C语言变量。本例中,“=”是output operand字段特有的约束,表示该操作数是只写的(write-only);“a”表示先将命令执行结果输出至%eax,然后再由寄存器%eax更新 位于内存中的out_var。关于常用的约束规则,本文后面会给出说明。
       若输出有多个,则典型格式示例如下:

  1. asm ( "cpuid"  
  2.       : "=a" (out_var1), "=b" (out_var2), "=c" (out_var3)  
  3.       : "a" (op)  
  4.      );  

       可见,我们可以为每个output operand指定其约束。
       4)input operands
       该字段为可选项,用以指明输入操作数,其典型格式为:
               : "constraints" (in_var)
       其中,constraints可以是gcc支持的各种约束方式,in_var通常为C语言提供的输入变量。
       与output operands类似,当有多个input时,典型格式为:
               : "constraints1" (in_var1), "constraints2" (in_var2), "constraints3" (in_var3), ...
       当然,input operands + output operands的总数通常是有限制的,考虑到每种指令集体系结构对其涉及到的指令支持的最多操作数通常也有限制,此处的操作数限制也不难理解。此处具体 的上限为max(10, max_in_instruction),其中max_in_instruction为ISA中拥有最多操作数的那条指令包含的操作数数目。
       需要明确的是,在指明input operands的情况下,即使指令不会产生output operands,其:也需要给出。例如asm ("sidt %0\n" : :"m"(loc)); 该指令即使没有具体的output operands也要将:写全,因为有后面跟着: input operands字段。
       5)list of clobbered registers 
       该字段为可选项,用于列出指令中涉及到的且没出现在output operands字段及input operands字段的那些寄存器。若寄存器被列入clobber-list,则等于是告诉gcc,这些寄存器可能会被内联汇编命令改写。因此,执行内联 汇编的过程中,这些寄存器就不会被gcc分配给其它进程或命令使用。

2. 常用约束(commonly used constraints)
       前面介绍output operands和input operands字段过程中,我们已经知道这些operands通常需要指明各自的constraints,以便更明确地完成我们期望的功能(试想,如果 不明确指定约束而由gcc自行决定的话,一旦代码执行结果不符合预期,调试将变得很困难)。
       下面开始介绍一些常用的约束项。
       1)寄存器操作数约束(register operand constraint, r)
       当操作数被指定为这类约束时,表明汇编指令执行时,操作数被将存储在指定的通用寄存器(General Purpose Registers, GPR)中。例如: 
           asm ("movl %%eax, %0\n" : "=r"(out_val));
       该指令的作用是将%eax的值返回给%0所引用的C语言变量out_val,根据"=r"约束可知具体的操作流程为:先将%eax值复制给任一GPR, 最终由该寄存器将值写入%0所代表的变量中。"r"约束指明gcc可以先将%eax值存入任一可用的寄存器,然后由该寄存器负责更新内存变量。
       通常还可以明确指定作为“中转”的寄存器,约束参数与寄存器的对应关系为:
        a : %eax, %ax, %al
        b : %ebx, %bx, %bl
        c : %ecx, %cx, %cl
        d : %edx, %dx, %dl
        S : %esi, %si
        D : %edi, %di
       例如,如果想指定用%ebx作为中转寄存器,则命令为:asm ("movl %%eax, %0\n" : "=b"(out_val));
       2)内存操作数约束(Memory operand constraint, m)
       当我们不想通过寄存器中转,而是直接操作内存时,可以用"m"来约束。例如:
            asm volatile ( "lock; decl %0" : "=m" (counter) : "m" (counter));
       该指令实现原子减一操作,输入、输出操作数均直接来自内存(也正因如此,才能保证操作的原子性)。    
       3)关联约束(matching constraint)
       在有些情况下,如果命令的输入、输出均为同一个变量,则可以在内联汇编中指定以matching constraint方式分配寄存器,此时,input operand和output operand共用同一个“中转”寄存器。例如:
           asm ("incl %0" :"=a"(var):"0"(var));
        该指令对变量var执行incl操作,由于输入、输出均为同一变量,因此可用"0"来指定都用%eax作为中转寄存器。注意"0"约束修饰的是input operands。
        4)其它约束
        除上面介绍的3中常用约束外,还有一些其它的约束参数(如"o"、"V"、"i"、"g"等),感兴趣的同学可以参考这里

3. 实例剖析
        前面介绍了很多理论性的规则,这里通过分析一个实例来加深对inline assembly的理解。
        下面的代码是Linux内核i386版本中的syscall0定义:

  1. #define _syscall0(type, name)          \  
  2. type name(void)                        \  
  3. {                                      \  
  4.     long __res;                        \  
  5.     __asm__ volatile ( "int $0x80"     \  
  6.       : "=a" (__res)                   \  
  7.       : "0" (__NR_##name));            \  
  8.     __syscall_return(type, __res);     \  
  9. }  

        对于系统调用fork来说,上述宏展开为:

  1. pid_t fork(void)  
  2. {  
  3.     long __res;                         
  4.     __asm__ volatile ( "int $0x80"      
  5.     : "=a" (__res)                    
  6.     : "0" (__NR_fork));             
  7.     __syscall_return(pid_t, __res);      
  8. }  

         根据前面对inline assembly语法及使用方法的说明,我们不难理解这段代码的含义。将这段内联汇编翻译更可读的伪码形式为:

  1. pid_t fork(void)  
  2. {  
  3.     long __res;                         
  4.     %eax = __NR_fork   /* __NR_fork为内核分配给系统调用fork的调用号 */  
  5.     int $0x80          /* 触发中断,内核根据%eax的值可知,引起中断的是fork system call */  
  6.     __res = %eax       /* 中断返回值保持在%eax中 */  
  7.     __syscall_return(pid_t, __res);      
  8. }  

【参考资料】
1. GCC-Inline-Assembly-HOWTO 
2. Inline assembly for x86 in Linux 
3. 《程序员的自我修养—链接、装载与库》,第12章
4. Using Assembly Language in Linux 


=============== EOF ================

分享到:
评论

相关推荐

    嵌入式汇编和c学习资料

    以下是对"嵌入式汇编和C学习资料"主题的详细说明: 1. **嵌入式汇编**:汇编语言是计算机硬件层面的一种编程语言,每个指令都对应一个特定的机器码,可以直接控制处理器的操作。在嵌入式开发中,汇编语言用于编写...

    GCC 入门剖析以及嵌入式汇编

    在GCC中,嵌入式汇编是一种将汇编代码直接插入C语言程序的技术,使得程序员可以利用汇编语言的高效特性处理特定任务,同时保持大部分代码在高级语言层面。这对于嵌入式系统,特别是涉及到硬件交互或者性能敏感的代码...

    Linux汇编语言及嵌入式汇编

    《Linux汇编语言及嵌入式汇编》 在计算机科学领域,汇编语言是一种低级编程语言,它与特定的计算机硬件紧密相关,允许程序员直接控制硬件资源。在Linux系统中,汇编语言虽然不常见,但在某些特定场景下,如系统启动...

    嵌入式汇编技术原理及其应用

    ### 嵌入式汇编技术原理及其应用 #### 引言 在现代软件开发领域,高级语言因其接近人类自然语言、易于理解和维护的特点而受到广泛欢迎,它们能够精确表达复杂的逻辑和数据结构,极大地提高了开发效率。然而,对于...

    wxh 《Delphi嵌入式汇编

    ### Delphi嵌入式汇编知识点解析 #### Delphi与嵌入式汇编简介 Delphi作为一种高级编程语言,以其高效、快速的开发环境而著称。它支持面向对象编程,并且内置了大量的类库和组件,使得开发者可以快速构建Windows...

    ARM嵌入式汇编程序实例排序

    总结,ARM嵌入式汇编程序实例排序涉及了嵌入式系统、汇编语言、源代码编写和算法实现等多个重要知识点。通过深入学习和实践,开发者可以更好地理解和驾驭嵌入式系统的底层运行机制,从而编写出更加高效、优化的代码...

    嵌入式汇编语言学习经验

    嵌入式汇编语言在软件开发中扮演着特殊的角色,特别是在需要高效低级操作或与硬件直接交互的场合。本文作者分享了从Windows环境切换到Linux环境学习和使用嵌入式汇编的经历,主要对比了两种环境下的汇编语言差异。 ...

    嵌入式排序汇编语言

    ### 嵌入式系统中的ARM汇编语言编程 #### 实验名称:ARM汇编语言编程实验 本实验的主要目的是通过编写ARM汇编语言程序来实现特定的功能。ARM汇编语言是一种用于ARM架构处理器的低级编程语言,它可以直接与硬件交互...

    嵌入式汇编资料

    包括两个文件,一个GCC Inline Assembly HOWTO,一个IBM的文档Inline assembly for x86 in Linux Putting the pieces together

    C语言与汇编语言混合编程_嵌入式汇编

    ### C语言与汇编语言混合编程详解:嵌入式应用视角 #### 一、引言 在嵌入式系统开发中,为了实现某些特定功能或提高性能,有时需要结合使用高级语言(如C语言)与低级语言(如汇编语言)。本文将详细介绍如何在...

    嵌入式LED实验报告

    是嵌入式应用开发的指导,Embest S3CEV40开发板是实验系统的主要硬件平台,它是英蓓特公司开发的一款全功能ARM开发板,基Samsung公司的S3C44B0X处理器(ARM7TDMI),资源丰富。硬件系统包含了嵌入式系统开发应用所需的...

    C语言与汇编语言混合编程

    本实验主要介绍了 ARM 嵌入式 C 语言编程的基本方法、嵌入式汇编(Inline Assembly)编程规则和方法,以及汇编语言程序和 C 语言程序之间的相互调用规则和编程方法。 一、ARM 嵌入式 C 语言编程基本方法 ARM ...

    C语言与汇编语言混合编程_32位嵌入式.pdf

    其中,嵌入式汇编(Inline Assembly)允许在C代码中插入汇编指令,实现对硬件的精确控制。同时,理解汇编语言程序和C语言程序之间的调用规则至关重要,这涉及到函数调用的参数传递、返回值处理以及堆栈管理等细节。 ...

    可能对大家有用的嵌入式学习资料

    6. **问答社区**:参与嵌入式汇编的在线论坛和问答平台,如Stack Overflow,能让你遇到问题时得到及时的帮助,并且通过解答他人的问题来巩固自己的知识。 在压缩包中的“嵌入式汇编学习资料与问问”可能包含了上述...

    为什么选择学习嵌入式.pdf

    1. 嵌入式研发方向职业生涯:包括ARM+Linux嵌入式底层内核驱动方向学习总体路线图、Linux基础操作、基本服务配置及使用、Shell编程、C语言、数据结构、嵌入式汇编、嵌入式体系结构等。 2. 嵌入式开发方向职业生涯:...

    汇编语言程序设计

    汇编语言(assembly language)是一种用于电子计算机、微处理器、微控制器或其他可编程器件的低级语言,亦称为符号语言。在汇编语言中,用助记符(Mnemonics)代替机器指令的操作码,用地址符号(Symbol)或标号(Label)...

    汇编写冒泡排序

    用汇编语言写冒泡排序,可供参考学习,用汇编写比较长,但是值得阅读

Global site tag (gtag.js) - Google Analytics