您的位置:首页 > 运维架构 > Linux

自己动手搭建 Linux 0.12 编译环境 — Boot

2013-04-20 11:18 603 查看
通过前面两篇的介绍,相信对搭建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:
内容来自用户分享和网络整理,不保证内容的准确性,如有侵权内容,可联系管理员处理 点击这里给我发消息
标签: