二、操作系統的引導及實驗
引導啓動程序
總體功能
從系統加電起執行的順序:
當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講