二、操作系統的引導及實驗

引導啓動程序

總體功能

從系統加電起執行的順序:
從系統加電起所執行程序的順序
當PC的電源打開後,CPU將自動進入實模式,從地址0xFFFF0開始自動執行代碼,這個地址通常是 ROM-BIOS 中的地址。 BIOS首先將進行一些基本的檢測,並從物理地址0x00000處開始加載中斷向量。
這就是爲什麼system模塊不直接加載到0地址的原因,這裏存有中斷向量。

對於Linux系統,除了在剛開始加載內核時需要用到BIOS提供的顯示和磁盤讀操作中斷功能,在內核正常運行之前則會在setup.s程序中重新初始化8259A芯片並且在head.s程序中重新設置一張中斷向量表(中斷描述符表)。完全拋棄了BIOS所提供的中斷服務功能。

8259A是專門爲了對8085A和8086/8088進行中斷控制而設計的芯片,它是可以用程序控制的中斷控制器。

因此在等使用完BIOS提供的中斷之後,纔可以覆蓋掉。

之後將啓動設備的第一個扇區:磁盤引導扇區bootsect(512字節),讀入到內存地址0x7C00處並跳轉執行。
bootsect是用8086彙編語言編寫的(源文件bootsect.s)。
首先,在被執行時會將自己移動到內存絕對地址0x90000處,並把啓動設備中的後2KB字節代碼讀入到0x90200處,而其他部分(system模塊)則被讀入到從內存地址0x10000處。

bootsect.s

bootsect.s代碼是磁盤引導塊程序,駐留在磁盤第一個扇區:(引導扇區,0 磁道(柱面),0 磁頭,第一個扇區)。
開機BIOS自檢後,會將引導扇區代碼bootsect加載到內存地址0x7C00開始出並執行。

在代碼執行期間,它會將自己移動到內存地址 0x90000 處並繼續執行。
該程序主要作用是:

  • 第2個扇區開始的4個扇區setup模塊加載到內存緊接着bootsect後面位置處0x90200(0x90000+一個扇區(512Bytes))
  • 利用BIOS 0x13 中斷取硬盤參數表中當前啓動引導盤的參數
  • 在屏幕上顯示 “Loading system…” 字符串。
  • 把在磁盤上setup模塊後面的system模塊加載到內存 0x10000 開始的地方。
  • 確定根文件系統的設備號,若無指定,則根據引導盤的每磁道扇區數判別出盤的類型和種類並保存到root_dev (引導扇區開始的第508地址處)

內核在磁盤上的分佈情況,一格代表一個扇區:

setup.s

setup是一個操作系統加載程序,主要作用是:

  • 利用BIOS中斷讀取系統數據,並將這些數據保存到 0x90000 開始的位置(覆蓋掉bootsect程序所在的地方,bootsect已執行完畢,此處是安全的)
    在這裏插入圖片描述
  • 隨後將system模塊從 0x10000 - 0x8FFFF(當時認爲system的長度不超過512KB)整塊向下移動到 0x00000 處。
  • 進入32位保護模式

head.s

這是system模塊的最開始的部分,在磁盤上被放置在setup模塊之後,即在第 6 個扇區開始放置,Linux 0.11 system 模塊大約有120KB,大約佔240個扇區
此時內核是在保護模式下運行,使用AT&T彙編語言,賦值方向從左至右

  • 加載各個數據段寄存器,重新設置IDT和GDT

  • 設置內存分頁

下圖是啓動引導時內核在內存中的位置變化:
在這裏插入圖片描述

實驗內容

此次實驗的基本內容是:

閱讀《Linux 內核完全註釋》的第 6 章,對計算機和 Linux 0.11 的引導過程進行初步的瞭解;
按照下面的要求改寫 0.11 的引導程序 bootsect.s
有興趣同學可以做做進入保護模式前的設置程序 setup.s。
改寫 bootsect.s 主要完成如下功能:

bootsect.s 能在屏幕上打印一段提示信息“XXX is booting…”,其中 XXX 是你給自己的操作系統起的名字,例如 LZJos、Sunix 等(可以上論壇上秀秀誰的 OS 名字最帥,也可以顯示一個特色 logo,以表示自己操作系統的與衆不同。)
改寫 setup.s 主要完成如下功能:

bootsect.s 能完成 setup.s 的載入,並跳轉到 setup.s 開始地址執行。而 setup.s 向屏幕輸出一行"Now we are in SETUP"。
setup.s 能獲取至少一個基本的硬件參數(如內存參數、顯卡參數、硬盤參數等),將其存放在內存的特定地址,並輸出到屏幕上。
setup.s 不再加載 Linux 內核,保持上述信息顯示在屏幕上即可。

bootsect.s的修改

其實很簡單,只需要 更改下msg1字符串以及顯示的長度(cx寄存器) 就可以了。
問題是,如果直接更改,這個字符串的長度一定會有所限制,因爲bootsect只佔用一個扇區(512 bytes)

附 BIOS 0x10 中斷功能表:
在這裏插入圖片描述
bootsect.s 文件修改部分的代碼(第92行左右):


! Print some inane message

	mov	ah,#0x03		! read cursor pos
	xor	bh,bh
	int	0x10
	
	mov	cx,#189			! 在此處進行改動
	mov	bx,#0x0007		! page 0, attribute 7 (normal)
	mov	bp,#msg1
	mov	ax,#0x1301		! write string, move cursor
	int	0x10


! 修改顯示的字符串
msg1:
.byte 13,10
.byte 13,10
.ascii "=================================="
.byte 13,10
.ascii "=                                ="
.byte 13,10
.ascii "=       Loading TEST-OS ...      ="
.byte 13,10
.ascii "=                                ="
.byte 13,10
.ascii "=================================="
.byte 13,10
.byte 13,10
.byte 13,10
.byte 13

運行截圖:
在這裏插入圖片描述
其實這裏的字符串長度爲189,如果再增加一個到190個時會越界,出現鏈接錯誤。
在這裏插入圖片描述

從 bootsect.s 讀入 setup.s ,並打印字符串

原版 bootsect.s 中有載入 setup 的這個操作,可以直接套用

setup 中要打印:“Now we are in SETUP”,跟在 bootsect 中類似,同樣調用 0x10 中斷輸出,所以可以直接套用,並將新的字符串命名爲 msg2。

修改部分的代碼:


! print message
    mov	ah,#0x03		! read cursor pos
	xor	bh,bh
	int	0x10
	mov	cx,#25
	mov	bx,#0x0007		! page 0, attribute 7 (normal)
	mov	bp,#msg2
    mov ax,cs 
    mov es,ax 
	mov	ax,#0x1301		! write string, move cursor
	int	0x10
    mov ax,cs
    mov es,ax
msg2:
    .byte 13,10
    .ascii "NOW we are in SETUP"
    .byte 13,10,13,10

setup.s 獲取基本硬件參數

根據 BIOS 中斷和功能號來獲取所需的硬件參數。
我們提取幾個參數即可:光標位置、內存大小、硬盤信息

  • 讀取光標位置(0x10, ah = 0x03):
    取當前光標位置(列、行),保存在 0x90000 處(2 Bytes)
! get cursor
    mov	ah,#0x03		! read cursor pos
	xor	bh,bh
	int	0x10
    mov [0],dx
  • 讀取內存大小(0x15, ah = 0x88):
    mov ah,#0x88
    int 0x15
    mov [2],ax
  • 讀取hd0磁盤信息:
    第一個磁盤參數表的首地址是中斷向量 0x41 的向量值,第二個緊接在第一個表的後面,中斷向量 0x46 的向量值,我們只考慮一個磁盤
    表的長度是16個字節,我們只需要將其複製到我們想要的地方即可。
! get hd0 data
    mov ax,#0x0000
    mov ds,ax 
    lds si,[4*0x41]  ! 中斷類型號*4即爲向量值(中斷函數入口地址)
    mov ax,#INITSEG
    mov es,ax
    mov di,#0x0004
    mov cx,#0x10
    rep
    movsb
  • 打印參數
    首先將16進制數轉成ASCII碼:
    0x30~0x39 對應數字 0~9,0x41~0x46 對應數字 a~f。從數字 9 到 a,其 ASCII 碼間隔了 7h.

