linux0.12-6-2(bootsect.S)

[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硬盘逻辑设备号

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值