分类: LINUX
2008-08-26 17:26:10
/*
* i386CPU加电或"总清"(reset)以后CPU处于实地址模式,并且代码段寄存器CS的内容为0xffff,而取指令指针
* (寄存器)IP的内容为0,也就是说,CPU加电或"总清"后从线性地址0xffff0开始取第一条指令.所以采用i386
* CPU的系统在0xffff0的位置上需有不挥发存储器.
*/
/*
* ===================== 内存结构图(会随着setup的运行而变动),说明部分见P666 =========================
*
* |------------------------|
* | |
* | |
* | |
* | |
* | |
* | |
* | | ▲ 高内存
* | | |
* | | |
* | |
* |------------------------| 0x100000(1MB)<--|
* | | |
* | | |
* | | |--用于图形接口卡以及BIOS本身
* | | |
* | | |
* |-->|------------------------| 0xA0000 <--|----|
* | | | |
* | | setup.S所在内存 | |
* | | | |
* | |------------------------| 0x90200 |--64KB
* | | | |
* | | bootsect.S所在内存 | |
* | | 以后会存放一些参数 | |
* | | | |
* | |------------------------| 0x90000 <--|----|
* | | | |
* | | 4KB用于引导命令行 | |
* | | (LILO支持引导命令行) | |
* | | 及 | |
* | | 一些需传递给内核的数据| |
* | | (从BIOS收集得到) | |
* | | | |
* | |------------------------| |
* | | | |
* | | | |
* (640KB系统基本内存)--| | 508KB | |--512KB
* | | | |
* | | | |
* | |------------------------| 0x10000 <--|----|
* | | | |
* | | BIOS保留使用 | |--64KB
* | | Linux引导保留部分空间 | |
* | | | |
* |-->|------------------------| 0 <--|
*
*
* 内核一般经过压缩,压缩后的内核就像一大块数据,跟引导扇区和引导辅助程序的映象拼接在一起,成为内核
* 的"引导映象".大小不超过508KB的引导映象称为"小映象",文件名为zImage,否则为"大内核",文件名为bzImage
* 由于bzImage在基本内存装载不下,所以要装载在0x100000(1MB)的地方. 不过,不管是zImage还是bzImage,
* 解除压缩后的内核映象总放在地址为0x100000(1MB)的地方.
*
* arch/i386/boot/tools中的build.c用来拼接引导映象的工具
*
* ================================================================================================= */
/*
* bootsect.S Copyright (C) 1991, 1992 Linus Torvalds
*
* modified by Drew Kckhardt
* modified by Bruce Evans (bde)
* modified by Chris Noe (May 1999) (as86 -> gas)
*
* bootsect is loaded at 0x7c00 by the bios-startup routines, and moves itself out of the
* way to address 0x90000, and jumps there.
*
* bde - should not jump blindly(盲目地,摸索地), there may be systems with only 512k low
* memory. Use int 0x12 to get the top of memory, etc.
*
* It then loads 'setup' directly after itself (0x90200), and the system at 0x10000, using
* BIOS interrupts.
*
* NOTE! currently system is at most (8*65536-4096)(即8*64KB-4KB,见上图)) bytes long.
* This should be no problem, even in the future. I want to keey it simple. This 508 kB
* kernel size should be enough, especially(特别) as this doesn't contain the buffer cache
* as in minix (and especially now that the kernel is compressed(被压缩的 :-)
*
* The loader has been made as simple as possible, and continues read errors will result in
* (导致) a unbreakable loop. Reboot by hand. It loads pretty(优美的,恰当的) fast by getting
* whole tracks(轨迹..) at a time whenever possible.
*/
#include
#include
SETUPSECS = 4 /* default nr of setup-sectors */
BOOTSEG = 0x07C0 /* original address of boot-sector */
INITSEG = DEF_INITSEG /* 0x9000: we move boot here - out of the way */
SETUPSEG = DEF_SETUPSEG /* 0x9020: setup starts here */
SYSSEG = DEF_SYSSEG /* 0x1000: system loaded at 0x10000 (65536) */
SYSSIZE = DEF_SYSSIZE /* 0x7F00: system size: # of 16-byte clicks */
/* to be loaded - 0x7F00*16=508KB */
ROOT_DEV = 0 /* ROOT_DEV is now written by "build" */
SWAP_DEV = 0 /* SWAP_DEV is now written by "build" */
#ifndef SVGA_MODE
#define SVGA_MODE ASK_VGA /* 0xfffd - ask for it at bootup */
#endif
#ifndef RAMDISK
#define RAMDISK 0
#endif
#ifndef CONFIG_ROOT_RDONLY
#define CONFIG_ROOT_RDONLY 1
#endif
.code16
.text
.global _start /* 告知连接程序,程序从_start标号开始执行. */
_start:
#if 0 /* hook(钩) for debugger, harmless(无害的) unless BIOS is fussy(繁琐的..) (old HP) */
/* INT 3功能是在程序中设置一个断电,程序运行到INT 3自动停下来
* 后可以用单步调试功能一步一步的调下去.
*/
int $0x3
#endif
/* 将引导扇区代码从0x07C00移到0x90000处 */
movw $BOOTSEG, %ax /* ds段基址: 0x07C0 */
movw %ax, %ds
movw $INITSEG, %ax /* es段基址: 0x9000 */
movw %ax, %es
movw $256, %cx
subw %si, %si
subw %di, %di
cld
rep
movsw /* DS:[SI] -> ES:[DI] */
/*
* 跳转到移动后的INITSEG:go处执行
*/
ljmp $INITSEG, $go
# bde - changed 0xff00 to 0x4000 to use debugger at 0x6400 up (bde). We
# would not have to worry about this if we checked the top of memory. Also
# my BIOS can be configured to put the wini drive tables in high memory
# instead of in the vector table. The old stack might have clobbered(击倒,使惨败) the
# drive table.
/*
* 12Byte用来存放磁盘参数表(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.
# Many BIOSs 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 ?
# sectors in some cases.
#
# Since single sector reads are slow and out of 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 does not hurt(伤害,受伤). Low does.
#
# Segments are as follows: ds = es = ss = cs -[= ??] INITSEG, fs = 0,
# and gs is unused.
/*
* BIOS中断向量表中断1Eh的地址0:78h处,放置的是指向软盘参数表的指针.现在需要更改
* 软盘参数表中"每磁道的扇区数",而软盘参数表放在BIOS ROM中,我们无法修改. 所以将
* 其复制到指定的RAM参数表中,再进行修改.然后将BIOS的中断向量表中0:78h处的远指针
* 修改为指向RAM中新复制的软盘参数表.
*/
movw %cx,%fs # set fs to 0
movw $0x78, %bx # fs:bx is parameter table address
pushw %ds
/*
* LDS OPS, OPD - 传送偏移地址及数据段首址
* (OPS) ->OPD, (OPS + 2) ->DS
*/
ldsw %fs:(%bx), %si # ds:si is source
movb $6, %cl # copy 12 bytes
pushw %di # di = 0x4000-12.
rep # do not need cld -> done on line 66
movsw
popw %di
popw %ds
/*
* 指定"每磁道的扇区数"为36
*/
movb $36, 0x4(%di) # patch(修补) sector count
movw %di, %fs:(%bx)
movw %es, %fs:2(%bx)
# Load the setup-sectors directly after the bootblock.
# Note that 'es' is already set up.
# Also, cx = 0 from rep movsw above.
/* 从磁盘上读入紧邻着bootsect的setup程序 */
load_setup:
/* 重置磁盘驱动器,使得刚才对"每磁道的扇区数"的设定发挥功能 */
/*
* INT 13H: 直接磁盘服务(Direct Disk Service).
* 功能00H: 磁盘系统复位.AH=00H,DL=驱动器,00H~7FH:软盘; 80H~0FFH:硬盘.
*/
xorb %ah, %ah # reset FDC
xorb %dl, %dl
int $0x13
/*
* 功能02H: 读扇区.
* AH=02H,AL=扇区数,CH=柱面,CL=扇区,DH=磁头,DL=驱动器,00H~7FH:软盘;80H~0FFH:硬盘
* ES:BX=缓冲区地址.
* 出口参数:CF=0——操作成功,AH=00H,AL=传输的扇区数,否则AH=状态代码.
*/
xorw %dx, %dx # drive 0, head 0
movb $0x02, %cl # sector 2, track 0
movw $0x0200, %bx # address = 512, in INITSEG
movb $0x02, %ah # service 2, "read sector(s)"
movb setup_sects, %al # (assume all on head 0, track 0)
int $0x13 # read it
jnc ok_load_setup # ok - continue
/* 读盘错误处理 */
pushw %ax # dump error code
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.
/*
* 猜测软盘"每磁道的扇区数",试着从大到小读36,18,15,0扇区,如果读取成功,说明磁盘上的每
* 磁道扇区数足以达到试读的数
*/
movw $disksizes, %si # table of sizes to try
probe_loop:
lodsb
cbtw # extend to word
movw %ax, sectors
cmpw $disksizes + 4, %si
jae got_sectors # if all else fails, try 9
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) [逻辑左移 ??]
int $0x13
jc probe_loop # try next value
/* 接下来要真正读入linux的kernel了,也就是在linux根目录下看到的"vmlinux". */
got_sectors:
/*
* INT 10H: 显示服务.AH=03H: 在文本坐标下,读取光标各种信息.BH=显示页码.
* 出口参数: CH=光标的起始行,CL=光标的终止行,DH=行(Y坐标),DL=列(X坐标).
*/
movw $INITSEG, %ax
movw %ax, %es # set up es
movb $0x03, %ah # read cursor pos
xorb %bh, %bh
int $0x10
/* 在屏幕上输出字符串"Loading". */
/*
* BH=页码,BL=属性(若AL=00H或01H),CX=显示字符串长度,(DH,DL)=坐标(行,列).
* ES:BP=显示字符串的地址,AL=显示输出方式.
* 0——字符串中只含显示字符,其显示属性在BL中.显示后,光标位置不变.
* 1——字符串中只含显示字符,其显示属性在BL中.显示后,光标位置改变.
* 2——字符串中含显示字符和显示属性.显示后,光标位置不变.
* 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 are loading...
movw $SYSSEG, %ax # ok, we have written the message, now
movw %ax, %es # we want to load system (at 0x10000)
call read_it /* 读磁盘上system模块,es为输入参数. */
call kill_motor /* 关闭驱动器马达,这样就可以知道驱动器的状态了. */
call print_nl
/* 此后,我们检查要使用哪个根文件系统设备(简称根设备).解说见0.11版本的P46. */
# 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.
/*
* 上面后两个设备文件的含义[0.11版本]:
* 在Linux中软驱的主设备号是2,次设备号 = type * 4 + nr, 其中nr为0-3分别对应软驱A,B,C或D;
* type是软驱的类型(2->1.2M或7->1.44M等).因为7 * 4 + 0 = 28,所以/dev/PS0(2,28)指的是1.44M
* A驱动器,其设备号是021c,同理/dev/at0(2,8)指的是1.2M A驱动器,设备号是0x0208.
*/
movw root_dev, %ax
orw %ax, %ax
jne root_defined
/* 每磁道扇区数.如sectors=15则是1.2Mb的驱动器.如sectors=18,则为1.44Mb软驱... */
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:
/*
* 所有程序加载完毕,跳转到被加载在bootsect后面的setup程序去.
*/
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.
# es = starting address segment (normally 0x1000)
sread: .word 0 # sectors read of current track [当前磁道中已读的扇区数]
head: .word 0 # current head [当前磁头号]
track: .word 0 # current track [当前磁道号]
read_it:
movb setup_sects, %al
incb %al
movb %al, sread /* 1个是引导扇区,4个是setup所在扇区 */
/*
* 测试输入的段值.必须位于内存地址64KB边界处,否则进入死循环.
*/
movw %es, %ax
testw $0x0fff, %ax
die: jne die # es must be at 64kB boundary
xorw %bx, %bx # bx is starting address within segment [bx为段内偏移位置]
rp_read:
#ifdef __BIG_KERNEL__
bootsect_kludge = 0x220 # 0x200 (size of bootsector) + 0x20 (offset
lcall bootsect_kludge # of bootsect_kludge in setup.S)
#else
movw %es, %ax
subw $SYSSEG, %ax
#endif
cmpw syssize, %ax # have we loaded all yet ?
jbe ok1_read
ret
ok1_read:
movw sectors, %ax /* 取每磁道扇区数 */
subw sread, %ax /* 减去当前磁道已读扇区数 */
movw %ax, %cx /* cx = ax = 当前磁道未读扇区数. */
shlw $9, %cx /* cx = cx * 512字节. */
addw %bx, %cx /* cx = cx + 段内当前偏移值(bx) = 此次读操作后,段内共读入的字节数. */
jnc ok2_read /* 无进位转移.若没超过64KB字节,则跳转至ok2_read处执行. */
je ok2_read
xorw %ax, %ax /* 若加上此次将读磁道上所有未读扇区时会超过64KB,则计算此时最多能读 */
subw %bx, %ax /* 入的字节数(64KB - 段内读偏移位置),再转换需要读取的扇区数. */
shrw $9, %ax
ok2_read:
call read_track
movw %ax, %cx /* cx = 该次操作已读取的扇区数. */
addw sread, %ax /* 当前磁道上已经读取的扇区数. */
cmpw sectors, %ax /* 如果当前磁道上还有扇区未读,则跳到ok3_read处. */
jne ok3_read
/* 读该磁道的下一磁头面(1号磁头)上的数据.如果已经完成,则去读下一磁道. */
mov $1, %ax
subw head, %ax /* 判断当前磁头号. */
jne ok4_read /* 如果是0磁头,则再去读1磁头同上的扇区数据. */
incw track /* 否则去读下一磁道. */
ok4_read:
movw %ax, head /* 保存当前磁头号. */
xorw %ax, %ax /* 清当前磁道已读扇区数. */
ok3_read:
movw %ax, sread /* 保存当前磁道已读扇区数. */
shlw $9, %cx /* 上次已读扇区数 * 512字节. */
addw %cx, %bx /* 调整当前段内数据开始位置. */
jnc rp_read /* 若小于64KB边界值,则跳转到rp_read处,继续读数据.
* 否则调整当前值,为读下一段数据作准备. */
movw %es, %ax
addb $0x10, %ah /* 将段基址调整为指向下一个64KB段内存. */
movw %ax, %es
xorw %bx, %bx /* 清段内数据开始偏移值. */
jmp rp_read /* 继续读数据. */
read_track:
pusha
pusha
movw $0xe2e, %ax # loading... message 2e = .
movw $7, %bx
int $0x10
popa
movw track, %dx /* 取当前磁道号. */
movw sread, %cx /* 取当前磁道上已读扇区数. */
incw %cx /* cl = 开始读扇区. */
movb %dl, %ch /* ch = 当前磁道号. */
movw head, %dx /* 取当前磁头号 */
movb %dl, %dh /* dh等于磁头号 */
andw $0x0100, %dx /* 磁头号不大于1. */
movb $2, %ah /* ah = 2,读磁盘扇区功能号 */
pushw %dx # save for error dump
pushw %cx
pushw %bx
pushw %ax
int $0x13
jc bad_rt /* 若出错,则跳转至bad_rt. */
addw $8, %sp
popa
ret
/* 执行驱动器复位操作(磁盘中断功能号0),再跳转到read_track处重试. */
bad_rt:
pushw %ax # save error code
call print_all # ah = error, al = read
xorb %ah, %ah
xorb %dl, %dl
int $0x13
addw $10, %sp
popa
jmp read_track
# 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
print_all:
movw $5, %cx # error code + 4 registers
movw %sp, %bp
print_loop:
pushw %cx # save count left
call print_nl # nl for readability
cmpb $5, %cl
jae no_reg # see if register name is needed
movw $0xe05 + 'A' - 1, %ax
subb %cl, %al
int $0x10
movb $'X', %al
int $0x10
movb $':', %al
int $0x10
no_reg:
addw $2, %bp # next register
call print_hex # print it
popw %cx
loop print_loop
ret
/* 屏幕打印"回车/换行"符 */
print_nl:
movw $0xe0d, %ax # CR
int $0x10
movb $0xa, %al # LF
int $0x10
ret
# print_hex is for debugging purposes, and prints the word
# pointed to by ss:bp in hexadecimal(十六进制).
/* 用十六进制的格式打印ss:bp处的字 */
print_hex:
movw $4, %cx # 4 hex digits
movw (%bp), %dx # load word into dx
print_digit:
rolw $4, %dx # rotate to use low 4 bits
movw $0xe0f, %ax # ah = request
andb %dl, %al # al = mask for nybble
addb $0x90, %al # convert al to ascii hex
daa # in only four instructions!
adc $0x40, %al
daa
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:
movw $0x3f2, %dx # 软驱控制卡的驱动端口,只写
xorb %al, %al # A驱动器,关闭FDC,禁止DMA和中断请求,关闭马达
outb %al, %dx
ret
sectors: .word 0 # 存放当前启动软盘每磁道的扇区数
disksizes: .byte 36, 18, 15, 0
msg1: .byte 13, 10 # 回车,换行的ASCII码
.ascii "Loading"
# XXX: This is a "very" snug fit.
.org 497
setup_sects: .byte SETUPSECS
root_flags: .word CONFIG_ROOT_RDONLY
syssize: .word SYSSIZE
swap_dev: .word SWAP_DEV
ram_size: .word RAMDISK
vid_mode: .word SVGA_MODE
root_dev: .word ROOT_DEV
boot_flag: .word 0xAA55