概述
在开发操作系统过程中,有时必须使用某些特殊的指令(如inb/outb/hlt),而C编译器无法自动使用这些指令。因此,如果要使用这些指令,有两种方式:
在汇编文件中为该指令的使用创建汇编函数(子程序),然后在C代码中以函数调用的方式去使用;
使用内联汇编:即在C代码中嵌入汇编程序。
在大多数情况下,使用内联汇编会更加简单、易用。因此,本文简要介绍本课程中所用的相关的GCC内联汇编的写法。
基本格式
基本格式如下,以asm关键字开头,包含3个可选的配置项。
asm (汇编语句
: 输出操作数(可选)
: 输入操作数(可选)
: 被破坏的寄存器列表(可选)
);
下面举个例子,其功能为将a的值赋值给b,具体代码如下:
int a = 10 , b ;
asm ( "movl %1, %%eax; movl %%eax, %0;"
: "=r" ( b ) /* 输出 */
: "r" ( a ) /* 输入 */
: "%eax" /* 破坏寄存器 */
) ;
其效果等价于movl a, %%eax; movl %%eax, b,即将a的值先给eax寄存器,再将eax寄存器的值给到b,最终实现b = a的效果。
具体格式说明
只有一条汇编语句
在内联汇编中,可以只有汇编语句,即没有输出、输入操作数,没有被破坏的寄存器列表。例如:
asm ( "hlt" ) ;
// 让CPU暂停运行
有多条汇编语句
当有多条汇编语句时,需要写在同一字符串中,不同语句之间用;或者\n\t分隔。
asm ( "sti; hlt" ) ; // 开启中断,然后让CPU暂停运行
asm ( "sti\n\thlt" ) ; // 开启中断,然后让CPU暂停运行
asm ( "sti\n\t" // 同上。C语言中多个连续的字符串,会被认为是同一字符串
"hlt" ) ; // 开启中断,然后让CPU暂停运行
有输出操作数
当汇编语句中有些数据需要存储到C语言中的某些变量中时,则可以使用输出操作数。
例如,下面的代码是将c变量的值通过mov $3, c设置成3。其中%[out]为定义的一个输出约束,其名称应与:后的out相同,“=r"©指定了c变量映射到某个寄存器中。
char c;
asm(“mov $3, %[out]”:[out]”=r"©);
其中=r还可以设置成其它,如下表所示:
r:任意寄存器
a:%eax, %ax, %al
b: %ebx, %bx, %bl
c: %ecx, %cx, %cl
d: %edx, %dx, %dl
S: %esi, %si
D: %edi, %di
通过上述方式,即实现了类似mov $3, c的效果。
有输入操作数
当需要从C语言中读取变量的值到汇编语句中时,则需要使用输入操作数。例如,下面的代码中,mov %[ch], %%al用于将c变量中的字符写到al中。因此,在输入操作数中使用了[ch]"r"©,即c变量映射到某个寄存器上(见上文中输出操作数的类似设置)。最终的效果为:mov $0xe, %%ah、mov %[c], %%al
char c = 'a';
asm (
"mov $0xe, %%ah\n\t"
"mov %[ch], %%al\n\t"
"int $0x10"::[ch]"r"(c));
避免优化
在某些情况下,GCC编译器会对内联汇编中的汇编语句进行优化处理,最终导致生成语句和预期的不同,影响程序的执行结果。为了避免这些问题,可以强制要求GCC不对其进行优化,即原样的生成。要达到这点,可以使用__asm__ __volatile__
替代原来的asm。