操作系统18----GCC内联汇编

         在操作系统中需要对于硬件资源进行管理,对于处理器最直接的对外接口是机器指令,而汇编语言可以看做是机器指令的助记符,对于某些汇编语言来说,C语言并没有与之对应的语法来进行执行,因此在C语言中需要调用汇编语言,从而对硬件资源进行操作和管理。GCC内联汇编提供了在C语言中使用汇编语言的规范,GCC中内联汇编包括基本内联汇编语句( basic inlineasm statement)扩展内联汇编语句( extended inline asm statement)。

基本内联汇编语句

格式如下:

asm("statements");

"asm" 和 "__asm__" 的含义是完全一样的。 如果有多行汇编, 则每一行都要加上 "\n\t"。 其中的 “\n” 是换行符, "\t” 是 tab 符, 在每条命令的 结束加这两个符号, 是为了让 gcc 把内联汇编代码翻译成一般的汇编代码时能够保证换行和留有一定的空格。 对于基本asm语句, GCC编译出来的汇编代码就是双引号里的内容。 例如:

asm("pushl %eax\n\t"
"movl $0,%eax\n\t"
"popl %eax"
);

编译之后汇编内容就是双引号内的内容。

asm("movl %eax, %ebx");
asm("xorl %ebx, %edx");
asm("movl $0, _boo);

在内联汇编中改变了 edx 和 ebx 的值, 但是由于 gcc 的特殊的处理方法, 即先形成汇编文件, 再交给 GAS 去汇编, 所以 GAS 并不知道已经改变了 edx和 ebx 的值, 如果程序的上下文需要 edx 或 ebx 作其他内存单元或变量的暂存, 就会产生没有预料的多次赋值, 引起严重的后果。 对于变量 _boo也存在一样的问题。 为了解决这个问题, 就要用到扩展 GCC 内联汇编语法。

扩展内联汇编语句

基本格式如下:

asm [volatile] ( Assembler Template
: Output Operands
[ : Input Operands
[ : Clobbers ] ])

例如如下内联汇编语句

#define read_cr0() ({ \
unsigned int __dummy; \
__asm__( \
"movl %%cr0,%0\n\t" \
:"=r" (__dummy)); \
__dummy; \
})

其中, __asm__ 表示汇编代码的开始, 其后可以跟 __volatile__( 这是可选项) , 其含义是避免 “asm” 指令被删除、 移动或组合, 在执行代码时, 如果不希望汇编语句被 gcc 优化而改变位置, 就需要在 asm 符号后添加 volatile 关键词: asm volatile(...); 或者更详细地说明为:__asm__ __volatile__(...);

然后就是小括弧, 括弧中的内容是具体的内联汇编指令代码。 ""为汇编指令部分, 例如, "movl %%cr0,%0\n\t"。 数字前加前缀 “%“, 如%1, %2等表示使用寄存器的样板操作数。 可以使用的操作数总数取决于具体CPU中通用寄存器的数 量, 如Intel可以有8个。 指令中有几个操作数, 就说明有几个变量需要与寄存器结合, 由gcc在编译时根据后面输出部分和输入部分的约束条件进行相应的处理。 由于这些样板操作数的前缀使用了”%“, 因此, 在用到具体的寄存器时就在前面加两个“%”, 如%%cr0。

输出部分( outputoperand list) , 用以规定对输出变量( 目标操作数) 如何与寄存器结合的约束( constraint),输出部分可以有多个约束, 互相以逗号分开。 每个约束以“=”开头, 接着用一个字母来表示操作数的类型, 然后是关于变量结合的约束。

:"=r" (__dummy)

“=r”表示相应的目标操作数( 指令部分的%0) 可以使用任何一个通用寄存器, 并且变量__dummy 存放在这个寄存器中。

:“=m”(__dummy)

“=m”就表示相应的目标操作数是存放在内存单元__dummy中。
常见的字母及其约束含义

输入部分( input operand list) : 输入部分与输出部分相似, 但没有“=”。 如果输入部分一个操作数所要求使用的寄存器, 与前面输出部分某个约束所要求的是同一个寄存器, 那就把对应操作数的编号( 如“1”, “2”等) 放在约束条件中
修改部分( clobber list,也称 乱码列表):这部分常常以“memory”为约束条件, 以表示操作完成后内存中的内容已有改变, 如果原来某个寄存器的内容来自内存, 那么现在内存中这个单元的内容已经改变。 乱码列表通知编译器, 有些寄存器或内存因内联汇编块造成乱码, 可隐式地破坏了条件寄存器的某些位( 字段) 。

注意, 指令部分为必选项, 而输入部分、 输出部分及修改部分为可选项, 当输入部分存在, 而输出部分不存在时, 冒号“: ”要保留, 当“memory”存在时, 三个冒号都要保留。

举例1:

int count=1;
int value=1;
int buf[10];
void main()
{
asm(
"cld \n\t"
"rep \n\t"
"stosl"
: :
"c" (count), "a" (value) , "D" (buf)
);
}

