linux内核中的汇编语言

Linux内核代码中,有一部分是用汇编语言编写的。其大部分是关于中断与异常处理的底层程序,还有就是与初始化有关的程序,以及一些核心代码中调用的公用子程序。

用汇编语言编写内核代码中的部分代码,大体上是出于如下几个方面考虑:

1linux内核中的底层程序直接与硬件打交道,需要一些专用的指令,而这些指令在C语言中并无对应的语言成分。

2)内核中实现某些操作的过程、程序段或函数,在运行时会非常频繁的被调用,这时用汇编语言编写,其时间效率会有大幅度提高。

3)在某些特殊的场合,一段程序的空间效率也非常重要,比如操作系统的引导程序一定要容纳在磁盘的第一个扇区中,多一个字节都不行。这时只能用汇编语言编写。

Linux内核代码中,以汇编语言编写的程序或者程序段,有两种不同的形式。一是完全的汇编代码,这样的代码采用.s作为文件名的后缀。第二种是嵌入在C程序中的汇编语言片断。

对于新接触linux内核源码的读者,哪怕他比较熟悉i386汇编语言,在理解这些汇编程序时都会感到困难,有的甚至会望而却步。其原因是:在内核汇编代码中GNU采用不同于常用Intel i386汇编语言的AT&T格式的汇编语言;而在嵌入C程序的片断中,更增加了一些指导汇编工具如何分配使用寄存器、以及如何与C程序中定义的变量相结合的语言成分。这些成分使得嵌入C程序的汇编语言片断实际上变成了一种介乎386汇编和C之间的一种中间语言。

首先我们讲一下AT&T格式与Intel格式汇编语言的以下主要区别,其它的详细情况可以参考AT&T汇编语言手册。

1)在Intel格式中大多使用大写字母,而AT&T格式中都使用小写字母。

2)在AT&T格式中,寄存器名上要加“%”作为前缀,而Intel格式则不带前缀。

3)在AT&T格式中,指令的源操作数在前,目标在后,恰好与Intel格式完全相反。

4)在AT&T格式中,访内指令的操作数大小由操作码后缀来决定。用作操作码后缀的字母有b(表示8位),w(表示16位),l(表示32位)。而在Intel格式中,则是在表示内存单元的操作数前面加上“BYTE PTR”,“WORD PTR”,“DWORD PTR”来表示。


当需要在C语言的程序中嵌入一段汇编语言程序段时,可以使用gcc提供的“asm”语句功能,例如,在include/asm/io.h中有这么一行:

#define __SLOW_DOWN_IO __asm__ __volatile__(“outb %a1,$0x80”)

这里,在asmvolatile前面的两个“__”字符,这是gccC语言的一种补充,含义我们在前面已经讲过了。下面我们看括号里面加上了引号的汇编指令,这是一条8位输出指令,如前所述在操作符上加上后缀“b”表示是8位操作,而0x80因为是常数,所以要加上前缀“$”,而寄存器a1也加了前缀“%”

上面这个例子还是很容易理解的,因为这就是简单的一条汇编语句,下面这个例子就困难多了(取子include/asm/atomic.h):

Static __inline__ void atomic_add(int i,atomic_t *v)

{

  __asm__ __volatile__

(

LOCK “addl %1,%0”

:”=m”(v->counter)

:”ir”(i),”m”(v->counter)

);

}

插入C代码中的汇编语言代码可以分成四部分,以冒号“:”加以分隔,其一般形式为:

指令部 : 输出部 : 输入部 : 损坏部

第一部分就是汇编语句本身,这一部分可以称为指令部,是必需的,而其它各部分则可以视情况而定。所以在最简单的情况下就与常规的汇编语句基本相同,如前面第一个例子。

当指令中的操作数要与C语言中的某些变量结合时,情况就复杂多了。如此例中,iv都是C语言函数的输入部分,怎么将其与汇编语言结合在一起呢?因为程序员无法确切知道gcc在嵌入点的前后会把哪一个寄存器分配用于哪一个变量,而且还得有个手段把使用寄存器的要求告诉gcc,反过来影响它对寄存器的分配。针对这个问题,gcc采用的办法是:程序员只提供具体的指令,而对寄存器的使用则只提供一个样板和一些约束条件,而把到底如何与变量结合的问题留给gccgas去处理。

在指令部中,数字加上前缀%,表示需要使用寄存器的样板操作数。这样,指令部中用到了几个不同的这种操作数,就说明有几个变量需要与寄存器结合,由gccgas在编译和汇编时根据后面的约束条件自行变通处理。那么,怎样表达对变量结合的约束条件呢?这就是其余几个部分的作用。输出部用以规定对输出变量的约束条件,必要时输出部可以有多个约束,互相之间用逗号分隔。每个输出约束以“=”号开头,然后是一个字母表示对操作数类型的说明,然后是关于变量结合的约束。例如在本例中,输出部里只有一个约束,“=m”表示相应的操作数(指令部中的%0)是一个内存单元v->counter

输出部后面是输入部,输入约束的格式与输出约束相似,但不带号。在本例中有两个输入约束,第一个为”ir”(i),表示指令中的%1可以是一个寄存器中的直接操作数(i表示immediater表示任何寄存器),并且该操作数来自C代码中的变量名i;第二个约束为”m”(v->counter)表示这是一个内存单元。

表示约束条件的字母有很多,主要有:“m””v””o”表示内存单元;”r”表示任何寄存器;”q”表示寄存器eaxebxecxedx之一;”i””h”表示直接操作数;”a””b””c””d”分别表示要求使用寄存器eax,ebx,ecxedx”S””D”分别表示要使用esiedi”I”表示常数(031)。

在有些操作中,除用于输入操作和输出操作数的寄存器以外,还要将若干寄存器用于计算或操作的中间结果。这样,这些寄存器原有的内容就损坏了,所以要在损坏部对操作的副作用加以说明,让gcc采取相应的措施,不过,有时候就直接把这些说明放在输出部了。另外还应注意,当输出部为空,即没有输出约束时,如果有输入约束存在,则必须保留分隔标记“:”号。

  • 0
    点赞
  • 5
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值