boot启动程序包含三个汇编程序,主要作用是:当PC机打开电源后,CPU将进入实模式,并从地址0xFFFF0处开始执行程序,这个地址就是BOIS程序。BOIS程序将执行系统的某些检测,并在物理0地址处初始化中断向量。接着将可启动设备的第一扇区(磁盘引导扇区)读入内存绝对地址0x7C00处,并跳转到这个地址开始执行,而0x7C00就是bootsect.s的入口地址。注:启动设备通常是软盘或硬盘。在现代计算机中,启动设备几乎都是硬盘。


boot目录下只有三个汇编文件,如下图,bootsect.s和setup.s是as86汇编语法编写,是实模式下运行的16位代码程序。而head.s通过gas汇编语法编写,运行在保护模式下。之所以使用两种汇编语法是因为gas直到1994年才开始支持编译实模式下16位程序的。所以Linux2.4x版本的内核只有gas汇编语法。

bios 硬盘引导 boot引导硬盘启动_操作数


如上述所言,系统启动时最先执行的就是BIOS程序,然后是bootsect.s,在内核执行bootsect.s程序时,会把bootsect.s程序移动到ox90000处,bootsect.s长0.5k,也就是长0x200。然后把setup.s程序移动到0x90200开始处,setup.s长,而内核其他部分(system模块)则被读入到从内存地址0x10000处,在系统加载期间会显示loading,然后将控制权交到setup.s。因此系统上电后的各程序加载顺序如下图所示:

bios 硬盘引导 boot引导硬盘启动_寄存器_02


系统启动时引导代码在内存中的动态位置如下图所示。

bios 硬盘引导 boot引导硬盘启动_寄存器_03


setup.s代码识别主机的某些特性以及VGA卡的类型。然后将系统从地址0x10000处移动到0x0000处,进入保护模式并转至系统的剩余部分(0x00000)。此时32位的运行方式的设置启动被完成:IDT、GDT、LDT被加载,处理器和协处理器也已经确认,分页工作也设置好;最终调用init/main.c中的main()程序。以上是head.s代码执行的步骤。至此,系统上电之后开始从0xFFFF0地址处开始执行BIOS程序,执行系统的某些检测,然后初始化中断向量,再将bootsect.s程序加载到0x7C00处。并在执行bootsect.s的同时将自己移动到0x90000处,然后将setup.s代码移动到0x90200开始处,将系统其他部分移动到0x10000开始处。执行setup.s,执行的过程当中,它会要求将系统的剩余部分移动到0x0000处,并开始执行head.s。

那么为什么setup.s不直接在0x0000处开始执行而是在0x90200处,这是因为setup.s在执行的过程当中需要用到BIOS的中断向量表,而中断向量表是存在0地址处的,大小为0x400(1k)个字节。需要等使用完BIOS中断调用才能覆盖。
而Linux系统需要运行,还需要最基本的文件系统支持,也就是跟目录。跟目录的默认块设备号也在bootsect.s中定义。


bootsect.s程序

bootsect.s用于x86上创建启动引导扇区,是引导扇区的第一个程序,而引导扇区位于硬盘的第一个扇区,大小为512字节,引导扇区包含了系统启动的必要信息,如操作系统的文件位置,文件系统类型等。系统启动时,BIOS首先读取引导扇区,并将执行权移交引导扇区上的bootsect.s。

前面说到bootsect.s执行期间会将自己移动到0x90000开始处并继续执行,而bootsect.s的主要作用是将setup.s模块加载到0x90200开始处,然后利用BIOS中断0x13取磁盘参数表中的引导盘参数。接着在屏幕上打印Loading system…。然后将setup.s后面的system模块加载到0x10000开始处。随后确定根文件系统的设备号(如果没有指定根文件设备号,则根据引导盘的每磁道扇区数判别盘的类型(是否为1.44M A盘)),并将其保存在root_dev(引导块的508地址处)。最后长跳转到setup.s程序开始处(0x90200)执行setup.s。

总的来说,bootsect.s做了四件事情:

1 将自己移动到0x90000开始处,并加载setup.s到0x90200处。

2 利用BIOS中断0x13取磁盘参数表中启动引导盘的参数 加载setup.s模块到0x90200处,加载system模块到0x10000处

3 将根文件设备号存在root_dev中。

4 长跳转到setup.s处开始执行setup.s

Linux0.11内核在1.44M磁盘所占扇区的分布情况如下图:

bios 硬盘引导 boot引导硬盘启动_bios 硬盘引导_04


boot扇区占一个扇区,setup模块占四个扇区。system模块占240个扇区。而1.44M盘共有2880个扇区。剩下的没有用到的扇区用来存放一个基本的根文件系统。


86as汇编

一句86as汇编代码通常由标号(可选)、指令名、操作数组成。标号通常用冒号结尾。如begdata:。
.globl和.global用于定义随后的标识符是外部的还是全局的。
.text、.date、.bss分别用于定义当前代码段、数据段和未定义数据段。

ds和es:ds(data segment register)数据寄存器,用于存储程序的全局变量和静态变量。es(extra segment register)用于存储其他数据段,如常量、字符串、数组等。
ax和bx:ax是累加器寄存器,一般用于保存计算的中间结果或函数返回值。bx是基址寄存器,用于存储偏移地址。
cx:计数器寄存器,用于循环控制。
sub 目标操作数,原操作数:目标操作数=目标操作数-原操作数
rep,结合ax使用,ax用于循环控制,rep是重复执行并递减ax,直至ax=0。需要注意的是,rep执行循环的语句是紧跟其后的一条语句,例如在Linux源码中:

mov	cx,#256
	sub	si,si
	sub	di,di
	rep
	movw

重复的部分只有紧跟rep后的movw。也就是将原地址的256个字复制到目标地址di中。
si和di:si用于存储原数据地址或偏移量,di用于存储目标地址或偏移量。
cs寄存器存储代码段的基地址。
ds寄存器存储数据段的基地址。
es寄存器通常用于存储额外的数据段基地址。
ss寄存器存储栈段的基地址。

寄存器名字

作用

ax

通用,累加器

bx

通用,存储偏移地址

cx

通用,计数器

si

原地址

di

目标地址

ss

段寄存器,堆栈寄存器

cs

段寄存器,代码段寄存器

ds

段寄存器,数据段寄存器

sp

通用寄存器,存堆栈偏移量

bp

通用寄存器,存堆栈基地址

dx

通用,数据寄存器,存临时数据

代码名称

代码基地址

代码长度

bootseg

0x7c00

bootsect.s(initseg)

0x90000

0x200(1个扇区)

setup.s

0x90200

四个扇区

system模块

0x10000

240扇区

jnc:有条件jmp,如果CF标志寄存器为0则跳转,否则继续

bios 硬盘引导 boot引导硬盘启动_运维_05


gas汇编语法记录

  • div
// 语法:div operand
__asm__("divl %4":"=a" (block),"=d" (sec):"0" (block),"1" (0),
		"r" (hd_info[dev].sect));

operand为被除数。而除数则需要放在紧随着div指令的另一个操作数中。商保存在被除数所在的寄存器中,余数则保存在紧随着该寄存器的另一个寄存器中。

在这句代码中,block被保存在eax寄存器中,而hd_info[dev].sect则作为除数的操作数直接放在divl指令的操作数中。:“0” (block),“1” (0),表示将block值传入0个操作数,将0传入第一个操作数