Linux0.11_bootsect.s

157 阅读5分钟

Linux0.11源码笔记

bootsect.s_1

loaded at 0x7c00 by the bios-startup routines, and moves

iself out of the way to address 0x90000, and jumps there.

SETUPLEN = 4                ; nr of setup-sectors
BOOTSEG  = 0x07c0           ; original address of boot-sector
INITSEG  = 0x9000           ; we move boot here - out of the way
SETUPSEG = 0x9020           ; setup starts here
SYSSEG   = 0x1000           ; system loaded at 0x10000 (65536).
ENDSEG   = SYSSEG + SYSSIZE     ; where to stop loading; ROOT_DEV: 0x000 - same type of floppy as boot.
;       0x301 - first partition on first drive etc
ROOT_DEV = 0x306
​
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
    go: mov ax,cs
    mov ds,ax
    mov es,ax
; put stack at 0x9ff00.
    mov ss,ax
    mov sp,#0xFF00      ; arbitrary value >>512

在系统启动时,BIOS会将硬盘中启动区的512字节数据,原封不动的放到**0x7c00**也就对应着源码中定义的: BOOTSEG = 0x07c0 地址处,并跳转到那个位置进行执行

启动区的定义非常简单,只要硬盘中的 0 盘 0 道 1 扇区的 512 个字节的最后两个字节分别是 0x550xaa,那么 BIOS 就会认为它是个启动区

    mov ax,#BOOTSEG
    mov ds,ax

在x86 16位实模式下(当时也没有保护模式的概念),内存地址是通过两个16位的地址拼成20位的地址来完成的,实际的操作就是 段地址<< 1 + 偏移地址 给出

所以此处,我们将0x07c0传入ds中作为数据段的地址,这么做的原因在于,我们开头说了BIOS会将硬盘中的512字节数据原封不动放到0x7c00处,而根据16位实模式下内存地址的规则,此处我们将0x07c0传入ds后,左移一位之后就会变成0x7c00,正好指向此处,方便以后通过这种基址的方式来访问内存中的数据

    mov ax,#INITSEG
    mov es,ax
    mov cx,#256
    sub si,si
    sub di,di

说这段之前先来看一下#INITSEG在源代码中的注释:; we move boot here - out of the way,意思是接下来我们要将boot区这512字节的代码由0x07c0处复制到0x9000处,为什么呢,out of the way!

为了完成这个操作,我么需要完成以下前置铺垫

  • 0x9000存入另一个段寄存器用于复制

    mov ax,#INITSEG
    mov es,ax
    
  • 要复制的数据有512字节,所以肯定要借助循环实现

    mov cx,#256
    sub si,si
    sub di,di
    

    将cx寄存器置为256:接下来要循环256次,由此可推断,每次要复制2字节(16位)的数据

    si di两个寄存器清0,作为接下来复制操作偏移地址的寄存器

rep
movw
jmpi    go,INITSEG

此处的作用的是将ds:si开始的512字节复制到es:di处,然后再跳到**0x9000:go**处继续执行,这样也就成功的将cs:ip指向了0x9000段下go的位置(此时0x7c00段的数据并没有被清空)

此处发生了一个很有意思的事情,当我到网上搜rep movw含义时,普遍给出的答案是:rep的作用是重复执行后面的代码cx次,movw的作用是将ds:si的数据以每次复制2字节(word)的方式,复制到es:di

可是这解释很不对劲,movw的功能未免太过于强大了,于是查阅了Intel® 手册卷2,果然上述解释是有问题的,这个功能来自于rep

微信截图_20220530164643.png

Move CX bytes/words/double words DS:[(E)SI] to ES:[(E)DI] 将cx单位的数据从ds:si移动到es:di(16位)

微信截图_20220530170629.png

go: mov ax,cs
    mov ds,ax
    mov es,ax
; put stack at 0x9ff00.
    mov ss,ax
    mov sp,#0xFF00      ; arbitrary value >>512

此时已转移到0x9000处(cs:0x9000),接下来将ds、es、ss寄存器都分别置为0x9000

由于栈是自上而下增长的,所以将sp寄存器的值置为0xFF00,此时栈顶指针就指向了0x9FF00

此处由注释可知对于sp的值只要 arbitrary value >> 512 (任意值大于512即可 0x90200)因为从0x90200地址开始处要放置setup程序

微信截图_20220601100110.png

