Linux C内联汇编用法

本文详细阐述了Linux内核中使用汇编的关键概念,包括asm和volatile的作用、InstructionList的定义、Output和Input部分的用途、以及Clobber/Modify的含义。通过实例代码分析,深入探讨了如何在驱动程序中实现原子操作,屏蔽本地中断等功能,为读者提供了一次全面而深入的汇编学习之旅。
  Linux内核中有很多c中使用汇编的情况,比如原子操作。内联汇编通常用下面的格式:

asm volatile("Instruction List" : Output : Input : Clobber/Modify);

        当然,或者写作如下格式(Output、Input、Clobber/Modify都是可选的),也就是三个冒号,4个部分:

asm volatile("Instruction List"

                                    : Output

                                    : Input

                                    : Clobber/Modify);

        为了理解方便,以屏蔽本地irq相关函数的代码为例子:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
static inline unsigned long native_save_fl( void )
{
     unsigned long flags;
  
     /*
      * "=rm" is safe here, because "pop" adjusts the stack before
      * it evaluates its effective address -- this is part of the
      * documented behavior of the "pop" instruction.
      */
     asm volatile ( "# __raw_save_flags\n\t"
              "pushf ; pop %0"
              : "=rm" (flags)
              : /* no input */
              : "memory" );
  
     return flags;
}
  
static inline void native_restore_fl(unsigned long flags)
{
     asm volatile ( "push %0 ; popf"
              : /* no output */
              : "g" (flags)
              : "memory" , "cc" );
}
  
static inline void native_irq_disable( void )
{
     asm volatile ( "cli" : : : "memory" );
}
  
static inline void native_irq_enable( void )
{
     asm volatile ( "sti" : : : "memory" );
}

        本篇blog耗时2小时。

 

1、asm和volatile

        volatile是c的关键字,是个修饰符,用于修饰变量或者函数,意思是告诉编译器,该变量或者函数是“易变”的,编译器会在优化时做相关的考虑。驱动中常用于修饰急促器变量。Linux下c中的使用内联汇编,就是这样,需要asm()的格式,可以没有volitile,但不能没有asm。

        关于asm到底是什么?首先,它不是ANSI C关键字,c89/c99标准中没有asm关键字;asm属于“GNU C Language Extensions”,像typeof,inline等都是这种情况。

2、Instruction List

        指令序列,就是指要执行的指令汇编指令,多条指令之间使用 分隔符“;”、“\n\t”等进行联合。

        对应例子中,就是

  • "pushf;pop %0"
  • “push %0;popf”
  • "cli"
  • "sti"

        含义分别为:

  • 将EFLAGS寄存器的值压栈,再出栈给的1个变量(先这么叫吧)
  • 将第1个变量压栈,再出栈给EFLAGS寄存器
  • 禁止本地中断
  • 开启本地中断

3、Output

        指令序列执行的结果的输出,指指令执行的结果要输出到哪。Output和Input部分,通常使用下列格式:

“constraint”(variable)

        constraint为限制的意思,翻译好听点就是“修饰”,用于限制variable的。

        在例子中,只有navtive_save_fl函数有“=rm”(flags),这句还不好臆测,还得查资料(参考资料5):

  • =,表示只写,也是output必须具备的(看见=,就是output)
  • r表示寄存器
  • m表示内存
  • flags为变量名

        联合起来表示将flags变量以内存或者寄存器的方式进行操作,flags是输出。

        关于constraint:

  • a,b,c,d,S,D 分别代表 eax,ebx,ecx,edx,esi,edi 寄存器
  • r 上面的寄存器的任意一个(谁闲着就用谁)
  • m 内存
  • i 立即数(常量,只用于输入操作数)
  • g 寄存器、内存、立即数 都行(gcc你看着办)

4、Input

       Input和Output很像,就是没有“=”。Input为指令序列提供输入,在例子中,只有native_restore_fl函数有“g”(flags),g表示“Any register, memory or immediate integer operand is allowed, except for registers that are not general registers.”,表示flags可以使用寄存器、内存、立即数等方式。

        还有个问题,就是Input和Output如何与Instruction List相关联的?Instruction List中的%0,前面叫他“第一个变量”,实际上是不准确的,实际上应该叫做input/output operand,输入/输出操作数,从Output开始,0表示第一个,1就表示第二个,以此类推。从gcc3.1开始,支持直接将%0写作%[input]、%[output]。

       input和Output可以有多个变量:中间用“,”隔开。

