3. 加載第二部分內(nèi)核代碼--setup

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,INITSEGgo: 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é)習筆記

最后編輯于
?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請聯(lián)系作者
【社區(qū)內(nèi)容提示】社區(qū)部分內(nèi)容疑似由AI輔助生成,瀏覽時請結(jié)合常識與多方信息審慎甄別。
平臺聲明:文章內(nèi)容(如有圖片或視頻亦包括在內(nèi))由作者上傳并發(fā)布,文章內(nèi)容僅代表作者本人觀點,簡書系信息發(fā)布平臺,僅提供信息存儲服務(wù)。

相關(guān)閱讀更多精彩內(nèi)容

友情鏈接更多精彩內(nèi)容