由於小端的原因,高位在高地址,低位在低地址,需要對每一位十六進制數做處理,4位十六進制數循環4次

! 以 16 進制方式打印棧頂的16位數
print_hex:
! 4 個十六進制數字
    mov cx,#4     
! 將(bp)所指的值放入 dx 中,如果 bp 是指向棧頂的話
    mov dx,(bp)     
print_digit:
! 循環以使低 4 比特用上 !! 取 dx 的高 4 比特移到低 4 比特處。
    rol dx,#4    
! ah = 請求的功能值,al = 半字節(4 個比特)掩碼。    
! 我們需要功能號 ah=0e
    mov ax,#0xe0f 
! 取 dl 的低 4 比特值。
    and al,dl
! 給 al 數字加上十六進制 0x30    
    add al,#0x30     
    cmp al,#0x3a
! 是一個不大於十的數字
    jl  outp
! 是a~f,要多加 7    
    add al,#0x07      
outp: 
    int 0x10
    loop    print_digit
    ret
! 這裏用到了一個 loop 指令;
! 每次執行 loop 指令,cx 減 1,然後判斷 cx 是否等於 0。
! 如果不爲 0 則轉移到 loop 指令後的標號處,實現循環;
! 如果爲0順序執行。
! 
! 另外還有一個非常相似的指令:rep 指令,
! 每次執行 rep 指令,cx 減 1,然後判斷 cx 是否等於 0。
! 如果不爲 0 則繼續執行 rep 指令後的串操作指令,直到 cx 爲 0,實現重複。

!以上一輪循環使一位十六進制數從高位移到低位並轉爲ASCII碼

! 打印回車換行
print_nl:
! CR
    mov ax,#0xe0d     
    int 0x10
! LF
    mov al,#0xa     
    int 0x10
    ret

最後運行結果:
在這裏插入圖片描述

源代碼:

bootsect.s

!
! 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 = 0x8000
!
!	bootsect.s		(C) 1991 Linus Torvalds
!
! 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.

.globl begtext, begdata, begbss, endtext, enddata, endbss
.text
begtext:
.data
begdata:
.bss
begbss:
.text

SETUPLEN = 2				! 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

! load the setup-sectors directly after the bootblock.
! Note that 'es' is already set up.

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,#189
	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

! 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 ax,es
	test ax,#0x0fff
die:	jne die			! es must be at 64kB boundary
	xor bx,bx		! bx is starting address within segment
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
	shl cx,#9
	add cx,bx
	jnc ok2_read
	je ok2_read
	xor ax,ax
	sub ax,bx
	shr ax,#9
ok2_read:
	call read_track
	mov cx,ax
	add ax,sread
	seg cs
	cmp ax,sectors
	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 ax,#0x1000
	mov es,ax
	xor bx,bx
	jmp rp_read

read_track:
	push ax
	push bx
	push cx
	push dx
	mov dx,track
	mov cx,sread
	inc cx
	mov ch,dl
	mov dx,head
	mov dh,dl
	mov dl,#0
	and dx,#0x0100
	mov ah,#2
	int 0x13
	jc bad_rt
	pop dx
	pop cx
	pop bx
	pop ax
	ret
bad_rt:	mov ax,#0
	mov dx,#0
	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 dx,#0x3f2
	mov al,#0
	outb
	pop dx
	ret

sectors:
	.word 0

msg1:
.byte 13,10
.byte 13,10
.ascii "=================================="
.byte 13,10
.ascii "=                                ="
.byte 13,10
.ascii "=       Loading TEST-OS ...      ="
.byte 13,10
.ascii "=                                ="
.byte 13,10
.ascii "=================================="
.byte 13,10
.byte 13,10
.byte 13,10
.byte 13
.org 508
root_dev:
	.word ROOT_DEV
boot_flag:
	.word 0xAA55

