本文來自微信公眾號:低并發(fā)編程 (ID:dibingfa),原文標(biāo)題:《第四回 | 把自己在硬盤里的其他部分也放到內(nèi)存來》,作者:閃客
本系列會以一個讀小說的心態(tài),從開機(jī)啟動后的代碼執(zhí)行順序,帶著大家閱讀和賞析 Linux 0.11 全部核心代碼,了解操作系統(tǒng)的技術(shù)細(xì)節(jié)和設(shè)計(jì)思想。
你會跟著我一起,看著一個操作系統(tǒng)從啥都沒有開始,一步一步最終實(shí)現(xiàn)它復(fù)雜又精巧的設(shè)計(jì),讀完這個系列后希望你能發(fā)出感嘆,原來操作系統(tǒng)源碼就是這破玩意。以下是已發(fā)布文章的列表,詳細(xì)了解本系列可以先從開篇詞看起。
開篇詞
本系列的 GitHub 地址:點(diǎn)此訪問
------- 正文開始 -------
書接上回,上回書咱們說到,操作系統(tǒng)的一些最最最最基礎(chǔ)的準(zhǔn)備工作,已經(jīng)準(zhǔn)備好了。
如這張圖所示,此時(shí)操作系統(tǒng)短短幾行代碼,將數(shù)據(jù)段寄存器 ds 和代碼段寄存器 cs 設(shè)置為了 0x9000,方便代碼的跳轉(zhuǎn)與數(shù)據(jù)的訪問。并且,將棧頂?shù)刂?ss:sp 設(shè)置在了離代碼的位置 0x90000 足夠遙遠(yuǎn)的 0x9FF00,保證棧向下發(fā)展不會輕易撞見代碼的位置。
簡單說,就是設(shè)置了如何訪問數(shù)據(jù)的數(shù)據(jù)段,如何訪問代碼的代碼段,以及如何訪問棧的棧頂指針,也即初步做了一次內(nèi)存規(guī)劃,從 CPU 的角度看,訪問內(nèi)存,就這么三塊地方而已。
做好這些基礎(chǔ)工作后,接下來就又該新的一翻折騰了,我們接著往下看。
load_setup: mov dx,#0x0000 ; drive 0, head 0 mov cx,#0x0002 ; sector 2, track 0 mov bx,#0x0200 ; address = 512, in 0x9000 mov ax,#0x0200+4 ; 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 jmp load_setup ok_load_setup: ...
這里有兩個 int 指令我們還沒見過。
注意這個 int 是匯編指令,可不是高級語言的整型變量喲。int 0x13 表示發(fā)起 0x13 號中斷,這條指令上面給 dx、cx、bx、ax 賦值都是作為這個中斷程序的參數(shù)。
中斷是啥如果你不理解,先不要管,如果你就是放不下,那可以看一眼我之前的文章:認(rèn)認(rèn)真真的聊聊中斷,里面講得非常細(xì)致。
總之這個中斷發(fā)起后,CPU 會通過這個中斷號,去尋找對應(yīng)的中斷處理程序的入口地址,并跳轉(zhuǎn)過去執(zhí)行,邏輯上就相當(dāng)于執(zhí)行了一個函數(shù)。而 0x13 號中斷的處理程序是 BIOS 提前給我們寫好的,是讀取磁盤的相關(guān)功能的函數(shù)。
之后真正進(jìn)入操作系統(tǒng)內(nèi)核后,中斷處理程序是需要我們自己去重新寫的,這個在后面的章節(jié)中,你會不斷看到各個模塊注冊自己相關(guān)的中斷處理程序,所以不要急。此時(shí)為了方便就先用 BIOS 提前給我們寫好的程序了。
可見即便是操作系統(tǒng)的源碼,有時(shí)也需要去調(diào)用現(xiàn)成的函數(shù)方便自己,并不是造輪子的人就非得完全從頭造。
本段代碼的注釋已經(jīng)寫的很明確了,直接說最終的作用吧,就是將硬盤的第 2 個扇區(qū)開始,把數(shù)據(jù)加載到內(nèi)存 0x90200 處,共加載 4 個扇區(qū),圖示其實(shí)就是這樣。
為了圖片清晰表達(dá)意思,可能比例就不那么嚴(yán)謹(jǐn)了,大家不必糾結(jié)。
可以看到,如果復(fù)制成功,就跳轉(zhuǎn)到 ok_load_setup 這個標(biāo)簽,如果失敗,則會不斷重復(fù)執(zhí)行這段代碼,也就是重試。那我們就別管重試邏輯了,直接看成功后跳轉(zhuǎn)的 ok_load_setup 這個標(biāo)簽后的代碼。
ok_load_setup: ... mov ax,#0x1000 mov es,ax ; segment of 0x10000 call read_it ... jmpi 0,0x9020
這段代碼省略了很多非主邏輯的代碼,比如在屏幕上輸出 Loading system ... 這個字符串以防止用戶等煩了。
剩下的主要代碼就都寫在這里了,就這么幾行,其作用是把從硬盤第 6 個扇區(qū)開始往后的 240 個扇區(qū),加載到內(nèi)存 0x10000 處,和之前的從硬盤搗騰到內(nèi)存是一個道理。
至此,整個操作系統(tǒng)的全部代碼,就已經(jīng)全部從硬盤中,被搬遷到內(nèi)存來了。
然后又通過一個熟悉的段間跳轉(zhuǎn)指令 jmpi 0,0x9020,跳轉(zhuǎn)到 0x90200 處,就是硬盤第二個扇區(qū)開始處的內(nèi)容。
那這里的內(nèi)容是什么呢?先不急,我們借這個機(jī)會把整個操作系統(tǒng)的編譯過程說下。整個編譯過程,就是通過 Makefile 和 build.c 配合完成的,最終會:
1. 把 bootsect.s 編譯成 bootsect 放在硬盤的 1 扇區(qū)。
2. 把 setup.s 編譯成 setup 放在硬盤的 2~5 扇區(qū)。
3. 把剩下的全部代碼(head.s 作為開頭)編譯成 system 放在硬盤的隨后 240 個扇區(qū)。
所以整個路徑就是這樣的。
所以,我們即將跳轉(zhuǎn)到的內(nèi)存中的 0x90200 處的代碼,就是從硬盤第二個扇區(qū)開始處加載到內(nèi)存的。第二個扇區(qū)的最開始處,那也就是 setup.s 文件的第一行代碼咯。
那這個代碼是什么呢?我們后面再說,不過先打開 setup.s 這個文件看看吧。
start: mov ax,#0x9000 ; this is done in bootsect already, but mov ds,ax mov ah,#0x03 ; read cursor pos xor bh,bh int 0x10 ; save it in known place, con_init fetches mov [0],dx ; it from 0x90000.
好了,到目前為止,你是不是覺得,我去,這前面編譯放在硬盤的位置,和后面代碼寫死的跳轉(zhuǎn)地址,竟然如此地強(qiáng)耦合?那萬一整錯了咋辦。
是啊,就是這樣,你以為呢?在操作系統(tǒng)剛剛開始建立的時(shí)候,那是完全自己安排前前后后的關(guān)系,一個字節(jié)都不能偏,就是這么強(qiáng)耦合,需要小心翼翼,需要大腦時(shí)刻保持清醒,規(guī)劃好自己寫的代碼被編譯并存儲在硬盤的哪個位置,而隨后又會被加載到內(nèi)存的哪個位置,不能錯亂。
但這也是很有好處的,那就是在這個階段,你完完全全知道每一步跳轉(zhuǎn),每一步數(shù)據(jù)訪問都是怎么設(shè)計(jì)和規(guī)劃的,不存在黑盒。
不像我們在寫高級語言的時(shí)候,完全不知道是怎么底層幫我們做了多少工作。雖然這解脫了程序員關(guān)心底層細(xì)節(jié)的煩惱,但在遇到問題或者想知道原理的時(shí)候,就顯得很討厭了。所以珍惜這個階段吧!
而且,你在上層之所以能那么隨心所欲,很多底層細(xì)節(jié)完全不用考慮,很省心,正是因?yàn)橄窠裉爝@樣以及之后每一章的各種底層代碼小心翼翼的做了很多鋪墊。
好了,本文的內(nèi)容就結(jié)束了。這也標(biāo)志著我們走完了第一個操作系統(tǒng)源碼文件 bootsect.s,開始向下一個文件 setup.s 進(jìn)發(fā)了!
后面的世界越來越精彩,欲知后事如何,且聽下回分解。
廣告聲明:文內(nèi)含有的對外跳轉(zhuǎn)鏈接(包括不限于超鏈接、二維碼、口令等形式),用于傳遞更多信息,節(jié)省甄選時(shí)間,結(jié)果僅供參考,IT之家所有文章均包含本聲明。