Chinaunix首页 | 论坛 | 博客
  • 博客访问: 162191
  • 博文数量: 33
  • 博客积分: 2530
  • 博客等级: 少校
  • 技术积分: 580
  • 用 户 组: 普通用户
  • 注册时间: 2008-07-25 16:03
文章分类
文章存档

2011年(1)

2010年(2)

2009年(17)

2008年(13)

我的朋友

分类: LINUX

2008-08-26 17:32:29

'bootsect.S' Detail Comment

/************************************************************************
只有在使用软盘启动时才用 bootsect.S这段代码;如从硬盘上的 bootloader 如lilo或grub
启动,他们是不会理会它的。也就是说,使用硬盘的 bootloader * 时, 直接跳过bootsect.S
的所有代码进入setup.S。

bootsect在bios启动例程中被加载到0x7c00,随后其自己把自己加载到0x90000并跳到那里去执
行。然后其加载直接位于其后的'setup'到0x90200,同时使用BIOS中断加载系统bvmlinux.out
到0x100000。

如果在加载过程中出现了读错误,那么将会导致一个无限不可中断的循环,这时候就需要通过手工来重新
启动计算机。
************************************************************************/

#include # for CONFIG_ROOT_RDONLY
#include

SETUPSECS = 4 # default 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)
SYSSIZE = DEF_SYSSIZE # system size: # of 16-byte clicks

# to be loaded
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
#endive

#ifndef RAMDISK
#define RAMDISK 0
#endif

#ifndef CONFIG_ROOT_RDONLY
#define CONFIG_ROOT_RDONLY 1
#endif

.code16
.text

.global _start
_start:

#if 0 # hook for debugger, harmless unless BIOS is fussy (old HP)
int $0x3
#endif

/************************************************************************
此段的作用是bootsect将自身从0x7c:0000搬迁到内存的0x9000:0000去。
************************************************************************/

movw $BOOTSEG, %ax
movw %ax, %ds
movw $INITSEG, %ax
movw %ax, %es
movw $256, %cx
subw %si, %si
subw %di, %di
cld
rep
movsw
ljmp $INITSEG, $go


/************************************************************************
设置堆栈,0x90000--[0x94000-12] 为堆栈空间,[0x94000-12]--0x94000 为存放硬盘
参数的空间,这里为bootsect和setup和存磁盘参数的栈留足了空间。 这里0x4000的值其实很随
意,只要保证其>=【(bootsect+setup)长度+栈空间】就可以了。12是磁盘参数的长度。
************************************************************************/
movw %ax, %ds # ax and es already contain INITSEG
movw %ax, %ss
movw %di, %sp # put stack at INITSEG:0x4000-12.

/************************************************************************
许多BIOS的缺省磁盘参数表支持可同时读取最大扇区的数目定义在缺省的磁盘参数表中,通常为7,由
于单扇区读取和每次7个扇区的读取非常慢,我们把这个最大数目扩展到了36,同时修改了缺省的磁盘
参数表。这个数目高点没有坏处,但是低了会导致效率底下。

将位于0000:0078的磁盘参数表拷到9000:4000-12处并修改,其中0000:0078是系统方磁盘参
数表的地方,是BIOS预先拷贝进去的。这里主要是为了读入包括setup.S在内的其他内核部分做准备。
注意,78H,80H保存为磁盘参数表段基地址和段内偏移地址,没拷贝前,78H,80H指向82H开始处
的8个字节,拷贝后,这里会将78H,80H中的地址值改写成指向9000H:4000H-12,这样当相应的
读取磁盘数据的中断在查询最大扇区数目时,首先会到BIOS定制地址去查询该参数存放未知,也就是
9000H:4000H-12,然后再查询9000H:4000H-12 + 4位置处的数据,也就是每次可读取最大扇
区数目。

现在的段数值如下: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
pushw %ds
ldsw %fs:(%bx), %si # ds:si is source, si = bx, ds = fs
movb $6, %cl # copy 12 bytes
pushw %di # di = 0x4000-12.
rep # rep movsw Move CX words from DS:[SI] to ES:[DI].
movsw

popw %di # di = 0x4000-12.
popw %ds # ds = INITSEG(0x90000)
movb $36, 0x4(%di) # patch sector count
movw %di, %fs:(%bx)
movw %es, %fs:2(%bx)
/************************************************************************
LDS命令解释:LDS可以同时装载一个地址到段寄存器和指针寄存器里面。
lds xx1:(xx2),si 把xx1:xx2段地址:偏移地址分别存到DS:SI,如果不用lds命令的话,就要这样写:
mov xx1,ax
mov ax,ds
mov xx2,si
而且这个过程很可能被中断。

另外,lds xx,si则表示mov xx,si
************************************************************************/

