系统启动bootsect.S
一般PC在电源一开时,是由内存中地址FFFF:0000开始执行(这个地址一定在ROM BI
OS中,ROM BIOS一般是在FEOOOh到FFFFFh中),而此处的内容则是一个jump指令,jump到另一个位於ROM BIOS中的位置,开始执行一系列的动作,包括了检查RAM,keyboard,显示器,软硬磁盘等等,这些动作是由系统测试代码 (POST,system test code)来执行的,随着制作BIOS厂商的不同而会有些许差异,但都是大同小异,读者可自行观察自家机器开机时,萤幕上所显示的检查讯息。
紧接着系统测试码之后,控制权会转移给ROM中的启动程序(ROM bootstrap routine),这个程序会将磁盘上的第零轨第零扇区读入内存中(这就是一般所谓的boot sector,如果你曾接触过电脑病毒,就大概听过它的大名),至於被读到内存的哪里呢? --绝对位置07C0:0000(即07C00h处),这是IBM系列PC的特性。而位在linux开机磁盘的boot sector上的正是linux的bootsect程序,也就是说,bootsect是第一个被读入内存中并执行的程序。现在,我们可以开始来看看到底bootsect做了什么。
第一步
首先,bootsect将它"自己"从被ROM BIOS载入的绝对地址0x7C00处搬到0x90000处,
然后利用一个jmpi(jump indirectly)的指令,跳到新位置的jmpi的下一行去执行,
movw $BOOTSEG, %ax #$BOOTSEG = 0x07C0
movw %ax, %ds
movw $INITSEG, %ax #$INITSEG = DEF_INITSEG=0x0x9000
movw %ax, %es
movw $256, %cx #256word s== 512bytes
subw %si, %si
subw %di, %di
cld
rep
movsw
ljmp $INITSEG, $go
第二步
接着,将其他segment registers包括DS,ES,SS都指向0x9000这个位置,与CS看齐
。另外将SP及DX指向一任意位移地址( offset ),这个地址等一下会用来存放磁盘参数
表(disk para- meter table )
go: movw $0x4000-12, %di # 0x4000 is an arbitrary value >=
# length of bootsect + length of
# setup + room for stack;
# 12 is disk parm size.
movw %ax, %ds # ax and es already contain INITSEG
movw %ax, %ss
movw %di, %sp # put stack at INITSEG:0x4000-12.sp=0x94000-12,12byte放磁盘参数
第三步
接着利用BIOS中断服务int 13h的第0号功能,重置磁盘控制器,使得刚才的设定发
挥功能。主要是为了一次读扇区数目增加,增加效率。
# Many BIOS's default disk parameter tables will not recognize
# multi-sector reads beyond the maximum sector number specified
# in the default diskette parameter tables - this may mean 7
# sectors in some cases.
#
# Since single sector reads are slow and out of the question,
# we must take care of this by creating new parameter tables
# (for the first disk) in RAM. We will set the maximum sector
# count to 36 - the most we will encounter on an ED 2.88.
#
# High doesn't hurt. Low does.
#
# Segments are as follows: ds = es = ss = cs = INITSEG, fs = 0,
# and gs is unused.
movw %cx, %fs # set fs to 0
movw $0x78, %bx # fs:bx is parameter table address,磁盘参数已经由BIOS读到这个地址了,就是物理地址0x78
pushw %ds
ldsw %fs:(%bx), %si # ds:si is source ds:si==0x0000:0x78
movb $6, %cl # copy 12 bytes
pushw %di # di = 0x4000-12.
rep # don't need cld -> done on line 66
movsw #ds:si--àes:di
popw %di
popw %ds
movb $36, 0x4(%di) # patch sector count,用36重置
movw %di, %fs:(%bx)
movw %es, %fs:2(%bx)
xorb %ah, %ah # reset FDC
xorb %dl, %dl
int $0x13
第四步
完成重置磁盘控制器之后,bootsect就从磁盘上读入紧邻着bootsect的setup程序,
也就是setup.S,此读入动作是利用BIOS中断服务int 13h的第2号功能。setup的image将
会读入至程序所指定的内存绝对地址0x90200处,也就是在内存中紧邻着bootsect 所在
的位置。待setup的image读入内存后,利用BIOS中断服务int 13h的第2号功能读取目前
磁盘的参数。
xorw %dx, %dx # drive 0, head 0
movb $0x02, %cl # sector 2, track 0
movw $0x0200, %bx # address = 512, in INITSEG ,0x90200
movb $0x02, %ah # service 2, "read sector(s)"
movb setup_sects, %al # (assume all on head 0, track 0) setup_sects=4,读取的扇区数
int $0x13 # read it,setup.S位于内存物理地址0x90200,
jnc ok_load_setup # ok – continue
#读盘错误处理
pushw %ax # dump error code 出错处理,ax得到错误码
call print_nl
movw %sp, %bp
call print_hex
popw %ax
jmp load_setup
ok_load_setup:
# Get disk drive parameters, specifically number of sectors/track.
# It seems that there is no BIOS call to get the number of sectors.
# Guess 36 sectors if sector 36 can be read, 18 sectors if sector 18
# can be read, 15 if sector 15 can be read. Otherwise guess 9.
movw $disksizes, %si # table of sizes to try 在文件末尾定义了数据disksizes: .byte 36, 18, 15, 9
probe_loop:
lodsb #(AL)<-((SI)),(SI)<-(SI)+1
cbtw # extend to word,al->ax
movw %ax, sectors #sectors: .word 0
cmpw $disksizes+4, %si
jae got_sectors #jae不低于,或者高于或者等于,或进位位为0则转移If all else fails, try 9。就是36,18,15,9都测试过了,就跳到got_sectors
xchgw %cx, %ax # cx = track and sector
xorw %dx, %dx # drive 0, head 0
xorb %bl, %bl
movb setup_sects, %bh
incb %bh
shlb %bh # address after setup (es = cs)
movw $0x0201, %ax # service 2, 1 sector
int $0x13
jc probe_loop # try next value,就是从0头,0磁道,读取第36,18,15号扇区,看能否成功,如果成功就跳到got_sectors,否则就再测试下一个数。把内容读到setup.S后面的内存中,这些扇区内容没有什么意义,只是为了测试sectors/track。
第五步
再来,就要读入真正linux的kernel了,也就是你可以在linux的根目录下看到的"v
mlinuz" 。在读入前,将会先呼叫BIOS中断服务int 10h 的第3号功能,读取游标位置,
之后再呼叫BIOS 中断服务int 10h的第13h号功能,在萤幕上输出字串"Loading",这个
字串在boot linux时都会首先被看到,相信大家应该觉得很眼熟吧。
got_sectors:
movw $INITSEG, %ax
movw %ax, %es # set up es
movb $0x03, %ah # read cursor pos
xorb %bh, %bh
int $0x10 #调用10号中断,功能3,读取光标的位置
movw $9, %cx
movw $0x0007, %bx # page 0, attribute 7 (normal)
movw $msg1, %bp
movw $0x1301, %ax # write string, move cursor
int $0x10 # tell the user we're loading..在光标处打印msg1的内容,msg1=’loading…..’;
movw $SYSSEG, %ax # ok, we've written the message, now ,$SYSSEG=0x1000
movw %ax, %es # we want to load system (at 0x10000)
call read_it #读取vmlinux到0x10000,大小为0x7f00,会已最快的速度去读,方法就是用前面测试的sectors/track,一次读完整个磁道。
call kill_motor
call print_nl
第六步
接下来做的事是检查root device,之后就仿照一开始的方法,利用indirect jump
跳至刚刚已读入的setup部份
# 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, one of /dev/fd0H2880 (2,32) or /dev/PS0 (2,28) or /dev/at0 (2,8)
# depending on the number of sectors we pretend to know we have.
movw root_dev, %ax
orw %ax, %ax
jne root_defined
movw sectors, %bx
movw $0x0208, %ax # /dev/ps0 - 1.2Mb
cmpw $15, %bx
je root_defined
movb $0x1c, %al # /dev/PS0 - 1.44Mb
cmpw $18, %bx
je root_defined
movb $0x20, %al # /dev/fd0H2880 - 2.88Mb
cmpw $36, %bx
je root_defined
movb $0, %al # /dev/fd0 - autodetect
root_defined:
movw %ax, root_dev
# After that (everything loaded), we jump to the setup-routine
# loaded directly after the bootblock:
ljmp $SETUPSEG, $0 #jmp to 0x90200