文章详情

  • 游戏榜单
  • 软件榜单
关闭导航
热搜榜
热门下载
热门标签
php爱好者> php文档>《基于linux的C编程与内核导读》连载(7)

《基于linux的C编程与内核导读》连载(7)

时间:2009-07-23  来源:叮汀

 今天的内容是本书第二章的第6节,主要讲内核代码中的汇编语言的特点,欢迎大家指出其中错误或提出改进意见。谢谢!

2.6 linux内核中的汇编语言

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

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

(1)linux内核中的底层程序直接与硬件打交道,需要一些专用的指令,而这些指令在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”)

这里,在asm和volatile前面的两个“__”字符,这是gcc对C语言的一种补充,含义我们在前面已经讲过了。下面我们看括号里面加上了引号的汇编指令,这是一条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语言中的某些变量结合时,情况就复杂多了。如此例中,i与v都是C语言函数的输入部分,怎么将其与汇编语言结合在一起呢?因为程序员无法确切知道gcc在嵌入点的前后会把哪一个寄存器分配用于哪一个变量,而且还得有个手段把使用寄存器的要求告诉gcc,反过来影响它对寄存器的分配。针对这个问题,gcc采用的办法是:程序员只提供具体的指令,而对寄存器的使用则只提供一个“样板”和一些约束条件,而把到底如何与变量结合的问题留给gcc和gas去处理。

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

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

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

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

相关阅读 更多 +
排行榜 更多 +
终极街头格斗

终极街头格斗

休闲益智 下载
大炮轰飞机

大炮轰飞机

飞行射击 下载
像素打僵尸

像素打僵尸

飞行射击 下载