对应汇编如下

movl count,%ecx
movl value,%eax
movl buf,%edi
#APP
cld
rep
stosl
#NO_APP

在计算机中,大部分数据存放在主存 中,8086CPU提供了一组处理主存中连续存放的数据串的指令——串操作指令。串操作指令中,源操作数用寄存器SI寻址,默认在数据段DS中,但允许段 超越;目的操作数用寄存器DI寻址,默认在附加段ES中,不允许段超越。每执行一次串操作指令,作为源地址指针的SI和作为目的地址指针的DI将自动修 改:+/-1(对于字节串)或+/-2(对于字串)。地址指针时增加还是减少取决于方向标志DF。在系统初始化后或者执行指令CLD指令后,DF=0,此 时地址指针增1或2;在执行指令STD后,DF=1,此时地址指针减1或2。

rep可以是任何字符传指令(CMPS, LODS, MOVS, SCAS, STOS)的前缀. rep能够引发其后的字符串指令被重复, 只要ecx的值不为0, 重复就会继续. 每一次字符串指令执行后, ecx的值都会减小。

stos((store into String),意思是把eax的内容拷贝到目的地址。
因此上述指令的功能是将value值拷贝到buf中,拷贝count次。

举例2:

asm("leal (%1,%1,4),%0"
: "=r" (x)
: "0" (x)
);

对应汇编语句如下

movl x,%eax
#APP
leal (%eax,%eax,4),%eax
#NO_APP
movl %eax,x

LEA指令将存储器操作数mem的4位16进制偏移地址送到指定的寄存器。这里,源操作数必须是存储器操作数,目标操作数必须是16位通用寄存器。Load effect address

AT&T汇编基本语法

AT&T与Intel汇编格式不同之处

* 寄存器命名原则
AT&T: %eax Intel: eax

* 源/目的操作数顺序
AT&T: movl %eax, %ebx Intel: mov ebx, eax

* 常数/立即数的格式
AT&T: movl $_value, %ebx Intel: mov eax, _value

把value的地址放入eax寄存器
AT&T: movl $0xd00d, %ebx Intel: mov ebx, 0xd00d

* 操作数长度标识
AT&T: movw %ax, %bx Intel: mov bx, ax

* 寻址方式
AT&T: immed32(basepointer, indexpointer, indexscale)
Intel: [basepointer + indexpointer × indexscale + imm32)

* 直接寻址
AT&T: foo Intel: [foo]
boo是一个全局变量。 注意加上$是表示地址引用, 不加是表示值引用。 对于局部变量, 可以通过堆栈指针引用。

* 寄存器间接寻址
AT&T: (%eax) Intel: [eax]

* 变址寻址
AT&T: _variable(%eax) Intel: [eax + _variable]
AT&T: _array( ,%eax, 4) Intel: [eax × 4 + _array]
AT&T: _array(%ebx, %eax,8) Intel: [ebx + eax × 8 + _array]

ucore中的内联汇编

static inline uint8_t
inb(uint16_t port) {
    uint8_t data;
    asm volatile ("inb %1, %0" : "=a" (data) : "d" (port));
    return data;
}

从端口port读取一个字节到eax中,并将变量data返回。

static inline void
outb(uint16_t port, uint8_t data) {
    asm volatile ("outb %0, %1" :: "a" (data), "d" (port));
}

将数据data输出到port端口

static inline uint32_t
read_ebp(void) {
    uint32_t ebp;
    asm volatile ("movl %%ebp, %0" : "=r" (ebp));
    return ebp;
}

将ebp寄存器中内存读出,并赋值给变量ebp

static inline void
insl(uint32_t port, void *addr, int cnt) {
    asm volatile (
            "cld;"
            "repne; insl;"
            : "=D" (addr), "=c" (cnt)
            : "d" (port), "0" (addr), "1" (cnt)
            : "memory", "cc");
}

CLD指令是使DF=0, 即SI,DI寄存器自动增加;

SI(Source Index):源变址寄存器可用来存放相对于DS段之源变址指针;
DI(Destination Index):目的变址寄存器,可用来存放相对于 ES 段之目的变址指针;

rep指令的目的是重复其上面的指令。ECX的值是重复的次数.repe和repne,前者是repeat equal,意思是相等的时候重复,后者是repeat not equal,不等的时候重复;每循环一次ECX自动减一。

insl  从 DX 指定的 I/O 端口将双字输入 ES:(E)DI 指定的内存位置;

如果输入部分一个操作数所要求使用的寄存器, 与前面输出部分某个约束所要求的是同一个寄存器, 那就把对应操作数的编号( 如“1”, “2”等) 放在约束条件中。port变量和edx绑定,cnt变量和ecx绑定,addr 变量和 edi绑定,每次递增完,地址加4。

把端口号为port 传送到 addr,读取cnt个字节。

参考资料:

http://www.ibiblio.org/gferg/ldp/GCC-Inline-Assembly-HOWTO.html

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值