第十回 | 進入 main 函數(shù)前的最后一躍!
新讀者看這里,老讀者直接跳過。
本系列會以一個讀小說的心態(tài),從開機啟動后的代碼執(zhí)行順序,帶著大家閱讀和賞析 Linux 0.11 全部核心代碼,了解操作系統(tǒng)的技術(shù)細節(jié)和設(shè)計思想。

你會跟著我一起,看著一個操作系統(tǒng)從啥都沒有開始,一步一步最終實現(xiàn)它復(fù)雜又精巧的設(shè)計,讀完這個系列后希望你能發(fā)出感嘆,原來操作系統(tǒng)源碼就是這破玩意。
以下是已發(fā)布文章的列表,詳細了解本系列可以先從開篇詞看起。
第八回 | 煩死了又要重新設(shè)置一遍 idt 和 gdt
第九回 | Intel 內(nèi)存管理兩板斧:分段與分頁
本系列的 GitHub 地址如下(文末閱讀原文可直接跳轉(zhuǎn))
https://github.com/sunym1993/flash-linux0.11-talk
------- 正文開始?-------
書接上回,上回書咱們說到,我們終于把這些雜七雜八的,idt、gdt、頁表都設(shè)置好了,并且也開啟了保護模式,相當于所有苦力活都做好鋪墊了,之后我們就要準備進入 main.c!那里是個新世界!
注意不是進入,而是準備進入哦,就差一哆嗦了。
由于上一講的知識量非常大,所以這一講將會非常簡單,作為進入 main 函數(shù)前的銜接,大家放寬心。
?
這仍然要回到上一講我們跳轉(zhuǎn)到設(shè)置分頁代碼的那個地方(head.s 里),這里有個騷操作幫我們跳轉(zhuǎn)到 main.c。
after_page_tables:
push 0
push 0
push 0
push L6
push _main
jmp setup_paging
...
setup_paging:
...
ret
直接解釋起來非常簡單。
?
push 指令就是壓棧,五個 push 指令過去后,棧會變成這個樣子。
?

?
然后注意,setup_paging 最后一個指令是 ret,也就是我們上一回講的設(shè)置分頁的代碼的最后一個指令,形象地說它叫返回指令,但 CPU 可沒有那么聰明,它并不知道該返回到哪里執(zhí)行,只是很機械地把棧頂?shù)脑刂诞斪龇祷氐刂?/strong>,跳轉(zhuǎn)去那里執(zhí)行。
?
再具體說是,把 esp 寄存器(棧頂?shù)刂罚┧赶虻膬?nèi)存處的值,賦值給 eip 寄存器,而 cs:eip 就是 CPU 要執(zhí)行的下一條指令的地址。而此時棧頂剛好是 main.c 里寫的 main 函數(shù)的內(nèi)存地址,是我們剛剛特意壓入棧的,所以 CPU 就理所應(yīng)當跳過來了。
當然 Intel CPU 是設(shè)計了 call 和 ret 這一配對兒的指令,意為調(diào)用函數(shù)和返回,具體可以看后面本回擴展資料里的內(nèi)容。
?
至于其他壓入棧的 L6 是用作當 main 函數(shù)返回時的跳轉(zhuǎn)地址,但由于在操作系統(tǒng)層面的設(shè)計上,main 是絕對不會返回的,所以也就沒用了。而其他的三個壓棧的 0,本意是作為 main 函數(shù)的參數(shù),但實際上似乎也沒有用到,所以也不必關(guān)心。
?
總之,經(jīng)過這一個小小的騷操作,程序終于跳轉(zhuǎn)到 main.c 這個由 c 語言寫就的主函數(shù) main 里了!我們先一睹為快一下。
void?main(void)?{
????ROOT_DEV?=?ORIG_ROOT_DEV;
????drive_info?=?DRIVE_INFO;
????memory_end?=?(1<<20)?+?(EXT_MEM_K<<10);
????memory_end?&=?0xfffff000;
????if?(memory_end?>?16*1024*1024)
????????memory_end?=?16*1024*1024;
????if?(memory_end?>?12*1024*1024)?
????????buffer_memory_end?=?4*1024*1024;
????else?if?(memory_end?>?6*1024*1024)
????????buffer_memory_end?=?2*1024*1024;
????else
????????buffer_memory_end?=?1*1024*1024;
????main_memory_start?=?buffer_memory_end;
????mem_init(main_memory_start,memory_end);
????trap_init();
????blk_dev_init();
????chr_dev_init();
????tty_init();
????time_init();
????sched_init();
????buffer_init(buffer_memory_end);
????hd_init();
????floppy_init();
????sti();
????move_to_user_mode();
????if?(!fork())?{
????????init();
????}
????for(;;)?pause();
}
沒錯,這就是這個 main 函數(shù)的全部了。
?
而整個操作系統(tǒng)也會最終停留在最后一行死循環(huán)中,永不返回,直到關(guān)機。
?
好了,至此,整個第一部分就圓滿結(jié)束了,為了跳進 main 函數(shù)的準備工作,我稱之為進入內(nèi)核前的苦力活,就完成了!我們看看我們做了什么。
?

?
我把這些稱為進入內(nèi)核前的苦力活,經(jīng)過這樣的流程,內(nèi)存被搞成了這個樣子。
?

?
之后,main 方法就開始執(zhí)行了,靠著我們辛辛苦苦建立起來的內(nèi)存布局,向嶄新的未來前進!
?
欲知后事如何,且聽下回分解。
------- 本回擴展資料 -------


壓棧和出棧的具體過程,上面文字寫的清清楚楚,下面 Intel 手冊還非常友好地放了張圖。

可以看到,我們本文就是左邊的那一套,把 main 函數(shù)地址值當做 Calling EIP 壓入棧,仿佛是執(zhí)行了 call 指令調(diào)用了一個函數(shù)一樣,但實際上這是我們通過騷操作代碼偽造的假象,騙了 CPU。
然后 ret 的時候就把棧頂?shù)哪莻€ Calling EIP 也就是 main 函數(shù)地址彈出棧,存入 EIP 寄存器,這樣 CPU 就相當于“返回”到了 main 函數(shù)開始執(zhí)行。
------- 關(guān)于本系列?-------
本系列的開篇詞看這
本系列的擴展資料看這(也可點擊閱讀原文),這里有很多有趣的資料、答疑、互動參與項目,持續(xù)更新中,希望有你的參與。
https://github.com/sunym1993/flash-linux0.11-talk
本系列全局視角

最后,祝大家都能追更到系列結(jié)束,只要你敢持續(xù)追更,并且把每一回的內(nèi)容搞懂,我就敢讓你在系列結(jié)束后說一句,我對 Linux 0.11 很熟悉。
另外,本系列完全免費,希望大家能多多傳播給同樣喜歡的人,同時給我的 GitHub 項目點個 star,就在閱讀原文處,這些就足夠讓我堅持寫下去了!我們下回見。
