通过前面两篇的介绍,相信对搭建Linux 0.12编译环境的诸多工具有了基础的了解,但不论是Bochs还是Linux主机,它们只是你搭建过程中的棋子而已,因为真正的工作环境是Linux 0.12 OS,前面铺垫了很多就是为了它能快速简单的搭建。
下面两篇的内容个人觉得很赞,是搭建Linux 0.12 OS的重点。首先通过这篇文章我们来了解一下Linux 0.12的启动过程,然后学习如何修改bootsect.s使其从硬盘启动。这是一个非常酷的定制工作,我们按照自己的想法来实现它,脱离原来的束缚且从中可以学到更多知识。
[硬盘规划].
硬盘就是我们所说的启动盘,它包括了boot扇区、内核映像(setup & system)、文件系统以及swap分区。还记得Linux 0.11吗,启动它需要两个软盘文件,一个包含了boot扇区和内核映像,一个包含了文件系统,如果是Linux 0.12只需在制作一个swap设备即可。硬盘唯一的好处就是将它们都整合在了一起。
这样就需要对硬盘进行相应的规划,基本雷打不动的就是boot扇区,它在开机的时候需要被BIOS加载,因此硬盘的第一扇区就是boot扇区。至于文件系统和swap分区,它们分别作为两个分区。内核映像非常关键,因为boot要找到它然后把它加载到内存中运行,所以我们采用了一种布局,直接将它放在boot扇区之后,即第一分区里面。后面你就会发现这样做可以减轻改写boot代码的难度。
[启动过程].
我们来回顾一下Linux 0.12的启动过程,我把关键的部分拿出来:
1. BIOS把启动盘的第一个扇区(bootsect)拷贝到物理地址0x7C00处
2. bootsect拷贝自己到物理地址0x90000处
3. bootsect拷贝内核映像setup(4个扇区)到物理地址0x90200处
4. bootsect拷贝内核映像system(0x3000*16 bytes = 192K)到物理地址0x10000处
后面的步骤不多述了,和这篇的主题无关。对于boot到这儿就差不多了,完整的描述了bootsect.S的功能。不要忘记了我们说启动过程的目的,在回想一下,Linux 0.11用一张软盘来存放了boot扇区和内核映像,其实心细的你已经发现上面提到的硬盘里面的boot扇区和内核映像布局其实保持了一致性,因此这里你就可以清楚明白这样做的目的完全是为了减轻改写boot代码的难度。既然要改写,说明软盘和硬盘的读取参数(BIOS interrupt 13h)上有些不同,主要是因为软盘和硬盘的结构不同,软盘(通常指1.44M软盘)每个磁道的扇区数为18,磁头数为2,而硬盘每个磁道的扇区数为63,磁头数为16,因此我们需要对bootsect.S做个小手术。
[硬盘boot].
这里我已经将bootsect.S用ATT汇编进行了重写,在此基础上进行修改使其能从硬盘中boot起来。
#
# SYS_SIZE is the number of clicks (16 bytes) to be loaded.
# 0x3000 is 0x30000 bytes = 196kB, more than enough for current
# versions of linux
#
#include <linux/config.h>
SYSSIZE = DEF_SYSSIZE
#
# bootsect.s (C) 1991 Linus Torvalds
# modified by Drew Eckhardt
#
# bootsect.s is loaded at 0x7c00 by the bios-startup routines, and moves
# iself out of the way to address 0x90000, and jumps there.
#
# It then loads 'setup' directly after itself (0x90200), and the system
# at 0x10000, using BIOS interrupts.
#
# NOTE! currently system is at most 8*65536 bytes long. This should be no
# problem, even in the future. I want to keep it simple. This 512 kB
# kernel size should be enough, especially as this doesn't contain the
# buffer cache as in minix
#
# The loader has been made as simple as possible, and continuos
# read errors will result in a unbreakable loop. Reboot by hand. It
# loads pretty fast by getting whole sectors at a time whenever possible.
.code16
.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 = DEF_INITSEG # we move boot here - out of the way
SETUPSEG = DEF_SETUPSEG # setup starts here
SYSSEG = DEF_SYSSEG # system loaded at 0x10000 (65536).
ENDSEG = SYSSEG + SYSSIZE # where to stop loading
# ROOT_DEV & SWAP_DEV are now written by "build".
ROOT_DEV = 0
SWAP_DEV = 0
.globl boot_start
boot_start:
/*
* BIOS会把一下代码拷贝到物理内存地址0x7C00处
*/
mov $BOOTSEG,%ax
mov %ax,%ds
/*
* 拷贝自己到物理内存地址0x90000处
*/
mov $INITSEG,%ax
mov %ax,%es
mov $256,%cx
sub %si,%si
sub %di,%di
rep movsw
ljmp $INITSEG,$go
go: mov %cs,%ax
mov $0xfef4,%dx # arbitrary value >>512 - disk parm size
mov %ax,%ds
mov %ax,%es
push %ax
mov %ax,%ss # put stack at 0x9ff00 - 12.
mov %dx,%sp
/*
* 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 18 - the most we will encounter on an HD 1.44.
*
* High doesn't hurt. Low does.
*
* Segments are as follows: ds=es=ss=cs - INITSEG,
* fs = 0, gs = parameter table segment
*/
push $0
pop %fs
mov $0x78,%bx # fs:bx is parameter table address
# seg fs
push %ds
lds %fs:(%bx),%si # ds:si is source
mov %dx,%di # es:di is destination
mov $6,%cx # copy 12 bytes
cld
rep movsw
pop %ds
mov %dx,%di
movb $18,4(%di) # patch sector count
# seg fs
mov %di,%fs:(%bx)
# seg fs
mov %es,%fs:2(%bx)
pop %ax
mov %ax,%fs
mov %ax,%gs
xor %ah,%ah # reset FDC
xor %dl,%dl
int $0x13
# load the setup-sectors directly after the bootblock.
# Note that 'es' is already set up.
/*
* 拷贝内核映像setup(4个扇区)到物理内存地址0x90200处
*
* int 13h - driver test PS/2
* ah = 00h - reset disk drive
* ah = 01h - get status of last drive operation
* ah = 02h - read sectors from drive
* ah = 03h - write sectors to drive
* ah = 04h - verify sectors from drive
* ah = 05h - format track
* ah = 06h - format track set bad sector flags
* ah = 07h - format drive starting at track
* ah = 08h - read drive parameters
* ...
* parameters:
* al : sectors of read count
* cl[7:6]+ch[7:0] : track, [0,]
* cl[5:0] : sector, [1,]
* dh : head, [0,]
* dl : drive, (floppy A =00h, harddisk =80h)
* es:bx : buffer address pointer
*
* result:
* cf : set on error, clear if no error
* ah : return code
* al : actual sectors read count
*/
load_setup:
mov $0x0080,%dx # drive 0, head 0
mov $0x0002,%cx # sector 2, track 0
mov $0x0200,%bx # address = 512, in INITSEG
mov $(0x0200+SETUPLEN),%ax # service 2, nr of sectors
int $0x13 # read it
jnc ok_load_setup # ok - continue
# push %ax # dump error code
# call print_nl
# mov %sp,%bp
# call print_hex
# pop %ax
xor %dl,%dl # reset FDC
xor %ah,%ah
int $0x13
jmp load_setup
ok_load_setup:
# Get disk drive parameters, specifically nr of sectors/track
/*
* 获取磁盘参数
* track_tl : 柱面数
* head_tl : 磁头数
* sectors : 扇区数
*
* parameters:
* dl : drive index
*
* result:
* cf : set on error, clear if no error
* ah : return code
* dl : number of hard disk drives
* dh : logical last index of heads
* cx : [7:6][15:8] logical last index of cylinders,
* [5:0] logical last index of sectors per track
* bl : drive type (only AT/PS2 floppies)
*/
mov $0x80,%dl
mov $0x0800,%ax # AH=8 is get drive parameters
int $0x13
mov %cl,%al
shr $6,%al
mov %al,%ah
mov %ch,%al
movw %ax,track_tl
mov %dh,%dl
xor %dh,%dh
movw %dx,head_tl
xor %ch,%ch
and $0x3f,%cl # cl(6~7)=0
movw %cx,sectors
mov $INITSEG,%ax
mov %ax,%es
# Print some inane message
mov $0x03,%ah # read cursor pos
xor %bh,%bh
int $0x10
mov $24,%cx
mov $0x0007,%bx # page 0, attribute 7 (normal)
mov $msg1,%bp
mov $0x1301,%ax # write string, move cursor
int $0x10
# ok, we've written the message, now
# we want to load the system (at 0x10000)
mov $SYSSEG,%ax
mov %ax,%es # segment of 0x010000
call read_it
# call kill_motor
# call print_nl
# 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 root_dev,%ax
or %ax,%ax
jne root_defined
# seg cs
mov sectors,%bx
mov $0x0208,%ax # /dev/ps0 - 1.2Mb
cmp $15,%bx
je root_defined
mov $0x021c,%ax # /dev/PS0 - 1.44Mb
cmp $18,%bx
je root_defined
undef_root:
jmp undef_root
root_defined:
# seg cs
mov %ax,root_dev
# after that (everyting loaded), we jump to
# the setup-routine loaded directly after
# the bootblock:
ljmp $SETUPSEG,$0
# This routine loads the system at address 0x10000, making sure
# no 64kB boundaries are crossed. We try to load it as fast as
# possible, loading whole tracks whenever we can.
#
# in: es - starting address segment (normally 0x1000)
#
sread: .word 1+SETUPLEN # sectors read of current track [0,]
head: .word 0 # current head [0,]
track: .word 0 # current track [0,]
/*
* 拷贝内核映像system(0x3000*16 bytes = 192K)到物理地址0x10000处
*/
read_it:
mov %es,%ax
test $0x0fff,%ax
die: jne die # es must be at 64kB boundary
xor %bx,%bx # bx is starting address within segment
/*
* 判断是否将内核映像system拷贝完成
*/
rp_read:
mov %es,%ax
cmp $ENDSEG,%ax # have we loaded all yet?
jb ok1_read /* 若ax >= ENDSEG,则拷贝完成 */
ret
/*
* 把一个磁道的扇区读完否则把64K的段地址读满
*/
ok1_read:
mov sectors,%ax
sub sread,%ax /* 把一个磁道的剩余扇区全读了 */
mov %ax,%cx
shl $9,%cx
add %bx,%cx /* 这里有暗示,进位只可能为1,因为把一个磁道的扇区全部
读完也只有63*512 = 7e00h字节 < 10000h 64K段 */
jnc ok2_read /* CF为0,说明64K段还有剩余 */
je ok2_read /* CF为1,ZF为1, 说明刚好把64K段读满了 */
xor %ax,%ax /* 读取的扇区数超过了64K段 */
sub %bx,%ax
shr $9,%ax /* 把64K段读满,需要读的扇区数 */
/*
* 读完一次磁盘后调整下次需要读取磁盘的位置(sread, head, track)
*/
ok2_read:
call read_track
mov %ax,%cx
add sread,%ax
cmp sectors,%ax
jne ok3_read /* ZF为0,说明一个磁道的扇区都还没读完,可以不做调整 */
incw head
mov head,%ax
cmpw head_tl,%ax
jne ok4_read /* ZF为0,说明一个柱面的磁头都还没读完 */
incw track /* 否则,需要调整到一下一个柱面上 */
xor %ax,%ax
/*
* 调整后的磁头号和初始化起始扇区为0
*/
ok4_read:
movw %ax,head
xor %ax,%ax
/*
* 调整下次需要拷贝到的物理内存位置(es:bx)
*/
ok3_read:
movw %ax,sread
shl $9,%cx /* cx是之前保存的当前读取的扇区数 */
add %cx,%bx
jnc rp_read /* CF为0,说明64K段还没读满,可以不做调整 */
mov %es,%ax
add $0x1000,%ax /* CF为1,需要将es段加1个64K段 */
mov %ax,%es
xor %bx,%bx /* 一个64K段刚好读完了,下一个段开始了 */
jmp rp_read
/*
* 读取磁盘数据
*
* parameters:
* ax : sectors of read count
* sread : current sector [0,]
* head : current head [0,]
* track : current track [0,]
* es:bx : buffer address pointer
*
* result:
*
* parameters:
* al : sectors of read count
* cl[7:6]+ch[7:0] : track, [0,]
* cl[5:0] : sector, [1,]
* dh : head, [0,]
* dl : drive, (floppy A =00h, harddisk =80h)
* es:bx : buffer address pointer
*/
read_track:
push %ax
push %bx
push %cx
push %dx
mov track,%dx
mov sread,%cx
inc %cx
mov %dl,%ch
shl $6,%dh
or %dh,%cl
mov head,%dx
mov %dl,%dh
mov $0x80,%dl
mov $0x02,%ah
int $0x13
jc bad_rt
pop %dx
pop %cx
pop %bx
pop %ax
ret
bad_rt:
mov $0,%ax
mov $0x80,%dx
int $0x13
pop %dx
pop %cx
pop %bx
pop %ax
jmp read_track
/*
* 下面print函数需要去掉的原因是boot扇区512B空间不够了
*/
/*
* print_all is for debugging purposes.
* It will print out all of the registers. The assumption is that this is
* called from a routine, with a stack frame like
* dx
* cx
* bx
* ax
* error
* ret <- sp
*
*/
# because of size > 512 byte, so need decrease these
/*
print_all:
mov $5,%cx # error code + 4 registers
mov %sp,%bp
print_loop:
push %cx # save count left
call print_nl # nl for readability
jae no_reg # see if register name is needed
mov $0xe05 + 0x41 - 1,%ax
sub %cl,%al
int $0x10
mov $0x58,%al # X
int $0x10
mov $0x3a,%al # :
int $0x10
no_reg:
add $2,%bp # next register
call print_hex # print it
pop %cx
loop print_loop
ret
print_nl:
mov $0xe0d,%ax # CR
int $0x10
mov $0xa,%al # LF
int $0x10
ret
*/
/*
* print_hex is for debugging purposes, and prints the word
* pointed to by ss:bp in hexadecmial.
*/
/*
print_hex:
mov $4,%cx # 4 hex digits
mov (%bp),%dx # load word into dx
print_digit:
rol $4,%dx # rotate so that lowest 4 bits are used
mov $0xe,%ah
mov %dl,%al # mask off so we have only next nibble
and $0xf,%al
add $0x30,%al # convert to 0 based digit, '0'
cmp $0x39,%al # check for overflow
jbe good_digit
add $0x41 - 0x30 - 0xa,%al # 'A' - '0' - 0xa
good_digit:
int $0x10
loop print_digit
ret
*/
/*
* This procedure turns off the floppy drive motor, so
* that we enter the kernel in a known state, and
* don't have to worry about it later.
*/
kill_motor:
push %dx
mov $0x3f6,%dx
mov $0,%al
outb %al,%dx
pop %dx
ret
sectors: # total sect
.word 0
head_tl: # total head
.word 0
track_tl: # total track
.word 0
msg1:
.byte 13,10
.ascii "Loading system ..."
.byte 13,10,13,10
/*
* 定位这里的目的是从0x1bc开始是存放硬盘分区信息的位置
*/
.org 0x1bc
end_end:
.byte 0
/*
* 存放交换分区、文件系统分区设备号
*/
.org 506
swap_dev:
.word SWAP_DEV
root_dev:
.word ROOT_DEV
boot_flag:
.word 0xAA55
.text
endtext:
.data
enddata:
.bss
endbss: