《Linux内核完全注释》(2)
3 内核编程语言和环境
首先介绍了as86和GNU as汇编程序的语法和使用方法,对GNU C中的内联汇编、语句表达式、寄存器变量和内联函数等C语言扩展内容进行介绍,同时描述了C和汇编函数之间的相互调用机制。
3.1 as86汇编器
是一个可以产生16位代码的汇编器,与之配套的是ld86链接器。其是Bruce Evans编写的Intel 8086和80386的编译程序和链接程序。
3.1.1 as86汇编语法
汇编器的作用事发汇编语言源程序(srcfile)编译成目标文件(objfile)。
as [option] -o objfile srcfile
3.1.2 汇编语言程序结构
作者写了一段37行的汇编程序boot.s,该程序是一个简单的引导扇区启动程序,可以直接运行。启动后,会在屏幕第17行、第5列处显示出红色的字符串“Loading system ..”,并且光标移到下一行。
作者还详细介绍了这段汇编代码中的一些小知识点:
1.在汇编中以感叹号!和分号;结尾的语句为注释。
2.‘.globl’是一个伪指令,又可以叫指示符,是帮助编译器编译的。这将告诉编译器后面的操作数可以被编译为全局变量。相同的指示符还有.text .data .bss,分别是代码段、数据段、和未初始化数据段。
3.‘entry start’是告诉编译器,程序将从这边开始。
4.作者还介绍了赋值、跳转、并详细介绍了MOV(寻址)的一些用法等,这些可以帮助以前不了解汇编的读者。
3.1.3 as86汇编语言程序的编译和链接
使用as86命令进行编译,生成目标文件,使用ld86进行链接,去掉符号信息。最后使用dd命令将最终文件,写入软盘或者Image盘。
3.1.4 as86和ld86使用方法和选项
想要尝试编译那些古老程序的读者可以去了解一下这章,介绍了这两个命令。
3.2 GNU as 汇编
“上节介绍的 as86 汇编器仅用于编译内核中的 boot/bootsect.s 引导扇区程序和实模式下的设置程序boot/setup.s。内核中其余所有汇编语言程序(包括 C 语言产生的汇编程序)均使用 gas 来编译,并与 C语言程序编译产生的模块链接。”
3.2.1 编译使用as汇编语言写的程序
gas应该就是GNU as。
使用方法为as 【选项】【-o objfile】【srcfile.s】
其编译语法使用AT&T系统V的汇编语法,为了与gcc编译的程序兼容。
3.2.x
3.2.2介绍了gas汇编语言,及其基本语法。
3.2.3介绍了指令语句、操作数和寻址的使用方法。
3.2.4是关于区与重定位。
3.2.5是关于gas汇编中符号的使用方法。
3.2.6介绍了20个汇编命令(伪指令)
3.2.7介绍如何使用as编写16位代码
3.2.8是AS汇编器的命令行选项
这一块内容主要是关于as汇编语言和as汇编器的,内容难度仅限于基本看懂内核代码中的汇编部分。本人不熟悉汇编语言,现在用的也比较少。
3.3 C语言程序
C语言比起汇编语言效率还是高一点。
C语言程序进行编译的时候通常会经过四个处理阶段:预处理阶段、编译阶段、汇编阶段、链接阶段。
预处理阶段对C语言程序中的指示符和宏进行替换,删除注释,输出纯C文件。编译是用gcc将纯C编译成汇编程序。汇编是用as汇编器将as汇编编译成.o目标文件。链接是使用ld链接器把各个.o文件和.a库文件链接起来,生成一个可执行文件。这个过程可以调用gcc命令进行执行。gcc【选项】【-o outfile】infile ... -S选项只会执行到汇编阶段前,生成.s汇编文件。-c选项不会执行链接,只生成.o目标文件。
当然,C语言程序也可以嵌入汇编,基本格式为:
asm("汇编语句"
:输出寄存器
:输入寄存器
:会被修改的寄存器
);
除第1行以外,后面带冒号的行若不使用就都可以省略。“asm”是内联汇编语句关键词;“汇编语句”是你写汇编指令的地方;“输出寄存器”表示当这段嵌入汇编执行完之后,哪些寄存器用于存放输出数据。此地,这些寄存器会分别对应一 C 语言表达式值或一个内存地址;“输入寄存器”表示在开始执行汇编代码时,这里指定的一些寄存器中应存放的输入值,它们也分别对应着一 C 变量或常数值。“会被修改的寄存器”表示你已对其中列出的寄存器中的值进行了改动, gcc 编译器不能再依赖于它原先对这些寄存器加载的值。如果必要的话, gcc 需要重新加载这些寄存器。因此我们需要把那些没有在输出/输入寄存器部分列出,但是在汇编语句中明确使用到或隐含使用到的寄存器名列在这个部分中。表3-4是常用寄存器加载代码说明。
之后说明了一些例子,讲解gcc是如何处理这些汇编语句。gcc自己也会对汇编语句进行优化,避免执行那些重复的语句。当然如果不想代码被gcc优化,就需要在 asm 符号后面添加关键词volatile。
圆括号中的组合语句,即形如”({...})”的语句,可以在 GNU C 中用作一个表达式使用。
当然,人们通常不会象上面这样写语句,这种语句表达式通常都用来定义宏。例如内核源代码init/main.c 程序中读取 CMOS 时钟信息的宏定义:
69 #define CMOS_READ(addr) ({ \ // 最后反斜杠起连接两行语句的作用。
70 outb_p(0x80|addr,0x70); \ // 首先向 I/O 端口 0x70 输出欲读取的位置 addr。
71 inb_p(0x71); \ // 然后从端口 0x71 读入该位置处的值作为返回值。
72 })
GNU 对 C 语言的另一个扩充是允许我们把一些变量值放到CPU寄存器中,即所谓寄存器变量。这样CPU就不用经常花费较长时间访问内存去取值。
register int res asm("ax");
这里 ax 是变量 res 所希望使用的寄存器。定义这样一个寄存器变量并不会专门保留这个寄存器不派其他用途。在程序编译过程中,当 gcc 数据流控制确定变量的值已经不用时就可能将该寄存器派作其他用途,而且对它的引用可能会被删除、移动或被简化。
在程序中,通过把一个函数声明为内联( inline)函数,就可以让 gcc 把函数的代码集成到调用该函数的代码中去。这样处理可以去掉函数调用时进入/退出时间开销,从而肯定能够加快执行速度。达到用空间换取时间的特性。
inline int inc(int *a)
后面介绍了static和inline组合,还有extern和inline的组合的使用注意点。