5、Clobber/Modify

       该部分表示哪些寄存器、内存被修改,但是又没有出现在Input/Output中。通常看到的就是memory,表示汇编语句可能修改了内存,如果有变量缓存在寄存器中,需要重新读取该变量。

 

参考资料:

1、Linux 中 x86 的内联汇编

2、__asm__ __volatile__内嵌汇编用法简述

3、第 19 章 汇编与C之间的关系

4、AT&T inline Assembly Constraint

5、Assembler Instructions with C Expression Operands

6、内联汇编

7、GCC-Inline-Assembly-HOWTO

Published in C语言技巧 and tagged asm, 汇编 on 2013年11月24日 by rock3
### C语言内联汇编使用指南 C语言中的内联汇编是一种强大的工具,允许开发者直接在C代码中嵌入汇编指令。这种特性通常用于性能关键的代码段或需要直接访问硬件资源的地方。以下是关于C语言内联汇编的详细用法和常见问题解决方法。 #### 1. 基本语法 GCC编译器支持内联汇编的基本语法如下: ```c __asm__ [__volatile__] ("汇编语句模板" : 输出部分 : 输入部分 : 破坏描述部分); ``` - **汇编语句模板**:这是汇编指令的具体内容,通常以字符串形式表示。 - **输出部分**:指定汇编代码的输出操作数及其约束条件。 - **输入部分**:指定汇编代码的输入操作数及其约束条件。 - **破坏描述部分**:列出在执行汇编代码过程中可能被修改的寄存器[^2]。 #### 2. 示例代码 以下是一个简单的例子,演示如何使用内联汇编来交换两个变量的值: ```c void swap(int *a, int *b) { __asm__ volatile ( "xchg %1, %0\n\t" : "+r" (*a), "+r" (*b) : : "memory" ); } ``` 在这个例子中: - `xchg` 是汇编指令,用于交换两个寄存器的值。 - `"memory"` 表示这段汇编代码可能会修改内存中的数据[^1]。 #### 3. 使用 `volatile` 关键字 `volatile` 关键字的作用是告诉编译器不要对内联汇编代码进行优化。如果省略了 `volatile`,编译器可能会重新排列或删除某些汇编代码,从而导致程序行为异常。 #### 4. 注意事项 - **跨平台问题**:内联汇编与具体的架构和编译器密切相关。例如,GCC 和 MSVC 对内联汇编的支持方式不同,因此写跨平台代码时需要特别注意。 - **调试困难**:混合使用C语言和汇编语言会使代码更难调试,因为错误可能出现在汇编代码中,而普通的调试工具无法有效处理。 - **维护问题**:由于内联汇编代码通常依赖于特定的硬件架构,因此代码的可读性和可维护性会受到影响。只有在性能至关重要的地方才建议使用内联汇编[^1]。 #### 5. 常见错误及解决方法 - **寄存器冲突**:如果汇编代码修改了某些寄存器但没有正确声明,可能导致程序崩溃。解决方案是确保在“破坏描述部分”中列出所有可能被修改的寄存器。 - **输入输出约束错误**:错误的约束条件可能导致编译失败或运行时错误。建议仔细查阅GCC文档,了解各种约束条件的含义。 - **缺少 `volatile`**:如果内联汇编代码未加 `volatile`,可能会被编译器优化掉。确保在必要时添加 `volatile` 关键字。 #### 6. 应用场景 内联汇编通常用于以下场景: - 性能优化:对于一些性能敏感的操作,如位操作、循环展开等,可以直接使用汇编代码实现。 - 硬件访问:直接控制硬件寄存器或中断控制器。 - 内核开发:Linux内核中大量使用内联汇编来实现与硬件交互的功能[^2]。 ### 示例代码:计算平方根 以下是一个使用内联汇编计算浮点数平方根的例子: ```c float sqrt_float(float x) { float result; __asm__ volatile ( "fsqrt" // 汇编指令:计算平方根 : "=t" (result) // 输出:将结果存储到 result 中 : "0" (x) // 输入:从 x 获取值 ); return result; } ```
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值