内核编程语言和环境
as86与GNU as汇编
对于汇编这种语言,相信所有同胞们都是望而却步。然而由于操作系统许多关键代码要求很高的执行速度和效率,因此在系统源码中通常会有10%左右的起关键作用的汇编语言。linux的32位初始化代码、所有中断和异常处理、许多宏定义都是用汇编或嵌入汇编。
系统内汇编代码分为两种,一种是as86汇编器用于编译内核中的boot/bootsect.s引导山区程序和boot/setup.s设置程序;其余汇编包括C语言产生的汇编程序均使用gas编译,这里我们只简单介绍一下gas的用法。以后遇到汇编的代码部分会详细说明一下语法
命令格式:as [选项] [-o objfile] [srcfile]
链接器、区与重定位
在C语言——关于链接的思考和程序运行时的数据结构那两篇文章中已经稍微介绍了一下目标文件结构和链接器的一些知识。这里再次补充。
在一个目标文件中(上面汇编出来的)会存在至少三个区:text、data、bss区,链接器ld会将这些区按照一定规律组合起来变成一个可执行文件,当存在不止一个目标文件时,例如 a.out 和 b.out,ld会将 a 和 b 的text段放在一起、data段放在一起、bss段放一起。
当然ld也会把一些重定位信息放进去以便得知程序的哪些数据会变化或者哪些数据该修改如何修改,所以为了执行重定位操作,每次在目标文件中涉及地址时,ld必须知道:
- 目标文件中对一个地址的引用是从何算起
- 地址的字节长度是多少
- 该地址引用的是哪个区,区开始地址多少
- 是否与PC(程序计数器)有关
链接器涉及的区
text、data、bss段就不必多写了,老生常谈。
需要提一下的是absolute段 :该区的0地址总是重定位到运行时刻的0地址处,如果你不想ld在重定位操作时改变你所引用的地址,那请使用这个段(我们叫他不可重定位段)。
符号
符号(Symbol)在编译链接过程中是个很重要的概念,程序员使用符号来命名对象,链接器使用符号进行链接操作,调试器利用符号进行调试(符号类型属性详见include/a.out.h)
- 符号名以一个一字母或“._”开始
- 除了名字以外符号还有值和属性,若不定义就使用,其属性为0,指示该符号是一个外部定义的符号。
- 符号值一般32位,其值是从区开始到该标号处的地址值,对于text、data、bss段,一个符号的值通常会在链接过程中由于ld改变段记得基地址而改变。
标号(Lable)是后面紧随一个冒号的符号,此时标号代表活动位置计数器的当前值,也可作为指令的操作数使用,例如使用“=”给其赋值。
C语言程序
C程序的编译与链接过程就不在这里赘述了,参照C语言的系列文章,下面来说一下C里面嵌入汇编。
嵌入汇编
具有输入和输出参数的嵌入汇编语句基本格式:
asm("汇编语句"
: 输出寄存器
: 输入寄存器
: 会被修该的寄存器);
除了第一行外,后面带冒号的行若不使用都可以省略。其中“输出寄存器”表示当这段嵌入汇编执行结束后,哪些寄存器用于存放输出数据。这些寄存器会分别对应C表达式值或一个内存地址;“输入寄存器”表示在开始执行汇编代码时,这里指定一些寄存器中应存放的输入值,对应C变量或常数值。“会被修改寄存器”表示你已对其中列出的寄存器中的值进行了改动,gcc编译器不能再依赖于它原先对这些寄存器加载的值,必要的话gcc需重新加载这些寄存器。
下面是kernel/traps.c中的一段代码:
01 #define get_seg_byte(seg, addr) \
02 ({ \
03 register char _res; \ //定义一个寄存器变量_res
04 _asm_("push %%fs; \ //先保存fs寄存器原值(段选择符)
05 mov %%ax,%%fs; \ //然后用seg设置fs
06 movb %%fs:%2,%%al; \ //取seg:addr处1字节到al寄存器
07 pop %%fs" \ //恢复fs寄存器原内容
08 :"=a" (_res) \ //输出寄存器列表
09 :"0" (seg),"m" (*(addr)); \ //输入寄存器列表
10 _res;})
这里需要解释的就第6行和8、9行:gcc将输入输出寄存器编号从0开始,也就是说8行为0,9行前半部分为1,后半部分为2。所以说第6行的”%2”就是(*(addr))偏移地址。第8、9行中的a、m