[218页]
6-2 bootsect.S程序
6-2-1 功能描述
1、 bootsect.S代码是磁盘引导块程序需,驻留在磁盘的第一个扇区中(引导扇区,0磁道(柱面),0磁头,第1个扇区)。
2、 BIOS会把引导扇区代码bootsect加载到内存地址0x7c00开始处并执行。
3、 bootsect代码将自己移动到内存绝对地址0x90000处并继续执行。
4、 bootsect代码把从磁盘第2个扇区开始的4个扇区的setup模块加载到内存紧接着bootsect后面位置处(0x90200)。
5、 利用BIOS中断0x13取磁盘参数表中当前启动引导盘的参数。
6、 在屏幕上显示"Loading……"字符串。
7、 把磁盘上setup模块后面的system模块加载到内存0x10000开始的地方。
8、 确定根文件系统的设备号,若没有指定,则根据所保存的引导盘的每磁道扇区数判断出盘的类型和种类
(如:是1.44MB A:盘吗?)并保存其设备号于root_dev(引导块的508地址处)。
9、 长跳转到setup程序开始处(0x90200)去执行setup程序。
后缀用大S的原因:
为了能在程序中使用#include语句来包含进linux/config.h头文件定义的常数。
6-2-2 代码注释
/*
SYS_SIZE是要加载的系统模块长度,单位是节,每节16字节。
0x3000=0x3000字节=0x3000=196KB。对于当前内核版本这个空间长度已经足够了。
*/
/*
使用头文件linux/config.h常量
#define DEF_INITSEG 0x9000 默认本程序代码移动到目的段位置;
#define DEF_SYSSEG 0x1000 默认从磁盘加载内核模块到内存的段位置;
#define DEF_SETUPSEG 0x9020 默认setup程序代码段位置;
#define DEF_SYSSIZE 0x3000 默认内核大小。单位是节,每节为16字节;
*/
#include <linux/config.h>
SYSSIZE = DEF_SYSSIZE !定义一个标号或符号。指明编译连接后内核大小。
/*
! bootsect.s (C) 1991 Linus Torvalds
! modified by Drew Eckhardt
bootsect.S被ROM BIOS启动子程序加载至0x7c00(31KB)处,
并将自己移到了地址0x90000(576KB)处,并跳转值哪里。
它然后使用BIOS中断将"setup"直接加载到自己的后面(0x90200)(575.5KB),并将内核模块加载到地址0x10000处。
注意!目前的内核系统最大长度限制(8*65536 B)(512KB),即使是在将来这也应该没有问题的。
我想让它保持简单明了。这样512KB的最大内核长度应该足够了,尤其是这里乜有想MINIX中一样
包含缓冲区高速缓冲。
加载程序已经做得够简单了,所以持续地读操作出错将导致死循环。只能手工启动。只要可能,
通过一次读取所有的扇区,加载过程可以做得很快。
伪指令(伪操作符).globl或.global用于定义随后的标识符是外部的或全局的,并且即使不适用
也强制引入。.text、.data和.bss用于分别定义当前代码段、数据段和未初始化数据段。在链接
多个目标模块时,链接程序(ld86)会根据它们的类别把各个目标模块中的相应段组合(合并)
在一起。这里把3个段都定义在同一重叠地址范围中,因此程序实际上不分段。
另外,后面带冒号的字符串是标号,例如下面的"begtext:"。
一条汇编语句由标号(可选)、指令助记符(指令名)和操作数三个字段组成。
标号位于一条指令的第一个字段。它代表其所在位置的地址,通常指明一个跳转指令的目标位置。
*/
.globl begtext, begdata, begbss, endtext, enddata, endbss
.text !文本段(代码段)
begtext:
.data !数据段
begdata:
.bss !未初始化数据段
begbss:
.text !文本段(代码段)
!下面等号'='或符号'EQU'用于定义标识符或标号所代表的值。
SETUPLEN = 4 ! setup程序代码占用磁盘扇区值
BOOTSEG = 0x07c0 ! bootsect代码所在内存原始段地址
INITSEG = DEF_INITSEG ! 将bootsect移到位置0x90000-避开内核占用处;
SETUPSEG = DEF_SETUPSEG ! setup程序从内存0x90200处开始
SYSSEG = DEF_SYSSEG ! 内核模块加载到0x10000(64KB)处
ENDSEG = SYSSEG + SYSSIZE ! 停止加载的段地址
!根文件系统设备号ROOT_DEV和交换设备号SWAP_DEV现在由tools目录下的build程序写入。
ROOT_DEV = 0 !根文件系统设备使用与系统引导时同样的设备
SWAP_DEV = 0 !交换设备使用与系统引导时同样的设备
/*
伪指令entry迫使链接程序在生成的执行程序(a.out)中包含指定的标识符或表。
这里是程序执行开始点。
这一段作用是将自身bootsect从目前段位置0x7c0(31KB)移动到0x9000(576KB)出,工256字(512字节),
然后跳转到移动后代码的go标志,即本程序的下一语句出。
*/
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
/*
SP指针大于(bootsect+setup+堆栈大小)
这里SP设置为0x9ff00-12(参数表长度),即sp=0xfef4
*/
go: mov ax,cs
mov dx,#0xfef4 ! arbitrary value >>512 - disk parm size
mov ds,ax
mov es,ax
push ax !这是BUG,因为下面已经修改了堆栈。
mov ss,ax ! put stack at 0x9ff00 - 12.
mov sp,dx
/*
BIOS设置的中断0x1E的中断向量值是软驱参数表地址。
该向量位于内存0x1E*4=0x78处。
这段代码首先从内存0x0000:0x0078处复制原软驱参数表到0x9000:0xfef4处,
然后修改表中的每磁道最大扇区数为18。
*/
push #0
pop fs !置段寄存器fs=0。
mov bx,#0x78 ! fs:bx指向存有软驱参数表地址处
/*
下面指令表示下一条语句的操作数在fs段寄存器所指的段中。它只影响其下一条语句。
这里把fs:bx所指内存位置处的表地址存放到寄存器对gs:si中作为原地址。
寄存器对es:di=0x90000:0xfef4为目的地址。
*/
seg fs
lgs si,(bx) ! gs:si is source gs:si=0x00:0x78
mov di,dx ! es:di is destination
mov cx,#6 ! copy 12 bytes
cld !清方向标志。复制时指针递增。
rep !复制12字节的软驱参数表到0x9000:0xfef4处。
seg gs
movw
mov di,dx
movb 4(di),*18 ! patch sector count
seg fs !让中断向量0x1E的值指向新表。
mov (bx),di !修改中断向量表0x78的值=0x90000:0xfef4
seg fs
mov 2(bx),es
pop ax !此时ax中时上面第65行保留下来的段值(0x9000)。
mov fs,ax !设置fs=gs=0x90000.
mov gs,ax
xor ah,ah ! reset FDC 复位软盘控制器,让其采用新参数。
xor dl,dl !dl=0,第1个软盘。
int 0x13
/*
在bootsect程序块后紧跟着加载setup模块的代码数据。
注意es已经设置好了。(在移动代码时es已经指向目的段地址处0x9000)
*/
/*
下面一段是利用ROM BIOS中断INT 0x13将setup模块从磁盘第2个扇区开始读到
0x90200开始出,共读4个扇区。在读操作过程中如果读出错,则显示磁盘上出错
扇区位置,然后复位驱动器并重试,没有退路。
*/
load_setup:
xor dx, dx ! 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
push ax ! dump error code
call print_nl
mov bp, sp
call print_hex
pop ax
xor dl, dl ! reset FDC
xor ah, ah
int 0x13
j load_setup
ok_load_setup:
!这段代码取磁盘驱动器的参数,实际上是取每磁道扇区数,
!并保存在位置sectors处。
xor dl,dl
mov ah,#0x08 ! AH=8 is get drive parameters
int 0x13
xor ch,ch
seg cs
mov sectors,cx !中断返回值在cx,保存到sectors
mov ax,#INITSEG
mov es,ax !因为上面取磁盘参数中断改了es值,这里重新改回。
!显示信息:"Loading"+回车+换行
!BIOS中断0x10功能号 ah=0x03,读光标位置
!ch=扫描开始线;cl=扫描结束线;dh=行号;dl=列号
mov ah,#0x03 ! read cursor pos
xor bh,bh
int 0x10
!BIOS中断0x10功能号 ah=0x13,显示字符串
mov cx,#9
mov bx,#0x0007 ! page 0, attribute 7 (normal)
mov bp,#msg1
mov ax,#0x1301 ! write string, move cursor
int 0x10
!现在开始将内核模块加载到0x10000(64KB)开始处。
mov ax,#SYSSEG
mov es,ax ! segment of 0x010000
call read_it !读磁盘上内核模块,es为输入参数。
call kill_motor !关闭驱动器马达,这样就可以知道驱动器的状态了。
call print_nl !光标回车换行。
/*
此后,我们检查要使用哪个根文件系统设备(简称根设备)。如果已经指定了设备(!=0),
就直接使用给定的设备。否则就需要根据BIOS报告的每磁道扇区数来确定到底使用
/dev/PS0(2,28),还是/dev/at0(2,8)。
软盘主设备号2,次设备号=type*4+nr,nr为0~3分别对应软盘A、B、C、D,type是软盘的类型(2->1.2MB,7->1.44MB)
*/
/*
这里默认为0x0306是因为当时linus开发linux系统时是在第2个硬盘第1个分区中存放根文件系统。
*/
seg cs
mov ax,root_dev !取508,509字节出的根设备号并判断是否已定义。
or ax,ax
jne root_defined
/*
取上面第148行保存的每磁道扇区数。如果sectors=15则说明是1.2MB的驱动器;
如果sectors=18,则说明是1.44MB软盘。因为是可引导的驱动器,所以肯定是A驱。
*/
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
/*
到此,所有程序都加载完毕,我们就跳转到被加载在bootsect后面的setup程序去。
下面段间跳转指令(Jump Intersegment)。跳转到0x9020:0000(setup.s程序开始处)去执行。
*/
jmpi 0,SETUPSEG !!!到此本程序就结束。!!!
/*
下面是几个子程序。
read_it用于读取磁盘上的system模块。
kill_motor用于关闭软驱电动机。
还有一些屏蔽显示子程序。
*/
/*
该子程序将系统模块加载到内存地址0x10000处,并确定没有跨越64KB的内存边界。
我们试图尽快地进行加载,只要可能,就每次加载整条磁道的数据。
输入:es-开始内存地址段值(通常是0x1000)
下面伪操作符.word定义一个2字节目标。相当于C语言程序中定义的变量和所占内存空间大小。
"1+SETUPLEN"表示开始时已经读进1个引导扇区和setup程序所占的扇区数据SETUPLEN。
*/
sread: .word 1+SETUPLEN ! sectors read of current track 当前磁道中已读扇区数。
head: .word 0 ! current head 当前磁头号
track: .word 0 ! current track 当前磁道号
/*
首先测试输入的段值。从磁盘上读入的数据必须存放在位于内存地址64KB的边界开始 处,
否则进入死循环。清bx寄存器,用于表示当前段内存放数据的开始位置。
test ax,#0x0fff--指令test以位逻辑与两个操作数。若两个操作数对应的位都为1,则结果值的对应位为1,
否则为0.该操作结果只影响标志(零标志ZF等)。例如若AX=0x1000,那么test指令的执行结果是(0x1000&0x0fff)=0x0.
于是ZF标志置位。此时即下一条指令jne条件不成立。
*/
read_it:
mov ax,es
test ax,#0x0fff !内核数据必须加载到64KB以外。
die: jne die ! es must be at 64kB boundary
xor bx,bx ! bx is starting address within segment
/*
接着判断是否已经读入全部数据。比较当前所读是否就是系统数据末端所处的段(#ENDSEG),
如果不是就跳转至下面ok1_read标号出继续读取数据。否则退出子程序返回。
*/
rp_read:
mov ax,es
cmp ax,#ENDSEG ! have we loaded all yet? 是否已经加载了全部数据?
jb ok1_read
ret
ok1_read:
seg cs
mov ax,sectors !取每磁道扇区数。
sub ax,sread !减去当前磁道已读扇区数
mov cx,ax !cx=ax=当前磁道未读扇区数据
shl cx,#9 !cx=cx*512字节+段内当前偏移值(bx)。
add cx,bx !=此次读操作后,段内工读入的字节数。
jnc ok2_read !若没有超过64KB,则跳转至ok2_read处执行。
je ok2_read
xor ax,ax
sub ax,bx
shr ax,#9
ok2_read:
call read_track !读当前磁道上指定开始扇区和需读扇区数的数据。
mov cx,ax !cx=该次操作已读取的扇区数。
add ax,sread !加上当前磁道上已经读取的扇区数。
seg cs
cmp ax,sectors !若当前磁道上的还有扇区未读,则跳转到ok3_read
jne ok3_read
mov ax,#1
sub ax,head !判断当前磁头号。
jne ok4_read
inc track
ok4_read:
mov head,ax
xor ax,ax
ok3_read:
mov sread,ax
shl cx,#9
add bx,cx
jnc rp_read
mov ax,es
add ah,#0x10
mov es,ax
xor bx,bx
jmp rp_read
read_track:
pusha
pusha
mov ax, #0xe2e ! loading... message 2e = .
mov bx, #7
int 0x10
popa
mov dx,track
mov cx,sread
inc cx
mov ch,dl
mov dx,head
mov dh,dl
and dx,#0x0100
mov ah,#2
push dx ! save for error dump
push cx
push bx
push ax
int 0x13
jc bad_rt
add sp, #8
popa
ret
bad_rt: push ax ! save error code
call print_all ! ah = error, al = read
xor ah,ah
xor dl,dl
int 0x13
add sp, #10
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:
mov cx, #5 ! error code + 4 registers
mov bp, sp
print_loop:
push cx ! save count left
call print_nl ! nl for readability
jae no_reg ! see if register name is needed
mov ax, #0xe05 + 0x41 - 1
sub al, cl
int 0x10
mov al, #0x58 ! X
int 0x10
mov al, #0x3a ! :
int 0x10
no_reg:
add bp, #2 ! next register
call print_hex ! print it
pop cx
loop print_loop
ret
print_nl:
mov ax, #0xe0d ! CR
int 0x10
mov al, #0xa ! LF
int 0x10
ret
/*
* print_hex is for debugging purposes, and prints the word
* pointed to by ss:bp in hexadecmial.
*/
print_hex:
mov cx, #4 ! 4 hex digits
mov dx, (bp) ! load word into dx
print_digit:
rol dx, #4 ! rotate so that lowest 4 bits are used
mov ah, #0xe
mov al, dl ! mask off so we have only next nibble
and al, #0xf
add al, #0x30 ! convert to 0 based digit, '0'
cmp al, #0x39 ! check for overflow
jbe good_digit
add al, #0x41 - 0x30 - 0xa ! '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 dx,#0x3f2
xor al, al
outb
pop dx
ret
sectors:
.word 0
msg1:
.byte 13,10
.ascii "Loading"
.org 506
swap_dev:
.word SWAP_DEV
root_dev:
.word ROOT_DEV
boot_flag:
.word 0xAA55
.text
endtext:
.data
enddata:
.bss
endbss:
6-2-3 其他信息
1、 Linux0.12硬盘设备号
硬盘的主设备号是3。其他设备的主主设备号分别为:
1-内存、2-磁盘、3-硬盘、4-ttyx、5-tty、6-并行口、7-非命名管道。
参考:表6-2硬盘逻辑设备号