.text
endtext:
.data
enddata:
.bss
endbss:

setup.s


INITSEG  = 0x9000		
entry _start
_start:
! print message
    mov	ah,#0x03		! read cursor pos
	xor	bh,bh
	int	0x10
	mov	cx,#25
	mov	bx,#0x0007		! page 0, attribute 7 (normal)
	mov	bp,#msg2
    mov ax,cs 
    mov es,ax 
	mov	ax,#0x1301		! write string, move cursor
	int	0x10
    mov ax,cs
    mov es,ax
! init ss:sp 
    mov ax,#INITSEG
    mov ss,ax
    mov sp,#0xFF00

! set
    mov ax,#INITSEG
    mov ds,ax

! get cursor
    mov	ah,#0x03		! read cursor pos
	xor	bh,bh
	int	0x10
    mov [0],dx

! get memory-size 
    mov ah,#0x88
    int 0x15
    mov [2],ax

! get hd0 data
    mov ax,#0x0000
    mov ds,ax 
    lds si,[4*0x41] 
    mov ax,#INITSEG
    mov es,ax
    mov di,#0x0004
    mov cx,#0x10
    rep
    movsb

! Ready to print
    mov ax,cs
    mov es,ax
    mov ax,#INITSEG
    mov ds,ax

!print cursor_pos

    mov ah,#0x03
    xor bh,bh
    int 0x10
    mov cx,#18
    mov bx,#0x0007
    mov bp,#msg_cursor
    mov ax,#0x1301
    int 0x10
    mov dx,[0]
    call print_hex

!print memory
    mov ah,#0x03
    xor bh,bh
    int 0x10
    mov cx,#14
    mov bx,#0x0007
    mov bp,#msg_memory
    mov ax,#0x1301
    int 0x10
    mov dx,[2]
    call    print_hex
! KB
    mov ah,#0x03
    xor bh,bh
    int 0x10
    mov cx,#2
    mov bx,#0x0007
    mov bp,#msg_kb
    mov ax,#0x1301
    int 0x10

!print Cyles
    mov ah,#0x03
    xor bh,bh
    int 0x10
    mov cx,#7
    mov bx,#0x0007
    mov bp,#msg_cyles
    mov ax,#0x1301
    int 0x10
    mov dx,[4]
    call print_hex

!Heads
    mov ah,#0x03
    xor bh,bh
    int 0x10
    mov cx,#8
    mov bx,#0x0007
    mov bp,#msg_heads
    mov ax,#0x1301
    int 0x10
    mov dx,[6]
    call print_hex

! Sectors
    mov ah,#0x03
    xor bh,bh
    int 0x10
    mov cx,#10
    mov bx,#0x0007
    mov bp,#msg_sectors
    mov ax,#0x1301
    int 0x10
    mov dx,[12]
    call print_hex

inf_loop:
    jmp inf_loop

print_hex:
    mov cx,#4
print_digit:
    rol dx,#4
    mov ax,#0xe0f
    and al,dl
    add al,#0x30
    cmp al,#0x3a
    jl outp
    add al,#0x07

outp:
    int 0x10
    loop print_digit
    ret
print_nl:
!CR
    mov ax,#0xe0d
    int 0x10
!LF
    mov al,#0xa
    int 0x10
    ret

msg2:
    .byte 13,10
    .ascii "NOW we are in SETUP"
    .byte 13,10,13,10
msg_cursor:
    .byte 13,10
    .ascii "Cursor position:"
msg_memory:
    .byte 13,10
    .ascii "Memory Size:"
msg_cyles:
    .byte 13,10
    .ascii "Cyls:"
msg_heads:
    .byte 13,10
    .ascii "Heads:"
msg_sectors:
    .byte 13,10
    .ascii "Sectors:"
msg_kb:
    .ascii "KB"

.org 510
boot_flag:
    .word 0xAA55

參考:
趙炯. Linux 內核 0.11 完全註釋 [CP/OL].:上海同濟大學機械電子工程研究所, 2007:6-7.
實驗樓-操作系統原理與實踐
操作系統(哈工大李治軍老師)32講

發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章