Chinaunix首页 | 论坛 | 博客
  • 博客访问: 3168162
  • 博文数量: 685
  • 博客积分: 0
  • 博客等级: 民兵
  • 技术积分: 5303
  • 用 户 组: 普通用户
  • 注册时间: 2014-04-19 14:17
个人简介

文章分类

全部博文(685)

文章存档

2015年(116)

2014年(569)

分类: 嵌入式

2014-05-22 01:21:18

原文地址:http://blog.sina.com.cn/s/blog_6449050e0100htfe.html

任何一个用高级语言编写的操作系统,其内核源码中总有少部分用汇编语言编写的,其中,大部分是关于中断和系统调用,异常处理等,用汇编语言编写这些代码大致出于以下考虑:

1,操作系统内核中的底层程序需要和硬件打交道,需要用到一些专用指令,这些指令在C语言中没有对应的部分,例如:对外设的输入输出指令,并没有对应的c语言,因此,这些底层的操作需要用汇编语言编写,还有,当需要和寄存器打交道的时候,同样需要用汇编语言。

2,CPU中的一些特殊指令例如开关中断,也没有对应的C语言成分,在原来指令结构中新增加的指令,也没有对应的C语言成分,这些指令也需要汇编语言。

3,在内核实现某些操作的过程或者函数,在运行的时候会频繁的调用,为了提高效率,需要用汇编语言,例如系统调用的进入和返回。(程序的时间效率)

4,在某些情况下,程序的空间效率也是举足轻重的,操作系统的引导程序就是一个很好的例子。系统的引导程序一定要能够容纳在磁盘上的第一个扇区,这个时候哪怕程序多出一个字节都不行,因此,需要用汇编语言来控制。



在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采取相应的措施,不过,有时候就直接把这些说明放在输出部了。另外还应注意,当输出部为空,即没有输出约束时,如果有输入约束存在,则必须保留分隔标记“:”号。

阅读(1088) | 评论(0) | 转发(0) |
0

上一篇:内核启动流程

下一篇:Linux内核KBuild系统

给主人留下些什么吧!~~