C语言中嵌入汇编程序

背景

阅读linux0.11源码的kernel/traps.c文件里包含了几个语句是内嵌的汇编程序,虽知道整体的意思,但是细化后就不明所以然,这里记录下。

C中嵌入汇编程序

格式

asm("汇编语句"
    : 输出寄存器
    : 输入寄存器
    : 会被修改的寄存器
    );

带冒号的行可以省略,“输出寄存器”表示汇编执行完后,存放输出数据的寄存器,“输入寄存器”开始执行代码的时候,指定指定寄存器存放值。

实例代码

示例1

int a=10,b;
asm("movl %1, %%eax;
     movl %%eax, %0;"
     :"=r"(b)
     :"r"(a)
     :"%eax"
    );

表示C语言里的“b=a;”。
“r”表示使用任意寄存器,%0、%1表示使用两个寄存器,一般只能%0~%9共十个操作数,按输入输出寄存器出现顺序进行映射。
寄存器用两个百分号,是因为使用了%0%1这些数字使百分号有了特殊意义,所以在操作数出现的寄存器必须用双百分表示。
会被修改的寄存器里边的%eax表示eax寄存器在汇编代码块执行过程中会被改写,在执行前要保护好,这是提交给编译器决定的。

示例2

#define get_seg_byte(seg,addr) ({ \
    register char __res; \
    __asm__("push %%fs; \                // 将fs寄存器值压栈
        mov %%ax,%%fs; \                // 将eax中的段值赋值给fs寄存器
        movb %%fs:%2,%%al; \            // 将fs:(*(addr)) --> al
        pop %%fs" \                        // 弹出fs
    :"=a" (__res) \                        // 将eax值 --> __res
    :"0" (seg),"m" (*(addr))); \
    __res;})

表示取段seg中地址addr处的一个字符。
上面是kernel/traps.c中的一段代码,其中:

参数:seg - 段选择符;addr - 段内指定地址。
输出:%0 - eax(__res);输入:%1 - eax(seg);%2 - 内存地址(*(addr))。
“=a”中的’a’称为加载代码,’=’表示这是输出寄存器,执行完后,eax的值将赋给_res,’”0” (seg)’表示在这段代码开始运行时将seg放到eax寄存器中,”0”(0可以省略)表示这里使用的寄存器和上面输出寄存器一样。

编译器规定:标号从输出寄存器的最左边开始,被编号为0,往右和往下将被依次编号。所以这里的”0”表示eax,”1”表示seg,”2”表示*(addr)。

%%fs:%2是使用编号为2的变量(*(addr)),这里加上%%fs的意思??

示例3

// 取段seg中地址addr处的一个字长(4字节)。
// 参数:seg - 段选择符;addr - 段内指定地址。
// 输出:%0 - eax(__res);输入:%1 - eax(seg);%2 - 内存地址(*(addr))。

#define get_seg_long(seg,addr) ({ \
    register unsigned long __res; \
    __asm__("push %%fs; \
            mov %%ax,%%fs; \
            movl %%fs:%2,%%eax; \
            pop %%fs" \
    :"=a" (__res) \
    :"0" (seg),"m" (*(addr))); \
    __res;})

示例4

// 取fs段寄存器的值(选择符)。
// 输出:%0 - eax(__res)

#define _fs() ({ \
    register unsigned short __res; \
    __asm__("mov %%fs,%%ax" \
    :"=a" (__res) \
    :                        // 没有输入寄存器
    ); \
    __res;})