Linux0.11 bootsect.s

概述

如下图是Linux内核完全注释的一副图,表示各个模式在系统的运行大概情况,我们首先分析bootsect.s程序

# 
# BIOS系统调用
# 80386在实模式下虽然是16位的地址,但是经过段映射后可以形成20位的地址共寻址1MB的地址空间
# BOIS会在地址0处建立1KB字节的中断向量表,每个中断向量使用4个字节的空间,
# 前两个字节为段地址,后两个字节为偏移地址,因此一共256个中断向量
# BIOS启动后会建立后中断向量表用于提供一些基本的功能
# 所谓BIOS调用就是使用BIOS的中断功能来执行一些用户想要的操作
# 
# 在AT汇编里,EAX表示32位寄存器,AX表示16位寄存器,AH,AL表示8位寄存器
#
# .code16 表示后面是16位的汇编代码
#
#
	.code16
# 
# rewrite with AT&T syntax by falcon <wuzhangjin@gmail.com> at 081012
#
# 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
#
# SYSSIZE是要加载的节数(16个字节为1节)0x3000*16也就是192KB的大小,
# 对于当前的内核来说已经足够了
#
	.equ SYSSIZE, 0x3000
#
#	bootsect.s		(C) 1991 Linus Torvalds
#
# 编译系统编译的镜像存放格式为:
# | 512 bootsect | 512*4 setup | system(head,kernel} |
#
# BOIS会将启动设备的前512字节拷贝至内存的0x7c00处,并跳转到此处运行,
# bootsect程序主要将自己(512个字节)搬移到0x90000(576K)处,
# 从启动设备继续读取setup模块,存放在自己后面,也就是0x90200地址处(576.5K)处
# 此时bootsect和setup的结尾地址为0x90a00
# bootsect和setup模块一共占用2.5KB的空间,其中bootsect占用0.5KB,setup占用2KB
#
# 以上的数据读取都使用了BIOS调用
#
#
#
# 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.
#
#

	.global _start, begtext, begdata, begbss, endtext, enddata, endbss
	.text
	begtext:
	.data
	begdata:
	.bss
	begbss:
	.text
#
# SETUPLEN 表示setup模块占用sectors的数量,一个sectors为512字节
# BOOTSEG 表示bootsect模块的原始运行地址
# INITSEG 表示bootsect模块搬移后的运行地址
# SETUPSEG 表示setup模块的运行地址
# SYSEG 表示system模块的运行地址,system最终会被搬移到0地址
# ENDSEG 表示system模块的结束地址
#
	.equ SETUPLEN, 4				# nr of setup-sectors
	.equ BOOTSEG, 0x07c0			# original address of boot-sector
	.equ INITSEG, 0x9000			# we move boot here - out of the way
	.equ SETUPSEG, 0x9020			# setup starts here
	.equ SYSSEG, 0x1000				# system loaded at 0x10000 (65536).
	.equ ENDSEG, SYSSEG + SYSSIZE	# where to stop loading

# ROOT_DEV:	0x000 - same type of floppy as boot.
#		0x301 - first partition on first drive etc
# 0x301 表示硬盘的第一个分区
# 0x21D 表示软盘的第一个分区
#
	.equ ROOT_DEV, 0x301
#	.equ ROOT_DEV, 0x21D
#

#
# 系统启动后,BIOS会将启动设备的前512字节拷贝至0x7c00处并运行
# 在编译bootsect模块中,我们发现了链接参数-Ttext 0 -e _start表示起始地址为0,程序入口为_start
# 
# 设置DS为0x07c0,设置ES为0x9000
# 将SI和DI清零
# movsw将DS:SI地址处的数据拷贝到ES:DI处,SI和DI会自动递增,拷贝的次数存放在CX寄存器中
# 因此下面的代码意思是,
# 将0x7c00的数据拷贝至0x90000(576K)处, 每次拷贝2个字节,共拷贝256次,512个字节
# 也就是将bootsect从0x07c00拷贝到0x90000(576K)处
#
# 为什么要拷贝到0x90000(576K)处,这是因为system会被拷贝到0x10000(64K)处
# 而Linus在写这个版本的Linux的时候假设内核的大小为512K,这个可以在后面的注释里看到
# 64K+512K就是576K
# 那为什么system要拷贝到0x10000而不是直接拷贝到0x00000地址呢,
# 这是因为在setup模块中需要用到BIOS掉用获取一些硬件参数,而BIOS可能占用了64K的地址
# 这就是为什么在setup的最后又将system模块拷贝到0x00000地址的原因
#
_start:
	mov	$BOOTSEG, %ax				# BOOTSEG 0x07c0
	mov	%ax, %ds					# DS = 0x07c0
	mov	$INITSEG, %ax				# INITSEC 0x9000
	mov	%ax, %es					# ES = 0x9000
	mov	$256, %cx					# CX = 256
	sub	%si, %si					# SI = 0
	sub	%di, %di					# DI = 0
	rep								# execute repeat util CX == 0, total 512 bytes
	movsw							# copy 2Bytes from DS:SI(0x07c00) to ES:DI(0x90000) 512 bytes