bootsect.s_2

It then loads 'setup' directly after itself (0x90200), and the system

at 0x10000, using BIOS interrupts.

load_setup:
    mov dx,#0x0000      ; 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
    mov dx,#0x0000
    mov ax,#0x0000      ; reset the diskette
    int 0x13
    j   load_setup
ok_load_setup:
​
; Get disk drive parameters, specifically nr of sectors/track
​
    mov dl,#0x00
    mov ax,#0x0800      ; AH=8 is get drive parameters
    int 0x13
    mov ch,#0x00
    seg cs
    mov sectors,cx
    mov ax,#INITSEG
    mov es,ax
​
; Print some inane message
​
    mov ah,#0x03        ; read cursor pos
    xor bh,bh
    int 0x10
    
    mov cx,#24
    mov bx,#0x0007      ; page 0, attribute 7 (normal)
    mov bp,#msg1
    mov ax,#0x1301      ; write string, move cursor
    int 0x10
​
; ok, we've written the message, now
; we want to load the system (at 0x10000)
​
    mov ax,#SYSSEG
    mov es,ax       ; segment of 0x010000
    call    read_it
    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.
​
    seg cs
    mov ax,root_dev
    cmp ax,#0
    jne root_defined
    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
​
; after that (everyting loaded), we jump to
; the setup-routine loaded directly after
; the bootblock:
​
    jmpi    0,SETUPSEG

如作者的介绍说,接下来的代码作用在于:使用BIOS中断程序将setup(由system.s编译而成)直接加载到启动子程序后(0x900 << 1 + 200h = 0x90200h),并将system(由head.s编译而成)加载到地址0x10000

加载setup(setup.s)

load_setup:
	mov	dx,#0x0000		; 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

INT 0X13传参的含义

0x13 号中断的处理程序是 BIOS 提前给我们写好的,读取磁盘的相关功能的函数。

  • dx

    • dh:磁头号
    • dl:驱动器号(若为硬盘则位7要置位)
  • cx

    • ch:磁道(柱面)号的低8位
    • cl:开始扇区(05位),磁道号(67位)
  • bx

    • es:bx指向数据缓冲区(出错则CF标志位置位)
  • ax

    • ah:0x02 - 读磁盘扇区到内存
    • al:需要读出的扇区数量

所以对于上述传参的含义为:

mov	dx,#0x0000		; 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

将0号驱动器0号磁头的第cx(2)个扇区开始的数据读取到es:bx处(0x90200h),共读取SETUPLEN(4)个扇区

jnc	ok_load_setup		; ok - continue

CF=0没有出现错误)则跳转到ok_load_setup

mov	dx,#0x0000
mov	ax,#0x0000		; reset the diskette
int	0x13
j	load_setup

若上面jnc的跳转失败,则复位驱动器,并重复执行上述过程,一直不成功则一直循环

加载system(head.s开头)

ok_load_setup:

; Get disk drive parameters, specifically nr of sectors/track

	mov	dl,#0x00
	mov	ax,#0x0800		; AH=8 is get drive parameters
	int	0x13
	mov	ch,#0x00
	seg cs
	mov	sectors,cx
	mov	ax,#INITSEG 
	mov	es,ax         ;重置es的值为0x9000

; Print some inane message

	mov	ah,#0x03		; read cursor pos
	xor	bh,bh
	int	0x10
	
	mov	cx,#24
	mov	bx,#0x0007		; page 0, attribute 7 (normal)
	mov	bp,#msg1
	mov	ax,#0x1301		; write string, move cursor
	int	0x10

; ok, we've written the message, now
; we want to load the system (at 0x10000)

	mov	ax,#SYSSEG
	mov	es,ax		; segment of 0x010000
	call	read_it ;读取system模块
	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.

	seg cs
	mov	ax,root_dev
	cmp	ax,#0
	jne	root_defined
	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

; after that (everyting loaded), we jump to
; the setup-routine loaded directly after
; the bootblock:

	jmpi	0,SETUPSEG

ok_load_setup部分的代码的主要作用

  • 取磁盘驱动器参数

  • 在显示器上显示一些信息

  • system模块加载到0x10000 (重要)

    把从硬盘第 6 个扇区开始往后的 240 个扇区,加载到内存 0x10000 处

jmpi	0,SETUPSEG

bootsect.s结束,并跳转到0x90200开始执行setup