3. 加載第二部分內核代碼--setup

1. bootsect對內存的規劃

BIOS已經把bootsect也就是引導程序載入內存了,現在它的作用就是把第二批和第三批程序陸續加載到內存中。為了把第二批和第三批程序加載到內存中適當的位置。bootsect首先要做的工作就是規劃內存。

  • 通常,我們用高級語言編寫應用程序,這些程序是在操作系統的平臺上運行的。我們只管寫高級語言的代碼、數據。至于代碼、數據在運行的時候放在內存的什么地方,是否會相互覆蓋,我們都不需care,OS和高級語言編譯器替我們做了大量的看護工作,確保不會出錯。而OS本身使用的是匯編語言,沒有高級語言編譯器替OS系統保障,只有靠OS的設計者把內存的安排想清楚,確保無論OS如何運行,都不會出現代碼與代碼、數據與數據、代碼與數據之間相互覆蓋的情況。為了更準確理解OS的運行機制,我們必須清楚OS的設計者是如何規劃內存的。

在實模式下,尋址的最大范圍是1MB。為了規劃內存,bootsect首先設計了如下代碼:
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

這些源碼的作用是對后續操作所涉及的內存位置進行設置,包括:

  • 將要加載的setup程序的扇區數(SETUPLEN)以及被加載到的位置(SETUPSEG)
  • 啟動扇區被BIOS加載的位置(BOOTSEG)及將要移動到的新位置(INTSEG)
  • 內核(Kernel)被加載的位置(SYSSEG)
  • 內核的末尾位置(ENDSEG)
  • 根文件系統設備號(ROOT_DEV)

設置這些位置就是為了確保將要載入內存的代碼與已經載入內存的代碼及數據各在其位,互不覆蓋,并且各自有足夠用的內存空間。

  • 操作系統的設計者要全面地、整體地考慮內存的規劃。

2. 賦值bootsect

接下來,bootsect啟動程序將它自身(全部512B內容)從內存0x07C00(BOOTSEG)處復制到內存0x90000(INITSEG)處。
執行這個操作的代碼(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)聯合使用,構成了源地址0x07C00
  • es(0x9000)和di(0x0000)聯合使用,構成了目的地址0x90000
  • mov cx,#256這一行循環控制量,提供了需要復制的“字”數(一個字2字節,256個字剛好是512字節,也就是第一個扇區的字節數)
jmpi    go,INITSEG
go: mov ax,cs

這兩行代碼寫的很巧。復制bootsect完成后,在內存的0x07C00和0x90000位置有兩段完全相同的代碼。復制代碼這件事本身也是要靠指令執行的。

  • 執行指令的過程就是CS和IP不斷變化的過程。
  • 執行到jmpi go,INITSEG之前,代碼的作用就是復制代碼自身;
  • 執行了jmpi go,INITSEG之后,程序就轉到了執行0x90000這邊的代碼了。Linus的設計意圖是,想在跳轉之后,在新的位置接著執行后面的mov ac, cs, 而不是死循環。
  • jmpi go,INITSEGgo: mov ax,cs配合,巧妙地實現了“到新位置后接著原來的執行序繼續執行下去”的目的。
    bootsect復制到了新的地方,并且要在新的地方繼續執行。因為代碼的整體位置發生了變化,所以代碼中的各段也會發生變化。前面已經改變了CS,現在對DS、ES、SS和SP進行調整。
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來把數據段寄存器(DS)、附加段寄存器(ES)、棧寄存器(SS)設置成與代碼段寄存器(CS)相同的位置,并將棧頂指針SP指向偏移地址為0xFF00處。

  • SS與SP聯合使用,就構成了棧數據在內存中的位置值。對這兩個寄存器的設置為后面程序的棧操作(push、pop...)打下了基礎。
  • 這里對SS、SP進行的設置是分水嶺,它標志著從現在開始,程序可以執行一些更為復雜的數據運算類指令了。
  • 從代碼開始出4個mov指令可以看出,系統給BIOS中斷服務程序傳參是通過幾個通用寄存器實現的。這是匯編程序的常用方法。
  • 參數傳遞完畢后,執行int 0x13指令,產生0x13中斷,通過中斷向量表找到這個中斷服務(0x90200)處,0x90200緊挨著bootsect的尾端,所以bootsect和setup是連在一起的。