# 
# 跳转至$INITSEC:go处运行,INITSEG定义为0x9000
# 因此也就是跳转至下面的标号“go”的地方开始运行,这条语句会将CS设置为INITSEG
#
#	
	ljmp $INITSEG, $go				# jump 0x9000:go
go:	mov	%cs, %ax					# CS = 0x9000
	mov	%ax, %ds					# DS = 0x9000
	mov	%ax, %es					# ES = 0x9000
	mov	%ax, %ss					# SS = 0x9000, put stack top at 0x9ff00
#
# 此处设置栈顶地址为0x9ff00
# 因为bootsec占用512字节,setup占用512*4个字节,从0x90000开始存放bootsect和setup,末尾地址为0x90a00
# 而x86的栈为FD栈,满减栈,因此从0x90a00到0x9ff00的空间都是可以用,栈顶指针初始值为0x9ff00
#
	mov	$0xff00, %sp				# x86 FD stack [full decrease stack]
	                                # we will copy 4 sectors(2048) form boot device
	                                # code in 0x90000->0x90a00 and stack top 0x9ff00

# load the setup-sectors directly after the bootblock.
# Note that 'es' is already set up.
#
# 下面一段代码使用BOIS系统调用从第二个扇区读,共读取4个扇区,2048个字节,我们
# 通过前面的注释可是直到setup模块,刚好占用4个扇区,下面代码的左右就是从第二个
# 扇区开始读取数据,存放在当前数据段的0x200处,也就是0x90200处,读取成功后挑战至
# ok_load_setup处开始运行,读取失败后继续进行尝试读取
# 目前我们只需知道其含义即可,具体可参考<Linux内核完全注释的讲解>
#
load_setup:
	mov	$0x0000, %dx				# drive 0, head 0
	mov	$0x0002, %cx				# sector 2, track 0
	mov	$0x0200, %bx				# address = 512, in INITSEG
	mov $0x200+SETUPLEN, %ax		# service 2, nr of sectors, SETUPLEN is 4
	int	$0x13						# read it
	jnc	ok_load_setup				# ok - continue
	mov	$0x0000, %dx				
	mov	$0x0000, %ax				# reset the diskette
	int	$0x13
	jmp	load_setup
    
ok_load_setup:

#
# Get disk drive parameters, specifically nr of sectors/track
# 获取当前软盘驱动的参数放在sectors处
#
	mov	$0x00, %dl
	mov	$0x0800, %ax				# AH=8 is get drive parameters
	int	$0x13
	mov	$0x00, %ch
	mov	%cx, %cs:sectors+0			# %cs means sectors is in %cs
	mov	$INITSEG, %ax
	mov	%ax, %es					# restore ES
#
# 使用系统调用打印:Loading system ...
# 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

# 
# 读取SYS模块,存放在地址0x10000(64K)开始的地方,
# 根据前面的SYSSIZE定义我们知道一共读取0x3000*16个字节也就是192KB的内容
# 对于我们来说已经够了,我们可以计算出当前的最大地址为64 + 192 = 256KB,
# 不能覆盖到bootsect和setup模块的起始地址
# ok, we've written the message, now
# we want to load the system (at 0x10000)
#
	mov	$SYSSEG, %ax				# AX = 0x1000
	mov	%ax, %es					# ES = 0x1000 segment of 0x010000
	call read_it					# 读取system模块到0x10000处,知道就行,暂时不关注
	call kill_motor					# 关闭驱动

