<kbd id="afajh"><form id="afajh"></form></kbd>
<strong id="afajh"><dl id="afajh"></dl></strong>
    <del id="afajh"><form id="afajh"></form></del>
        1. <th id="afajh"><progress id="afajh"></progress></th>
          <b id="afajh"><abbr id="afajh"></abbr></b>
          <th id="afajh"><progress id="afajh"></progress></th>

          第十回 | 進入 main 函數(shù)前的最后一躍!

          共 1574字,需瀏覽 4分鐘

           ·

          2021-12-17 09:52

          新讀者看這里,老讀者直接跳過。


          本系列會以一個讀小說的心態(tài),從開機啟動后的代碼執(zhí)行順序,帶著大家閱讀和賞析 Linux 0.11 全部核心代碼,了解操作系統(tǒng)的技術(shù)細節(jié)和設(shè)計思想。



          你會跟著我一起,看著一個操作系統(tǒng)從啥都沒有開始,一步一步最終實現(xiàn)它復(fù)雜又精巧的設(shè)計,讀完這個系列后希望你能發(fā)出感嘆,原來操作系統(tǒng)源碼就是這破玩意。


          以下是已發(fā)布文章的列表,詳細了解本系列可以先從開篇詞看起。


          開篇詞

          第一回 | 最開始的兩行代碼

          第二回 | 自己給自己挪個地兒

          第三回 | 做好最最基礎(chǔ)的準備工作?

          第四回 | 把自己在硬盤里的其他部分也放到內(nèi)存來

          第五回 | 進入保護模式前的最后一次折騰內(nèi)存

          第六回 | 先解決段寄存器的歷史包袱問題

          第七回 | 六行代碼就進入了保護模式

          第八回 | 煩死了又要重新設(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)存布局,向嶄新的未來前進!

          ?

          欲知后事如何,且聽下回分解。



          ------- 本回擴展資料 -------



          關(guān)于 ret 指令,其實 Intel CPU 是配合 call 設(shè)計的,有關(guān) call 和 ret 指令,即調(diào)用和返回指令,可以參考 Intel 手冊:
          Intel 1?Chapter 6.4?CALLING PROCEDURES USING CALL AND RET

          可以看到還分為不改變段基址的 near call 和 near ret


          以及改變段基址的 far call 和 far ret


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



          可以看到,我們本文就是左邊的那一套,把 main 函數(shù)地址值當做 Calling EIP 壓入棧,仿佛是執(zhí)行了 call 指令調(diào)用了一個函數(shù)一樣,但實際上這是我們通過騷操作代碼偽造的假象,騙了 CPU。


          然后 ret 的時候就把棧頂?shù)哪莻€ Calling EIP 也就是 main 函數(shù)地址彈出棧,存入 EIP 寄存器,這樣 CPU 就相當于“返回”到了 main 函數(shù)開始執(zhí)行。



          ------- 關(guān)于本系列?-------



          本系列的開篇詞看這

          閃客新系列!你管這破玩意叫操作系統(tǒng)源碼


          本系列的擴展資料看這(也可點擊閱讀原文),這里有很多有趣的資料、答疑、互動參與項目,持續(xù)更新中,希望有你的參與。

          https://github.com/sunym1993/flash-linux0.11-talk


          本系列全局視角



          最后,祝大家都能追更到系列結(jié)束,只要你敢持續(xù)追更,并且把每一回的內(nèi)容搞懂,我就敢讓你在系列結(jié)束后說一句,我對 Linux 0.11 很熟悉。


          另外,本系列完全免費,希望大家能多多傳播給同樣喜歡的人,同時給我的 GitHub 項目點個 star,就在閱讀原文,這些就足夠讓我堅持寫下去了!我們下回見。

          瀏覽 110
          點贊
          評論
          收藏
          分享

          手機掃一掃分享

          分享
          舉報
          評論
          圖片
          表情
          推薦
          點贊
          評論
          收藏
          分享

          手機掃一掃分享

          分享
          舉報
          <kbd id="afajh"><form id="afajh"></form></kbd>
          <strong id="afajh"><dl id="afajh"></dl></strong>
            <del id="afajh"><form id="afajh"></form></del>
                1. <th id="afajh"><progress id="afajh"></progress></th>
                  <b id="afajh"><abbr id="afajh"></abbr></b>
                  <th id="afajh"><progress id="afajh"></progress></th>
                  亚洲无视频 | 一级a一级a爰片免费免免水l软件 | 男人的天堂一区 | 自拍偷拍视频网站 | 亚洲操逼黄色网 |