網(wǎng)站備案需要花錢嗎東莞網(wǎng)絡(luò)推廣平臺(tái)
作者:小牛呼嚕嚕 | https://xiaoniuhululu.com
計(jì)算機(jī)內(nèi)功、源碼解析、科技故事、項(xiàng)目實(shí)戰(zhàn)、面試八股等更多硬核文章,首發(fā)于公眾號(hào)「小牛呼嚕?!?/p>
文章目錄
- 回顧計(jì)算機(jī)啟動(dòng)過程
- 8086、80x86是什么意思?
- 寄存器初始化CS:IP
- CPU是如何和ROM相連的?
- 加載MBR到內(nèi)存中
- bootsect.S具體干了什么?
- 設(shè)置段基址 & 內(nèi)存分段機(jī)制
- bootsect的"再次搬家"到0x90000
- 加載setup.s到內(nèi)存0x90200
- 加載system到內(nèi)存0x10000
- 尾語
大家好,我是呼嚕嚕,在上一篇文章聊聊x86計(jì)算機(jī)啟動(dòng)發(fā)生的事?我們了解了x86計(jì)算機(jī)啟動(dòng)過程,MBR、0x7c00是什么?其中當(dāng)bios引導(dǎo)結(jié)束后,操作系統(tǒng)接過計(jì)算機(jī)的控制權(quán)后,發(fā)生了哪些事?本文將揭開迷霧的序章-Bootsect.S
回顧計(jì)算機(jī)啟動(dòng)過程
我們先來回顧一下,上古時(shí)期計(jì)算機(jī)按下電源鍵的啟動(dòng)過程,這里以8086架構(gòu)為例:
8086、80x86是什么意思?
有許多人不知道 經(jīng)常遇到的8086、80x86是什么意思?我們簡(jiǎn)單科普一下:
- 8086是Intel公司推出的最早,也是最流行的面向個(gè)人電腦的CPU型號(hào)
- x86泛指一系列基于Intel 8086且向后兼容的中央處理器指令集架構(gòu),由于以“86”作為結(jié)尾,因此其架構(gòu)被稱為"x86"
- 80x86也就是在8086基礎(chǔ)上的增強(qiáng)版,包括80286,80386,80486,其后面就是我們所熟悉的奔騰、酷睿、i5、i7等等
寄存器初始化CS:IP
相比于上一篇文章聊聊x86計(jì)算機(jī)啟動(dòng)發(fā)生的事,我們這里再講細(xì)致點(diǎn),當(dāng)計(jì)算機(jī)一按下電源后,8086CPU就處于實(shí)模式的狀態(tài),此時(shí)會(huì)將CPU的寄存器初始化為CS=0xFFFF;IP=0x0000
,也就是實(shí)際物理地址0xFFFF0
(CS左移4位+IP)
CS : 代碼段寄存器;IP : 指令指針寄存器。CS:IP指向的內(nèi)容 會(huì)被CPU當(dāng)做計(jì)算機(jī)指令去執(zhí)行
那么從地址0xFFFF0
中取出來的指令是什么?我們知道當(dāng)電路通電后,內(nèi)存是一片空白的,內(nèi)存斷電后 數(shù)據(jù)是無法保存的,所以BIOS程序需要事先被刷入只讀存儲(chǔ)器ROM中。物理地址0xFFFF0
就是指向這樣一段BIOS ROM
CPU是如何和ROM相連的?
那么問題又來了,CPU是如何和ROM相連的?CPU 不僅和ROM相連,還和RAM(俗稱內(nèi)存),IO接口等設(shè)備相連,他們是通過總線相連。還好當(dāng)時(shí)筆者將計(jì)算機(jī)組成原理好好復(fù)習(xí)了一遍,不然這部分真挺難理解的。
總線是貫穿整個(gè)系統(tǒng)的是一組電子管道,是連接各個(gè)部件的信息傳輸線,是各個(gè)部件共享的傳輸介質(zhì),稱作總線,它攜帶信息字節(jié)并負(fù)責(zé)在各個(gè)計(jì)算機(jī)部件間傳遞。
總線按系統(tǒng)總線傳輸信息內(nèi)容的不同,又可以分為3 種:數(shù)據(jù)總線、地址總線和控制總線。我們這里用到的就是地址總線,把 0xFFFF0 作為 CPU 的地址總線信號(hào)傳輸出去,去這個(gè)地址總線對(duì)應(yīng)的位置處找
由于計(jì)算機(jī)有多個(gè)設(shè)備,必然會(huì)存在多個(gè)設(shè)備同時(shí)競(jìng)爭(zhēng)總線控制權(quán)的問題,這時(shí)候就需要**總線仲裁,**讓某個(gè)設(shè)備優(yōu)先獲得總線控制權(quán),獲得了總線控制權(quán)的設(shè)備,才能開始傳送數(shù)據(jù)。未獲勝的設(shè)備只能等待獲勝的設(shè)備處理完成后才能執(zhí)行。
我們簡(jiǎn)單總結(jié)一下:當(dāng)總線仲裁器仲裁通過后,CPU可以依靠地址總線尋址,找到對(duì)應(yīng)設(shè)備ROM上地址0xFFFF0
處的內(nèi)容。
拓展可見:什么是計(jì)算機(jī)中的高速公路-總線?
加載MBR到內(nèi)存中
當(dāng)BIOS自檢完成,設(shè)置啟動(dòng)順序后,利用 BIOS 的輸入功能將啟動(dòng)磁盤的啟動(dòng)扇區(qū)MBR(也叫第一扇區(qū),主引導(dǎo)記錄)的內(nèi)容原封不動(dòng)地搬到內(nèi)存的0x7C00
地址處,并設(shè)置CPU寄存器CS=0x07C0,IP=0x0000
。到這一步,計(jì)算機(jī)的控制權(quán)將交到操作系統(tǒng)手中!
為什么是0x7C00這個(gè)地址?如何得出?別再問了,本文不再解釋了,具體看筆者的上一篇文章聊聊x86計(jì)算機(jī)啟動(dòng)發(fā)生的事
對(duì)于Linux0.12來說,第一個(gè)程序Bootsect.S 編譯成二進(jìn)制后,需要事先放到主引導(dǎo)記錄MBR中,MBR大小就是一個(gè)扇區(qū)的大小512字節(jié),如果這512字節(jié)的最后兩個(gè)字節(jié)是0x55AA
,表明這個(gè)設(shè)備可以用于啟動(dòng)。只有這樣我們BIOS才能識(shí)別它,才能把bootsect.S加載到內(nèi)存中。
如果不是0x55和0xAA,表明設(shè)備不能用于啟動(dòng),控制權(quán)于是被轉(zhuǎn)交給"啟動(dòng)順序"中的下一個(gè)設(shè)備。如果到最后還是沒找到符合條件的,直接報(bào)出一個(gè)無啟動(dòng)區(qū)的error。
下面我們看下操作系統(tǒng)編譯后,存放在儲(chǔ)存設(shè)備(硬盤)的模塊分布:
先簡(jiǎn)單介紹一下,不必深究,后續(xù)文章會(huì)娓娓道來:
- bootsect.s的主要作用就是加載操作系統(tǒng),把操作系統(tǒng)從硬盤中,加載到內(nèi)存里去
- setup.s的主要作用:首先獲得光標(biāo),內(nèi)存,顯卡,磁盤等硬件參數(shù)存放在內(nèi)存空間中,方便后續(xù)程序使用;臨時(shí)建立gdt、idt表,并且從實(shí)模式進(jìn)入到了保護(hù)模式
- 在linux0.12源碼,boot目錄下還有一個(gè)head.s,在上圖中被歸于system模塊,屬于操作系統(tǒng)主體文件,主要是進(jìn)行進(jìn)入保護(hù)模式之后的初始化工作
- system模塊:就是操作系統(tǒng)的主體,比如文件系統(tǒng),IO,進(jìn)程等模塊。 Linux0.12 內(nèi)核 system 模塊大約占隨后的 260 個(gè)扇區(qū)。
更多精彩文章在公眾號(hào)「小牛呼嚕?!?/p>
bootsect.S具體干了什么?
bootsect的主要作用就是加載操作系統(tǒng),把操作系統(tǒng)從硬盤中,加載到內(nèi)存里去,我們下面結(jié)合bootsect.s的源碼一起來看看bootsect.S具體干了什么?
呼嚕嚕這里整個(gè)過程先匯成了圖,大家配合圖去閱讀下文,對(duì)照起來,更容易理解
設(shè)置段基址 & 內(nèi)存分段機(jī)制
要想bootsect啟動(dòng),需要讓BIOS將bootsect.s 從硬盤的MBR中搬到 內(nèi)存位置0x7c00
處,大小512個(gè)字節(jié)。當(dāng)bootsect被BIOS加載到內(nèi)存后,計(jì)算機(jī)的控制權(quán)就到操作系統(tǒng)bootsect的手上了。
entry start ! 告知鏈接程序,程序入口是從start 標(biāo)號(hào)開始執(zhí)行的
start:mov ax,#BOOTSEG !BOOTSEG=0x7c0 , 將 ds 段寄存器置為 0x7C0mov ds,ax !再將 ax 段寄存器里的值復(fù)制到 ds 段寄存器里mov ax,#INITSEG !SETUPSEG=0x9000,將 es 段寄存器置為 0x9000mov es,ax !再將 ax 段寄存器里的值復(fù)制到 es 段寄存器里mov cx,#256sub si,sisub di,direpmovw jmpi go,INITSEG
我們可以看到CPU實(shí)際執(zhí)行第一句的代碼 mov ax,#BOOTSEG !BOOTSEG=0x7c0
,這是匯編寫的,其實(shí)這里的0x7c0
對(duì)應(yīng)的就是我們上文的地址0x7C00
0x7c0
是段地址,0x7C00
是其實(shí)際的物理地址,0x7c0
左移四位就是0x7c00
,這就是內(nèi)存尋址-分段機(jī)制
那么大家一定會(huì)有疑問內(nèi)存為什么分段?
計(jì)算機(jī)內(nèi)存究竟是什么?其實(shí)它就像數(shù)組一樣,咦有人不懂?dāng)?shù)組是什么,那么我們可以再頭腦風(fēng)暴一下,內(nèi)存其實(shí)就像紙帶一樣,我們來看下上古時(shí)期的計(jì)算機(jī):
穿孔紙帶,圖片來源于網(wǎng)絡(luò)
紙帶上有一個(gè)個(gè)孔,這樣大家可能還看不明白,我們?cè)賮砜匆粡垐D:
這些孔排列組合其實(shí)就是二進(jìn)制數(shù),紙帶其實(shí)就是儲(chǔ)存數(shù)據(jù)的介質(zhì),那么內(nèi)存就是足夠長(zhǎng)的“紙帶”
在現(xiàn)代計(jì)算機(jī)中,內(nèi)存它使用的是DRAM芯片,也叫動(dòng)態(tài)隨機(jī)存取存儲(chǔ)器,即只需給出地址,就能直接訪問指定地址的數(shù)據(jù),這一點(diǎn)特別像數(shù)組,所以許多材料都是用數(shù)組來畫內(nèi)存圖
那么CPU訪問內(nèi)存明明可以直接通過地址訪問內(nèi)存,為什么還要分段?其實(shí)這又是一個(gè)歷史因素導(dǎo)致的,讓我們回到"分段"首次出現(xiàn)的時(shí)候:"分段"是從Intel 8086芯片開始的,8086又是你…
由于8086那個(gè)時(shí)代CPU、內(nèi)存都很昂貴, CPU 和寄存器等寬度都是 16 位的,其可尋址2的16次方字節(jié),也就是64kb,然而8086有20根地址線,可尋址的最大內(nèi)存空間是1MB。CPU和寄存器的尋址能力遠(yuǎn)遠(yuǎn)不能滿足使用,于是機(jī)智的祖師爺們,采用了分段技術(shù)
分段,為解決這個(gè)問題,8086引入段寄存器,如CS、DS、ES、SS
。通過段基址+段內(nèi)偏移地址的方式生成20位的地址,擴(kuò)大尋址能力,從而實(shí)現(xiàn)對(duì)1MB內(nèi)存空間的尋址。由于這樣程序中指令了只用到16位地址,縮短了指令長(zhǎng)度,也變相地提高了程序執(zhí)行速度。
- CS:代碼段寄存器,存放代碼段的段基址
- DS是數(shù)據(jù)段寄存器,存放數(shù)據(jù)段的段基址
- ES是擴(kuò)展段寄存器,存放當(dāng)前程序使用附加數(shù)據(jù)段的段基址,該段是串操作指令中目的串所在的段
- SS是堆棧段寄存器,存放堆棧段的段基址
- 80836還新增2個(gè)寄存器,FS標(biāo)志段寄存器、GS全局段寄存器。
使用段地址還有一個(gè)好處是 程序可以重定位,那個(gè)時(shí)候的計(jì)算機(jī)可沒有虛擬地址之說,只有物理地址,訪問任何存儲(chǔ)單元都直接給出物理地址。這就帶來一個(gè)問題: 如果此時(shí)計(jì)算機(jī)多道程序并發(fā)運(yùn)行,程序中的地址都是實(shí)際物理地址,這些程序編譯出來的程序運(yùn)行地址是相同的,計(jì)算機(jī)只能運(yùn)行一個(gè)程序。
重定向: 將程序中指令的地址改成另一個(gè)地址,但該地址處的內(nèi)容還是原內(nèi)存地址處的內(nèi)容。這樣程序指令雖然還是物理地址,但程序能夠并發(fā)運(yùn)行了。
1982年處理器80286,首次提出保護(hù)模式概念,為了保持兼容性,所以同樣支持內(nèi)存分段管理,將8086這種稱為實(shí)模式,最大的區(qū)別是物理內(nèi)存地址不能直接被程序訪問,這塊非常重要,篇幅也較長(zhǎng),筆者先挖坑,后續(xù)系列文章再單獨(dú)出一篇。
咳咳,拓展的有點(diǎn)多了,趕緊讓我們回到bootsect源碼處
mov ds,ax
這句話代碼的意思就是:將 ax 段寄存器里的值復(fù)制到 ds 段寄存器里。ds在上文我們提到,8086特地為采用內(nèi)存分段機(jī)制,引入的段寄存器。ds具體表示 數(shù)據(jù)段寄存器,存放數(shù)據(jù)段的段基址
換句話說,就是將段基址設(shè)為0x07c0
,那么后續(xù)數(shù)據(jù)段程序中只需寫段內(nèi)偏移地址,就能訪問實(shí)際物理地址了。比如后續(xù)程序中出現(xiàn)mov ax,0x01
,0x01
其實(shí)是[ds:0x01]
,那么ax的實(shí)際物理地址= 0x07c0 <<4 + 0x01
。將ds寄存器段基址設(shè)置好后,其實(shí)就是方便之后程序訪問內(nèi)存,訪問的數(shù)據(jù)的內(nèi)存地址都先默認(rèn)加上 0x7c00
,然后再去內(nèi)存中尋址。
如果實(shí)際編程時(shí),代碼段的起始地址一般放到 CS寄存器,雖然CPU沒有強(qiáng)制規(guī)定代碼段、數(shù)據(jù)段等分離。
mov ax,#INITSEG
,mov es,ax
將 ax 段寄存器里的值0x9000
復(fù)制到 es 段寄存器里,和ds賦值同理,不再贅述。需要注意的是8086無法直接給段寄存器進(jìn)行賦值,需要使用通用寄存器來當(dāng)中介(一般使用ax)
bootsect的"再次搬家"到0x90000
接著bootsect自己把自己從內(nèi)存位置0x7c00
處,搬到0x90000
處,這次可沒BIOS幫忙了,得自食其力
start:mov ax,#BOOTSEG mov ds,ax mov ax,#INITSEG mov es,ax mov cx,#256 ! 設(shè)置移動(dòng)計(jì)數(shù)值=256 字(512 字節(jié));sub si,si ! si寄存器 清零sub di,di ! di寄存器 清零rep ! 重復(fù)執(zhí)行并遞減 cx 的值,直到 cx = 0 為止。movw ! 即 movs 指令。從內(nèi)存[si]處移動(dòng) cx 個(gè)字到[di]處。//一次移動(dòng)兩個(gè)字節(jié),256B*2=512B
mov cx,#256
將cx 寄存器的值賦值為 256,單位是字(Word), 1 word=2Byte
sub si,si
是si寄存器 清零操作,sub
是匯編語言中的一種運(yùn)算指令,它用來執(zhí)行減法運(yùn)算,并將結(jié)果存儲(chǔ)到被減數(shù)(前者)上去。比如sub a,b
就是a = a-b
。再結(jié)合前面的ds,es,那么此時(shí)si的段地址ds:si = 0x07C0:0x0000
,同理di的段地址es:di = 0x9000:0x0000
rep
就是重復(fù)執(zhí)行后一條指令,movw
就是復(fù)制的意思。rep movw
就是重復(fù)多次搬運(yùn)
我們可以知道這段的總體意思就是:循環(huán)256次,反復(fù)將段地址0x07C0:0x0000
的內(nèi)容一個(gè)字一個(gè)字的復(fù)制到段地址0x9000:0x0000
處,直到寄存器cx為0。這樣就實(shí)現(xiàn)了bootsect的"自我搬運(yùn)",把實(shí)際物理內(nèi)存地址0x7c00處
512個(gè)字節(jié)的內(nèi)容全部復(fù)制到實(shí)際物理內(nèi)存地址0x90000處
。
那為啥bootsect還要"多此一舉" 將自己從0x7c00
,搬到0x90000
處?
- 操作系統(tǒng)system后續(xù)最終是要從物理內(nèi)存起始位置處
地址0
開始存放,好處是讓system代碼中的地址對(duì)應(yīng)上實(shí)際的物理地址。- 一般要留
512KB
的內(nèi)存空間放操作系統(tǒng)system,會(huì)覆蓋0x7c00地址的內(nèi)容,所以需要把bootsect代碼搬到內(nèi)存更高處。
加載setup.s到內(nèi)存0x90200
當(dāng)上面bootsect完成自我搬運(yùn)后,緊接著執(zhí)行jmpi go,INITSEG
,jmpi有段間跳轉(zhuǎn)的作用。這里 INITSEG 指出跳轉(zhuǎn)到的段地址0x9000
,標(biāo)號(hào) go 是段內(nèi)偏移地址。
其實(shí)就是執(zhí)行完jmpi go,INITSEG
后,CPU已經(jīng)移動(dòng)到內(nèi)存0x90000+go
位置處的代碼中 執(zhí)行。為啥要加go?其實(shí)此時(shí)bootsect編譯后的二進(jìn)制內(nèi)容,已經(jīng)搬運(yùn)到內(nèi)存0x90000
處,但是我們不能再從頭執(zhí)行start: mov ax,#BOOTSEG
操作,而是從go: mov ax,cs
處代碼繼續(xù)執(zhí)行下去。
jmpi go,INITSEG ! 段間跳轉(zhuǎn)。這里 INITSEG 指出跳轉(zhuǎn)到的段地址,標(biāo)號(hào) go 是段內(nèi)偏移地址。go: mov ax,cs mov dx,#0xfef4 ! arbitrary value >>512 - disk parm sizemov ds,axmov es,axpush ax ! 臨時(shí)保存段值(0x9000)mov ss,ax ! put stack at 0x9ff00 - 12.mov sp,dxpush #0 ! 置段寄存器 fs = 0。pop fs ! fs:bx 指向存有軟驅(qū)參數(shù)表地址處(指針的指針)mov bx,#0x78 ! fs:bx is parameter table addressseg fslgs si,(bx) ! gs:si is sourcemov di,dx ! es:di is destinationmov cx,#6 ! copy 12 bytescldrep ! 復(fù)制 12 字節(jié)的軟驅(qū)參數(shù)表到 0x9000:0xfef4 處。seg gsmovwmov di,dxmovb 4(di),*18 ! patch sector countseg fs ! 讓中斷向量 0x1E 的值指向新表。mov (bx),diseg fsmov 2(bx),espop axmov fs,axmov gs,axxor ah,ah ! reset FDC 讓中斷向量 0x1E 的值指向新表。xor dl,dlint 0x13
上述主要是將 寄存器DS、ES 和SS 重新設(shè)置為CPU移動(dòng)后,代碼所在的段處0×9000
,設(shè)置SP棧寄存器0xfef4
棧指針要遠(yuǎn)大于512字節(jié)偏移(即 0x90200 )處都可以,一般setup程序大概占用4個(gè)扇區(qū),這樣棧頂段地址ss:sp
和現(xiàn)有的代碼足夠遠(yuǎn) ,防止后續(xù)棧操作覆蓋掉已有的代碼。
還有BIOS 設(shè)置的中斷 0x1e 的中斷向量值等操作。這邊和主干操作不太相干,簡(jiǎn)略過一下,主要就是把這些寄存器重新設(shè)置好值,方便后續(xù)使用。
更多精彩文章在公眾號(hào)「小牛呼嚕嚕」
接下來緊接著將setup.s 加載到內(nèi)存0x90200
處
load_setup:xor dx, dx ! 驅(qū)動(dòng)器drive 0, 磁頭head 0mov cx,#0x0002 ! 扇區(qū)sector 2, 磁道號(hào)track 0,從第二個(gè)扇區(qū)開始讀mov bx,#0x0200 ! 偏移address = 512, in INITSEG ,表示讀到0x90200mov ax,#0x0200+SETUPLEN ! service 2, nr of sectors ,SETUPLEN是 4個(gè)扇區(qū)int 0x13 ! read itjnc ok_load_setup ! ok,就跳到ok_load_setuppush ax ! dump error codecall print_nl ! 屏幕光標(biāo)回車mov bp, spcall print_hex ! 顯示十六進(jìn)制值pop ax xor dl, dl ! reset FDCxor ah, ahint 0x13j load_setup ! j 即 jmp 指令,失敗就再跳轉(zhuǎn)到load_setup,重復(fù)執(zhí)行
那怎么簡(jiǎn)單高效將磁盤里的內(nèi)容加載到內(nèi)存中呢?linus這里用的是bios的中斷程序,因?yàn)榇藭r(shí)bios還在內(nèi)存中,可以為我們所用,0x13
號(hào)中斷 在BIOS中是可以訪問軟盤、IDE、ROM、遠(yuǎn)程磁盤服務(wù)的作用。
這里0x13 和C語言中的函數(shù)調(diào)用是很像的,不過需要注意的是它的參數(shù)只能通過寄存器去傳參,而C語言函數(shù)調(diào)用不僅可以寄存器傳參,還可以棧傳參。所以0x13的參數(shù)就是其前面的dx,cx,bx,ax寄存器的值,另外磁盤只認(rèn)磁頭磁道扇區(qū),如果給個(gè)地址,磁盤是不識(shí)別的,磁盤一副不太聰明的樣子。
另外xor
對(duì)兩個(gè)操作數(shù)進(jìn)行邏輯(按位)異或操作,并將結(jié)果存放在目標(biāo)操作數(shù),xor dx,dx
也是一個(gè)置零操作,指定驅(qū)動(dòng)和磁頭
那么我們連起來,這段主要是讓bios 0x13號(hào)中斷處理程序 從磁盤的第2扇區(qū)開始讀,接連讀4個(gè)扇區(qū)的內(nèi)容到內(nèi)存0x90200
處中。成功就跳轉(zhuǎn)到ok_load_setup
,沒成功就回到load_setup
,重復(fù)執(zhí)行上述操作。
加載system到內(nèi)存0x10000
當(dāng)bootsect成功將setup.s搬到內(nèi)存0x90200
處后,CPU從ok_load_setup
處繼續(xù)執(zhí)行指令。接下來就是需要將整個(gè)操作系統(tǒng)system(head.s+其他文件,大約260個(gè)扇區(qū))的內(nèi)容加載到內(nèi)存0x10000
處,下面我們就具體看下代碼是如何實(shí)現(xiàn)的:
ok_load_setup:! Get disk drive parameters, specifically nr of sectors/track
!提示這面段代碼功能是:利用BIOSINT 0x13 中斷,來來取磁盤的一些參數(shù),比如是取每磁道扇區(qū)數(shù),并保存在
位置 sectors 處xor dl,dlmov ah,#0x08 ! AH=8 is get drive parametersint 0x13xor ch,chseg cs !表示下一條語句的操作數(shù)在 cs 段寄存器所指的段中。它只影響其下一條語句mov sectors,cxmov ax,#INITSEGmov es,ax !取磁盤參數(shù)中斷改了es寄存器的值,這里重置es的值! Print some inane message 提示下面這段功能是:打印一些消息mov ah,#0x03 ! read cursor pos 讀取當(dāng)前光標(biāo)的地址xor bh,bhint 0x10 ! bios 0x10中斷,其作用:在屏幕上顯示字符和字符串mov cx,#9mov bx,#0x0007 ! page 0, attribute 7 (normal)mov bp,#msg1 ! msg1的內(nèi)容是: .byte 13,10(換行+回車) .ascii "Loading"mov ax,#0x1301 ! write string, move cursorint 0x10! ok, we've written the message, now
! we want to load the system (at 0x10000) 加載system到內(nèi)存0x10000mov ax,#SYSSEGmov es,ax ! segment of 0x010000call read_it ! 讀磁盤上 system 模塊call kill_motor ! 關(guān)閉驅(qū)動(dòng)器馬達(dá)call print_nl ! 光標(biāo)回車換行... 省略非主干代碼...! after that (everyting loaded), we jump to
! the setup-routine loaded directly after
! the bootblock:jmpi 0,SETUPSEG !bootsect程序到這里就結(jié)束了,跳轉(zhuǎn)到0x9020,同時(shí)setup獲得控制權(quán)
這里int 0x10
號(hào)中斷,其作用是 在屏幕上顯示字符和字符串,由于操作系統(tǒng)比較大,加載需要時(shí)間,這時(shí)在屏幕上顯示提示信息"Loading"
這里將操作系統(tǒng)加載到內(nèi)存中,是通過子程序read_it
來實(shí)現(xiàn)的,read_it就不具體展開了,比較復(fù)雜。我們需要知道由于操作系統(tǒng)比較大,一個(gè)磁道是遠(yuǎn)遠(yuǎn)放不下的,另外磁盤是不認(rèn)地址的,在搬運(yùn)過程中,需要進(jìn)行磁道、扇區(qū)和磁頭的計(jì)算,特別是一個(gè)段的大小是64k,如果放不下,需要更換段地址。如果不更換段地址,會(huì)從該段地址0字節(jié)開始重新寫,這樣會(huì)覆蓋之前的內(nèi)容。
那為什么一個(gè)段的大小是64KB呢?
我們知道在8086CPU中,其內(nèi)存地址是表示為段基址+段內(nèi)偏移地址,其中偏移地址使用一個(gè)16位的二進(jìn)制數(shù)表示,表示范圍0000~FFFF
,所以總共有2^16(2的16次方)=64K個(gè)不同的地址,一個(gè)內(nèi)存最小單元是字節(jié)Byte,所以一個(gè)段大小為64KB
jmpi 0,SETUPSEG
,bootsect程序到這里就結(jié)束了,跳轉(zhuǎn)到內(nèi)存地址0x90200
,同時(shí)setup獲得控制權(quán)
為了幫助大家理解,呼嚕嚕這里又把本篇文章全部串起來,大家可以根據(jù)下面這張圖重新回顧一下bootsect整個(gè)工作流程:
額外補(bǔ)充一下:
boot_flag: .word 0xAA55
最后2個(gè)字節(jié)是0xAA55
,由于bootsect是采用AT&T匯編,小端顯示的,實(shí)際上就是0x55AA
與前文MBR那邊前后呼應(yīng)
這也說明了操作系統(tǒng)在開始加載到內(nèi)存的程序中,得與內(nèi)存地址一一對(duì)應(yīng), 不能多一個(gè)字節(jié),也不能少一個(gè)字節(jié)!!!
尾語
本文主要講解了bootsect.S的主要工作流程,Linux0.12雖然和如今的Linux6.x內(nèi)核相比顯得過于簡(jiǎn)陋,但麻雀雖小五臟俱全,它是我們打開操作系統(tǒng)大門的鑰匙,后面讓我們看看setup.s獲得計(jì)算機(jī)的控制權(quán)后,會(huì)發(fā)生什么?
最近實(shí)在太忙了,后面隨緣更新,留言可催更(bushi)~~
參考資料:
《Linux內(nèi)核完全注釋5.0》
《操作系統(tǒng)真象還原》
https://elixir.bootlin.com/linux/0.12/source/boot/bootsect.S
https://files.embeddedts.com//old/saved-downloads-manuals/EBIOS-UM.PDF
本篇文章到這里就結(jié)束啦,如果我的文章對(duì)你有所幫助的話,還請(qǐng)點(diǎn)個(gè)免費(fèi)的贊,你的支持會(huì)激勵(lì)我輸出更高質(zhì)量的文章,感謝!