#
# 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.
#

	mov	%cs:root_dev+0, %ax			# 获取root_dev的参数
	cmp	$0, %ax						# 和0进行对比
	jne	root_defined				# 如果不为0说明,root_dev已经定义,跳转到root_defined处,root_dev的值存放在ax寄存器中
	mov	%cs:sectors+0, %bx			# 否则获取sectors处的数据,根据sectors的数据判断是1.2M还是1.44M的软盘
	mov	$0x0208, %ax				# /dev/ps0 - 1.2Mb
	cmp	$15, %bx					# 1.2M的sectors数目是15个
	je	root_defined				# 如果相等跳转到root_defined处
	mov	$0x021c, %ax				# /dev/PS0 - 1.44Mb
	cmp	$18, %bx					# 1.44M的sectors的数目是18个
	je	root_defined				# 如果相等跳转到root_defined处
undef_root:
	jmp undef_root
root_defined:
	mov	%ax, %cs:root_dev+0			# 将ax的值存放在root_dev处
# 
# 当所有的模块都加载完成后,跳转到0x09200地址处运行,我们知道此处是setup的地址
# 跳转到 SETUPSEG的 0 偏移开始运行,SETUPSEG为0x9020,即地址0x90200,
#
# 目前,
# bootsect在0x90000地址处共512字节
# setup在0x90200地址处共2KB
# system模块在0x10000(64KB)地址处共192KB字节
# 以上都在实模式的1MB访问空间内
#
# after that (everyting loaded), we jump to
# the setup-routine loaded directly after
# the bootblock:
#

	ljmp $SETUPSEG, $0				# setup code

# 
# 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
head:	.word 0						# current head
track:	.word 0						# current track

read_it:
	mov	%es, %ax					# AX = 0x1000
	test $0x0fff, %ax				# ES must be 64KB boundary
die:	
	jne die							# es must be at 64kB boundary
	xor %bx, %bx					# bx is starting address within segment
rp_read:
	mov %es, %ax
 	cmp $ENDSEG, %ax				# have we loaded all yet?
	jb ok1_read
	ret
ok1_read:
	#seg cs
	mov	%cs:sectors+0, %ax
	sub	sread, %ax
	mov	%ax, %cx
	shl	$9, %cx
	add	%bx, %cx
	jnc ok2_read
	je 	ok2_read
	xor %ax, %ax
	sub %bx, %ax
	shr $9, %ax
ok2_read:
	call read_track
	mov %ax, %cx
	add sread, %ax
	#seg cs
	cmp %cs:sectors+0, %ax
	jne ok3_read
	mov $1, %ax
	sub head, %ax
	jne ok4_read
	incw track 
ok4_read:
	mov	%ax, head
	xor	%ax, %ax
ok3_read:
	mov	%ax, sread
	shl	$9, %cx
	add	%cx, %bx
	jnc	rp_read
	mov	%es, %ax
	add	$0x1000, %ax
	mov	%ax, %es
	xor	%bx, %bx
	jmp	rp_read

read_track:
	push %ax
	push %bx
	push %cx
	push %dx
	mov	track, %dx
	mov	sread, %cx
	inc	%cx
	mov	%dl, %ch
	mov	head, %dx
	mov	%dl, %dh
	mov	$0, %dl
	and	$0x0100, %dx
	mov	$2, %ah
	int	$0x13
	jc	bad_rt
	pop	%dx
	pop	%cx
	pop	%bx
	pop	%ax
	ret
bad_rt:	mov	$0, %ax
	mov	$0, %dx
	int	$0x13
	pop	%dx
	pop	%cx
	pop	%bx
	pop	%ax
	jmp	read_track

#
# 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	$0x3f2, %dx
	mov	$0, %al
	outsb
	pop	%dx
	ret

sectors:
	.word 0

msg1:
	.byte 13,10
	.ascii "Loading system ..."
	.byte 13,10,13,10

	.org 508
root_dev:
	.word ROOT_DEV
boot_flag:
	.word 0xAA55
	
	.text
	endtext:
	.data
	enddata:
	.bss
	endbss:
	

 

posted on 2022-03-14 10:04  sudochen  阅读(93)  评论(0编辑  收藏  举报

导航