/************************************************************************
直接加载随后的setup扇区,注意当前es已经被设置成了INITSEG,同时由于前面的rep movsw
已经将cx置为0。这里的读取原理如下:

读第一个磁盘的扇区状态ah,磁头号=0 dh,驱动器号=0 dl,扇区2 cl,磁道0 ch,如果读取
没有问题的话,往下继续,否则循环读取,一般出错了用户只能手动重新启动计算机, 这里一共读取
4个扇区的数据到0x90200位置ES:BX确定的地址中。由于bootsect.S位于MBR的第一个扇区,
而setup.S位于其后紧接的四个扇区,因此这里接着从软盘的第二个扇区开始连续读出4个扇区的数
据到0x90200处,而0x90000到0x90200处为bootsect.S
************************************************************************/

load_setup:

# 复位软盘
xorb %ah, %ah # reset FDC
xorb %dl, %dl
int $0x13

# 读取setup.S
xorw %dx, %dx # drive 0, head 0
movb $0x02, %cl # ector 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) setup_sects=4
int $0x13 # read it

jnc ok_load_setup # 如果读取成功,跳转到ok_load_setup
# 否则打印错误信息
pushw %ax # dump error code
call print_nl
movw %sp, %bp
call print_hex
popw %ax
jmp load_setup

/************************************************************************
直接加载随后的setup扇区,注意当前es已经被设置成了INITSEG,同时由于前面的rep movsw下
面获取当前磁盘的参数,主要是每磁道的扇区数目,目前为止还没有BIOS调用用来读取每磁道的扇区数
目,所以只能够通过猜的方法,首先,我们猜每磁道扇区数目为36,然后用这个数目如果不成功,猜18,
否则15,如果还猜不到,那就默认为9 。

这里猜的方法就是调用int 13 AH=2试图一次性读取36(或18,或15)数目的扇区,如果读取失
败,则表示当前磁盘每磁道的扇区数目要小于36,所以我们继续尝试18.....当然如果成功则不再往
下继续尝试,最终的正确结果存放在sectors处的内存中。

关于LODS指令:LODS 指令和STOS指令功能互逆,它将SI寄存器指向的主存单元的内容送至AL或
AX寄存器,并相应修改SI使其指向下一个元素。LODS指令不影响标志。相当于:
mov ds:si al;
si += 1;
这里其实其主要用处就是用disksizes表中的各个数据来测试每个磁道的扇区数目!
************************************************************************
ok_load_setup:
movw $disksizes, %si # table of sizes to try

probe_loop:
lodsb
cbtw # extend to word

# ax = 36(第一轮),18,15,9,这里没有$的变量(sectors)表示内存位置。因此表示把ax寄存器
的值存入sectors位置处。
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)
movw $0x0201, %ax # service 2, 1 sector
int $0x13
jc probe_loop # try next value

/************************************************************************
显示字符串:回车+"loading"
************************************************************************/

got_sectors:
movw $INITSEG, %ax
movw %ax, %es # set up es
movb $0x03, %ah # read cursor pos
xorb %bh, %bh
int $0x10
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..

/************************************************************************
读取内核映像bvmlinux.out到1M处(小内存这里我们就暂时不考虑了)
************************************************************************/
movw $SYSSEG, %ax # ok, we've written the message, now
movw %ax, %es # we want to load system (at 0x10000)

call read_it # 读取内核映像bvmlinux.out到1M处
call kill_motor # 关闭马达
call print_nl # 打印回车


/************************************************************************
到现在为止,我们已经检测到当前使用的根设备,这时候就可以依旧根设备的类型来设置设备的主从设
备号。当然,如果root_dev处已经被build.c写入了根设备信息,则就不需要做这些事情了。最后,
将根设备的主从设备好保存到roo_dev符号所在的内存中。后面在OS启动的时候将会使用到该信息。
************************************************************************/

movw root_dev, %ax # ax = 0
orw %ax, %ax
jne root_defined

movw sectors, %bx # bx=36/18/15/9
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

/************************************************************************
万事俱备,我们直接跳到随后的setup中去执行。

关于ljmp需要说明的是:当在实模式下执行一个长跳转时,CPU将会设置CS和IP为操作数中的两个值!
************************************************************************/
ljmp $SETUPSEG, $0 # CS:IP = SETUPSEG:0

