1. bootsect對內(nèi)存的規(guī)劃
BIOS已經(jīng)把bootsect也就是引導(dǎo)程序載入內(nèi)存了,現(xiàn)在它的作用就是把第二批和第三批程序陸續(xù)加載到內(nèi)存中。為了把第二批和第三批程序加載到內(nèi)存中適當?shù)奈恢谩ootsect首先要做的工作就是規(guī)劃內(nèi)存。
- 通常,我們用高級語言編寫應(yīng)用程序,這些程序是在操作系統(tǒng)的平臺上運行的。我們只管寫高級語言的代碼、數(shù)據(jù)。至于代碼、數(shù)據(jù)在運行的時候放在內(nèi)存的什么地方,是否會相互覆蓋,我們都不需care,OS和高級語言編譯器替我們做了大量的看護工作,確保不會出錯。而OS本身使用的是匯編語言,沒有高級語言編譯器替OS系統(tǒng)保障,只有靠OS的設(shè)計者把內(nèi)存的安排想清楚,確保無論OS如何運行,都不會出現(xiàn)代碼與代碼、數(shù)據(jù)與數(shù)據(jù)、代碼與數(shù)據(jù)之間相互覆蓋的情況。為了更準確理解OS的運行機制,我們必須清楚OS的設(shè)計者是如何規(guī)劃內(nèi)存的。
在實模式下,尋址的最大范圍是1MB。為了規(guī)劃內(nèi)存,bootsect首先設(shè)計了如下代碼:
bootsect.s
.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 = 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
這些源碼的作用是對后續(xù)操作所涉及的內(nèi)存位置進行設(shè)置,包括:
- 將要加載的setup程序的扇區(qū)數(shù)(SETUPLEN)以及被加載到的位置(SETUPSEG)
- 啟動扇區(qū)被BIOS加載的位置(BOOTSEG)及將要移動到的新位置(INTSEG)
- 內(nèi)核(Kernel)被加載的位置(SYSSEG)
- 內(nèi)核的末尾位置(ENDSEG)
- 根文件系統(tǒng)設(shè)備號(ROOT_DEV)
設(shè)置這些位置就是為了確保將要載入內(nèi)存的代碼與已經(jīng)載入內(nèi)存的代碼及數(shù)據(jù)各在其位,互不覆蓋,并且各自有足夠用的內(nèi)存空間。
- 操作系統(tǒng)的設(shè)計者要全面地、整體地考慮內(nèi)存的規(guī)劃。
2. 賦值bootsect
接下來,bootsect啟動程序?qū)⑺陨?全部512B內(nèi)容)從內(nèi)存0x07C00(BOOTSEG)處復(fù)制到內(nèi)存0x90000(INITSEG)處。
執(zhí)行這個操作的代碼(bootsect.s)
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.
- ds(0x07C0)和si(0x0000)聯(lián)合使用,構(gòu)成了源地址0x07C00
- es(0x9000)和di(0x0000)聯(lián)合使用,構(gòu)成了目的地址0x90000
- mov cx,#256這一行循環(huán)控制量,提供了需要復(fù)制的“字”數(shù)(一個字2字節(jié),256個字剛好是512字節(jié),也就是第一個扇區(qū)的字節(jié)數(shù))
jmpi go,INITSEG
go: mov ax,cs
這兩行代碼寫的很巧。復(fù)制bootsect完成后,在內(nèi)存的0x07C00和0x90000位置有兩段完全相同的代碼。復(fù)制代碼這件事本身也是要靠指令執(zhí)行的。
- 執(zhí)行指令的過程就是CS和IP不斷變化的過程。
- 執(zhí)行到
jmpi go,INITSEG之前,代碼的作用就是復(fù)制代碼自身; - 執(zhí)行了
jmpi go,INITSEG之后,程序就轉(zhuǎn)到了執(zhí)行0x90000這邊的代碼了。Linus的設(shè)計意圖是,想在跳轉(zhuǎn)之后,在新的位置接著執(zhí)行后面的mov ac, cs, 而不是死循環(huán)。 -
jmpi go,INITSEG與go: mov ax,cs配合,巧妙地實現(xiàn)了“到新位置后接著原來的執(zhí)行序繼續(xù)執(zhí)行下去”的目的。
bootsect復(fù)制到了新的地方,并且要在新的地方繼續(xù)執(zhí)行。因為代碼的整體位置發(fā)生了變化,所以代碼中的各段也會發(fā)生變化。前面已經(jīng)改變了CS,現(xiàn)在對DS、ES、SS和SP進行調(diào)整。
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.
上述代碼通過ax,用CS的值0x9000來把數(shù)據(jù)段寄存器(DS)、附加段寄存器(ES)、棧寄存器(SS)設(shè)置成與代碼段寄存器(CS)相同的位置,并將棧頂指針SP指向偏移地址為0xFF00處。
- SS與SP聯(lián)合使用,就構(gòu)成了棧數(shù)據(jù)在內(nèi)存中的位置值。對這兩個寄存器的設(shè)置為后面程序的棧操作(push、pop...)打下了基礎(chǔ)。
- 這里對SS、SP進行的設(shè)置是分水嶺,它標志著從現(xiàn)在開始,程序可以執(zhí)行一些更為復(fù)雜的數(shù)據(jù)運算類指令了。
- 從代碼開始出4個mov指令可以看出,系統(tǒng)給BIOS中斷服務(wù)程序傳參是通過幾個通用寄存器實現(xiàn)的。這是匯編程序的常用方法。
- 參數(shù)傳遞完畢后,執(zhí)行int 0x13指令,產(chǎn)生0x13中斷,通過中斷向量表找到這個中斷服務(wù)(0x90200)處,0x90200緊挨著bootsect的尾端,所以bootsect和setup是連在一起的。
1.2.3 加載第三部分內(nèi)核代碼--system模塊
第2批代碼已經(jīng)載入內(nèi)存,現(xiàn)在要加載第3批代碼。仍然適用BIOS提供的int 0x13中斷。
bootsect程序執(zhí)行第3批程序載入工作,將系統(tǒng)模塊載入內(nèi)存。
- 這次載入底層技術(shù)上與setup沒有差別。
- 這次加載的扇區(qū)是240個,需要的時間較長。為了防止加載期間用戶誤認為是機器故障而執(zhí)行不當操作,linus在此設(shè)計了一行屏幕顯示信息“Loading system...”以提示用戶此時正在加載OS。此時OS的main函數(shù)還未開始執(zhí)行,這一行顯示需要用匯編來實現(xiàn)。
- 從體系角度看,顯示器也是一個外設(shè),所以還要用到其他BIOS中斷
- bootsect借助BIOS int 0x13中斷,將system加載到內(nèi)存。加載工作主要由bootsect調(diào)研read_it子程序完成。
- 這個子程序?qū)④洷P第6個扇區(qū)開始的約240個扇區(qū)的system模塊加載至內(nèi)存的SYSSEG(0x10000)處往后的120KB空間中。
- 由于是長時間操作軟盤,所以需要對軟盤設(shè)備進行更多監(jiān)控,對讀盤結(jié)果不斷進行檢測。
到此為止,第3批程序已經(jīng)加載完畢,整個OS的代碼已經(jīng)全部加在至內(nèi)存。bootsect的主體工作已經(jīng)做完。還有一點小事就是,再次確定一下根設(shè)備號。
根文件系統(tǒng)設(shè)備(Root Device): Linux0.11使用Minix操作系統(tǒng)的文件系統(tǒng)管理方式,要求系統(tǒng)必須存在一個根文件系統(tǒng),其他文件系統(tǒng)掛接其上,而不是同等地位 。
bootsect程序的人物已經(jīng)完成,通過執(zhí)行"jmpi 0, SETUPSEG"語句跳轉(zhuǎn)至0x90200處,就是setup程序加載的位置。CS:IP指向setup程序的第一條指令,意味著由setup程序接著bootsect程序繼續(xù)執(zhí)行。
- setup程序現(xiàn)在開始執(zhí)行。它做的第一件事情就是利用BIOS提供的中斷服務(wù)程序從設(shè)備上提取內(nèi)核運行所需的機器系統(tǒng)數(shù)據(jù),包括光標位置、顯示頁面等數(shù)據(jù),并分別從中斷向量0x41和0x46向量值所指的內(nèi)存地址處獲取硬盤參數(shù)表1、2,把他們存放在0x9000:0x0080和0x9000:0x0090處。
- 這些機器系統(tǒng)數(shù)據(jù)被加載到內(nèi)存0x90000 ~ 0x901FC位置。這些數(shù)據(jù)將在以后的main函數(shù)執(zhí)行時發(fā)揮重要作用。
- BIOS提取的機器系統(tǒng)數(shù)據(jù)將覆蓋bootsect程序所在部分區(qū)域。這些數(shù)據(jù)由于是要留用的,所以在它們失去使用價值前不能被覆蓋掉。
- 在空間上OS對內(nèi)存嚴格按需使用。
- 在時間上,使用完畢的內(nèi)存立即挪做它用。
到此為止,OS內(nèi)核程序的加載工作已完成。接下來,系統(tǒng)通過已加載到內(nèi)存中的代碼,將實現(xiàn)從實模式到保護模式的轉(zhuǎn)變,使Linux0.11真正成為“現(xiàn)代”操作系統(tǒng)。
1.3 開始向32位模式轉(zhuǎn)變,為mian函數(shù)的調(diào)用做準備
- 接下來,OS要使計算機在32位保護模式下工作。這期間要做吧大量的重建工作,并且持續(xù)工作到OS的main函數(shù)執(zhí)行過程中。操作系統(tǒng)執(zhí)行的操作包括:
- 打開32位的尋址空間
- 打開保護模式
- 建立保護模式下的中斷響應(yīng)機制等與保護模式配套的相關(guān)工作
- 建立內(nèi)存的分頁機制
- 做好調(diào)用main函數(shù)的準備
《Linux內(nèi)核設(shè)計的藝術(shù)第2版》學(xué)習筆記