1.2.3 加載第三部分內核代碼--system模塊

第2批代碼已經載入內存,現在要加載第3批代碼。仍然適用BIOS提供的int 0x13中斷。
bootsect程序執行第3批程序載入工作,將系統模塊載入內存。

  • 這次載入底層技術上與setup沒有差別。
  • 這次加載的扇區是240個,需要的時間較長。為了防止加載期間用戶誤認為是機器故障而執行不當操作,linus在此設計了一行屏幕顯示信息“Loading system...”以提示用戶此時正在加載OS。此時OS的main函數還未開始執行,這一行顯示需要用匯編來實現。
    • 從體系角度看,顯示器也是一個外設,所以還要用到其他BIOS中斷
  • bootsect借助BIOS int 0x13中斷,將system加載到內存。加載工作主要由bootsect調研read_it子程序完成。
    • 這個子程序將軟盤第6個扇區開始的約240個扇區的system模塊加載至內存的SYSSEG(0x10000)處往后的120KB空間中。
    • 由于是長時間操作軟盤,所以需要對軟盤設備進行更多監控,對讀盤結果不斷進行檢測。

到此為止,第3批程序已經加載完畢,整個OS的代碼已經全部加在至內存。bootsect的主體工作已經做完。還有一點小事就是,再次確定一下根設備號。

根文件系統設備(Root Device): Linux0.11使用Minix操作系統的文件系統管理方式,要求系統必須存在一個根文件系統,其他文件系統掛接其上,而不是同等地位 。

bootsect程序的人物已經完成,通過執行"jmpi 0, SETUPSEG"語句跳轉至0x90200處,就是setup程序加載的位置。CS:IP指向setup程序的第一條指令,意味著由setup程序接著bootsect程序繼續執行。

  • setup程序現在開始執行。它做的第一件事情就是利用BIOS提供的中斷服務程序從設備上提取內核運行所需的機器系統數據,包括光標位置、顯示頁面等數據,并分別從中斷向量0x41和0x46向量值所指的內存地址處獲取硬盤參數表1、2,把他們存放在0x9000:0x0080和0x9000:0x0090處。
  • 這些機器系統數據被加載到內存0x90000 ~ 0x901FC位置。這些數據將在以后的main函數執行時發揮重要作用。
  • BIOS提取的機器系統數據將覆蓋bootsect程序所在部分區域。這些數據由于是要留用的,所以在它們失去使用價值前不能被覆蓋掉。
  • 在空間上OS對內存嚴格按需使用。
  • 在時間上,使用完畢的內存立即挪做它用。

到此為止,OS內核程序的加載工作已完成。接下來,系統通過已加載到內存中的代碼,將實現從實模式到保護模式的轉變,使Linux0.11真正成為“現代”操作系統。

1.3 開始向32位模式轉變,為mian函數的調用做準備

  • 接下來,OS要使計算機在32位保護模式下工作。這期間要做吧大量的重建工作,并且持續工作到OS的main函數執行過程中。操作系統執行的操作包括:
    • 打開32位的尋址空間
    • 打開保護模式
    • 建立保護模式下的中斷響應機制等與保護模式配套的相關工作
    • 建立內存的分頁機制
    • 做好調用main函數的準備

《Linux內核設計的藝術第2版》學習筆記

最后編輯于
?著作權歸作者所有,轉載或內容合作請聯系作者
平臺聲明:文章內容(如有圖片或視頻亦包括在內)由作者上傳并發布,文章內容僅代表作者本人觀點,簡書系信息發布平臺,僅提供信息存儲服務。

推薦閱讀更多精彩內容