/************************************************************************
读取内核子例程,不再讲述,见PowerDesigner。

This routine loads the system at address 0x10000, making sure no 64kB
boundaries are crossed. We try to load it as fast aspossible, 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 # al=4
incb %al # al=5
movb %al, sread # sread=5
movw %es, %ax
testw $0x0fff, %ax
die: jne die # es must be at 64kB boundary

xorw %bx, %bx # bx is starting address within segment
rp_read:
/************************************************************************
lcall的说明,Far Calls in Real-Address Mode. When executing a far call
in realaddress mode, the processor pushes the current value of both
the CS and EIP registers on the stack for use as a return-instruction
pointer. The processor then performs a [far branch] to the code
segment and offset specified with the target operand for the called
procedure.

The target operand specifies an absolute far address either directly
with a pointer (ptr16:16 or ptr16:32) or indirectly with a memory
location(m16:16 or m16:32). With the pointer method, the segment and
offset of the called procedure is encoded in the instruction using
a 4-byte (16-bit operand size) far address immediate. With the
indirect method, the target operand specifies a memory location that
contains a 4-byte (16-bit operand size) far address. The operand-size
attribute determines the size of the offset(16 or 32 bits) in the far
address. The far address is loaded directly into the CS and EIP
registers. If the operand-size attribute is 16, the upper two bytes
of the EIP register are cleared.

这里说的意思就是说,下面的调用是采用内存位置调用,在给内存出保存了CS:IP,然后CPU会加载该
内存处的CS:IP到CPU的CS:IP,从而实现了CS:IP的重新加载。

1.__BIG_KERNEL__在Makefile中被定义了$(CPP) $(CPPFLAGS) -D__BIG_KERNEL__.....
2.bootsect_kludge:0x200 (size of bootsector) + 0x20 (offset of
bootsect_kludge in setup.S),故等220H
************************************************************************/
#ifdef
bootsect_kludge = 0x220
lcall bootsect_kludge
#else
movw %es, %ax
subw $SYSSEG, %ax
#endif
cmpw syssize, %ax
jbe ok1_read
ret

ok1_read:
movw sectors, %ax
subw sread, %ax
movw %ax, %cx

shlw $9, %cx
addw %bx, %cx
jnc ok2_read

je ok2_read

xorw %ax, %ax
subw %bx, %ax
shrw $9, %ax

ok2_read:
call read_track
movw %ax, %cx
addw sread, %ax
cmpw sectors, %ax
jne ok3_read

/************************************************************************
如果刚才head=1,则此时head=0,如果刚才head=0,则此时head=1,这里是表示要读取双面磁道,pc
机使用的软盘驱动器都是双头(双面)的。 没有见过哪种pc机用的的是单面的。本段代码在单面软盘上是
通不过的。
************************************************************************/

movw $1, %ax
subw head, %ax
jne ok4_read

incw track

ok4_read:
movw %ax, head
xorw %ax, %ax

ok3_read:
movw %ax, sread
shlw $9, %cx
addw %cx, %bx
jnc rp_read

movw %es, %ax
addb $0x10, %ah
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
movb %dl, %ch
movw head, %dx
movb %dl, %dh
andw $0x0100, %dx
movb $2, %ah
pushw %dx
pushw %cx
pushw %bx
pushw %ax
int $0x13
jc bad_rt

addw $8, %sp
popa
ret

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.

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
outb %al, %dx
ret

sectors: .word 0
disksizes: .byte 36, 18, 15, 9
msg1: .byte 13, 10
.ascii "Loading"

# XXX: This is a *very* snug fit.

.org 497 # 下面很多数据由build.c写入
setup_sects: .byte SETUPSECS # 497 写入setup.o文件大小/512,如果<4则=4
root_flags: .word CONFIG_ROOT_RDONLY # 498
syssize: .word SYSSIZE # 500 写入bvmlinux.out文件大小/16,不能超过2.5M
swap_dev: .word SWAP_DEV # 502
ram_size: .word RAMDISK # 504
vid_mode: .word SVGA_MODE # 506
root_dev: .word ROOT_DEV # 508 508:minor_root 509:major_root
boot_flag: .word 0xAA55 # 510 510:0x55 511:0xAA
阅读(1956) | 评论(0) | 转发(0) |
给主人留下些什么吧!~~