当机器加电后,首先强制把CPU里面的CS:IP设置为:CS=0xF000,IP=0xFFF0。这是CPU与BIOS开发商之间做的约定,是整个系统能够跑的起来的逻辑起始点。

0xFFFF0处就是BIOS代码所在的ROM区域的开头,在CS:IP指向此处后会执行BIOS的基本IO设备的检查,等BIOS检查完毕后,开始把启动设备(比如磁盘、光驱、软盘等)的0盘面0磁道第1扇区载入到内存的0x07C00处。最后BIOS跳转到0x07C00,把控制权交给操作系统的boot引导程序。 注意这个0x07C00是BIOS的约定,他们可以自由选择把操作系统的引导区从磁盘中拷贝到那个位置。例如在Seabios的源码里面:https://git.seabios.org/cgit/seabios.git/tree/src/boot.c 就是直接硬编码这个地址的:

bios 原代码 bios源代码剖析_ios

而这个0盘面0磁道1扇区,就是引导区,这个扇区的512字节的内容就是bootsect.s汇编代码汇编后得到的。

接下来我们看看这段代码做了什么:

首先是定义了几个段的段基址:
BOOTSEG : BOOT段地址
INITSEG : 初始化地址,将来会把boot引导区的代码先拷贝到这里去
SETUPSEG: setup的启动段基址
SYSSEG : SYS的段基址

.globl begtext, begdata, begbss, endtext, enddata, endbss
.text
begtext:
.data
begdata:
.bss
begbss:
.text

SETUPLEN = 4				! nr of setup-sectors
BOOTSEG  = 0x07c0			! original address of boot-sector
INITSEG  = 0x9000			! we move boot here - out of the way
SETUPSEG = 0x9020			! setup starts here
SYSSEG   = 0x1000			! system loaded at 0x10000 (65536).
ENDSEG   = SYSSEG + SYSSIZE		! where to stop loading

! ROOT_DEV:	0x000 - same type of floppy as boot.
!		0x301 - first partition on first drive etc
ROOT_DEV = 0x306

接下来,进入start:

entry _start
_start:
	mov	ax,#BOOTSEG
	mov	ds,ax
	mov	ax,#INITSEG
	mov	es,ax
	mov	cx,#256
	sub	si,si
	sub	di,di
	rep
	movw
	jmpi	go,INITSEG
go:	···

这段代码主要是想把BOOTSEG段的512字节的boot代码拷贝到INITSEG所在的段里面去。注意movw这个指令是一次性拷贝两个字节的内容,因此cx的初始值是256。
而最后 jmpi go,INITSEG。则是把INITSEG的值给CS,把go的偏移量给IP。之所以要这么做是因为,上面已经完成了将boot拷贝到0x9000段的工作。在0x07c0和0x9000这两个段里面有两段一模一样的boot引导代码。把INITSEG给CS是想调到0x9000开始的段去执行,而把go的编译量给IP是想让CPU直接紧跟着 jmpi go,INITSEG 后一条指令继续执行的。

go:	mov	ax,cs
	mov	ds,ax
	mov	es,ax
! put stack at 0x9ff00.
	mov	ss,ax
	mov	sp,#0xFF00		! arbitrary value >>512

! load the setup-sectors directly after the bootblock.
! Note that 'es' is already set up.

CS转到0x9000段后,相继将ds,es,ss三个段寄存器设置过来,同时设置sp寄存器的值,将栈顶设置为0x9FF00.

load_setup:
	mov	dx,#0x0000		! drive 0, head 0
	mov	cx,#0x0002		! sector 2, track 0
	mov	bx,#0x0200		! address = 512, in INITSEG
	mov	ax,#0x0200+SETUPLEN	! service 2, nr of sectors
	int	0x13			! read it
	jnc	ok_load_setup		! ok - continue
	mov	dx,#0x0000
	mov	ax,#0x0000		! reset the diskette
	int	0x13
	j	load_setup

紧接着,使用0x13中断,将启动盘的第2个扇区开始,共SETUPLEN(4)个扇区的内容读取到es:0x020 (也就是0x90200)地址去。
如果读取正确跳转到ok_load_setup ,否则重置dx,ax 循环执行load_setup。

ok_load_setup:

! Get disk drive parameters, specifically nr of sectors/track

	mov	dl,#0x00
	mov	ax,#0x0800		! AH=8 is get drive parameters
	int	0x13
	mov	ch,#0x00
	seg cs
	mov	sectors,cx
	mov	ax,#INITSEG
	mov	es,ax

! Print some inane message

	mov	ah,#0x03		! read cursor pos
	xor	bh,bh
	int	0x10
	
	mov	cx,#24
	mov	bx,#0x0007		! page 0, attribute 7 (normal)
	mov	bp,#msg1
	mov	ax,#0x1301		! write string, move cursor
	int	0x10

! ok, we've written the message, now
! we want to load the system (at 0x10000)

	mov	ax,#SYSSEG
	mov	es,ax		! segment of 0x010000
	call	read_it
	call	kill_motor

接下来是载入SYSTEM部分,也一样是使用0x13中断来完成了,把SYSTEM部分载入到0x10000开始的段里面。同时还会使用0x10打印“Loading system…”的提示字符串。

msg1:
	.byte 13,10
	.ascii "Loading system ..."
	.byte 13,10,13,10

读取完SYSTEM后,就是准备一下根文件系统等机器数据了,这部分不重要。

! After that we check which root-device to use. If the device is
! defined (!= 0), nothing is done and the given device is used.
! Otherwise, either /dev/PS0 (2,28) or /dev/at0 (2,8), depending
! on the number of sectors that the BIOS reports currently.

	seg cs
	mov	ax,root_dev
	cmp	ax,#0
	jne	root_defined
	seg cs
	mov	bx,sectors
	mov	ax,#0x0208		! /dev/ps0 - 1.2Mb
	cmp	bx,#15
	je	root_defined
	mov	ax,#0x021c		! /dev/PS0 - 1.44Mb
	cmp	bx,#18
	je	root_defined
undef_root:
	jmp undef_root
root_defined:
	seg cs
	mov	root_dev,ax

最后,使用jmpi 0,SETUPSEG 跳转到Setup所在的代码段,也就是:CS=0x90200,IP=0。而0x90200正好就是刚刚从启动盘的第1到第5扇区的内容。

! after that (everyting loaded), we jump to
! the setup-routine loaded directly after
! the bootblock:

	jmpi	0,SETUPSEG