<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>

          一個(gè)新進(jìn)程的誕生(二)從內(nèi)核態(tài)到用戶態(tài)

          共 5936字,需瀏覽 12分鐘

           ·

          2022-02-15 13:14




          本系列作為?你管這破玩意叫操作系統(tǒng)源碼?的第三大部分,講述了操作系統(tǒng)第一個(gè)進(jìn)程從無(wú)到有的誕生過(guò)程,這一部分你將看到內(nèi)核態(tài)與用戶態(tài)的轉(zhuǎn)換、進(jìn)程調(diào)度的上帝視角、系統(tǒng)調(diào)用的全鏈路、fork 函數(shù)的深度剖析。


          不要聽到這些陌生的名詞就害怕,跟著我一點(diǎn)一點(diǎn)了解他們的全貌,你會(huì)發(fā)現(xiàn),這些概念竟然如此活靈活現(xiàn),如此順其自然且合理地出現(xiàn)在操作系統(tǒng)的啟動(dòng)過(guò)程中。


          本篇章作為一個(gè)全新的篇章,需要前置篇章的知識(shí)體系支撐。


          第一部分 進(jìn)入內(nèi)核前的苦力活

          第二部分 大戰(zhàn)前期的初始化工作


          當(dāng)然,沒(méi)讀過(guò)的也問(wèn)題不大,我都會(huì)在文章里做說(shuō)明,如果你覺(jué)得有困惑,就去我告訴你的相應(yīng)章節(jié)回顧就好了,放寬心。



          ------- 第三部分目錄?-------



          (一)先整體看一下



          ------- 正文開始?-------



          書接上回,上回書咱們從整體上鳥瞰了一下第三部分要講的內(nèi)容,代碼上就是還差四句話就走到了 main 函數(shù)的盡頭。

          void?main(void)?{
          ????...????
          ????move_to_user_mode();
          ????if?(!fork())?{
          ????????init();
          ????}
          ????for(;;)?pause();
          }

          今天我們就重點(diǎn)講這第一句代碼,move_to_user_mode。



          讓進(jìn)程無(wú)法逃出用戶態(tài)



          這行代碼的意思直接說(shuō)非常簡(jiǎn)單,就是從內(nèi)核態(tài)轉(zhuǎn)變?yōu)榱擞脩魬B(tài),但要解釋清楚這個(gè)意思,還需要聽我慢慢道來(lái)


          我相信你肯定聽說(shuō)過(guò)操作系統(tǒng)的內(nèi)核態(tài)與用戶態(tài),用戶進(jìn)程都在用戶態(tài)這個(gè)特權(quán)級(jí)下運(yùn)行,而有時(shí)程序想要做一些內(nèi)核態(tài)才允許做的事情,比如讀取硬盤的數(shù)據(jù),就需要通過(guò)系統(tǒng)調(diào)用,來(lái)請(qǐng)求操作系統(tǒng)在內(nèi)核態(tài)特權(quán)級(jí)下執(zhí)行一些指令。


          我們現(xiàn)在的代碼,還是在內(nèi)核態(tài)下運(yùn)行,之后操作系統(tǒng)達(dá)到怠速狀態(tài)時(shí),是以用戶態(tài)的 shell 進(jìn)程運(yùn)行,隨時(shí)等待著來(lái)自用戶輸入的命令。


          所以,就在這一步,也就是 move_to_user_mode 這行代碼,作用就是將當(dāng)前代碼的特權(quán)級(jí),從內(nèi)核態(tài)變?yōu)橛脩魬B(tài)。


          一旦轉(zhuǎn)變?yōu)榱擞脩魬B(tài),那么之后的代碼將一直處于用戶態(tài)的模式,除非發(fā)生了中斷,比如用戶發(fā)出了系統(tǒng)調(diào)用的中斷指令,那么此時(shí)將會(huì)從用戶態(tài)陷入內(nèi)核態(tài),不過(guò)當(dāng)中斷處理程序執(zhí)行完之后,又會(huì)通過(guò)中斷返回指令從內(nèi)核態(tài)回到用戶態(tài)。



          整個(gè)過(guò)程被操作系統(tǒng)的機(jī)制拿捏的死死的,始終讓用戶進(jìn)程處于用戶態(tài)運(yùn)行,必要的時(shí)候陷入一下內(nèi)核態(tài),但很快就會(huì)被返回而再次回到用戶態(tài),是不是非常無(wú)奈?這樣操作系統(tǒng)就掌控了控制權(quán),而用戶進(jìn)程再怎么折騰也無(wú)法逃出這個(gè)模式。

          ?


          內(nèi)核態(tài)與用戶態(tài)的本質(zhì)-特權(quán)級(jí)

          ?


          首先從一個(gè)最大的視角來(lái)看,這一切都源于 CPU 的保護(hù)機(jī)制。CPU 為了配合操作系統(tǒng)完成保護(hù)機(jī)制這一特性,分別設(shè)計(jì)了分段保護(hù)機(jī)制分頁(yè)保護(hù)機(jī)制。

          ?

          當(dāng)我們?cè)?第七回 | 六行代碼就進(jìn)入了保護(hù)模式?將 cr0 寄存器的 PE 位開啟時(shí),就開啟了保護(hù)模式,也即開啟了分段保護(hù)機(jī)制。

          ?

          ?

          當(dāng)我們?cè)?第九回 | Intel 內(nèi)存管理兩板斧:分段與分頁(yè) 將 cr0 寄存器的 PG 位開啟時(shí),就開啟了分頁(yè)模式,也即開啟了分頁(yè)保護(hù)機(jī)制。

          ?

          ?

          有關(guān)特權(quán)級(jí)的保護(hù),實(shí)際上屬于分段保護(hù)機(jī)制的一種。具體怎么保護(hù)的呢?由于這里的細(xì)節(jié)比較繁瑣,所以我舉個(gè)例子簡(jiǎn)單理解下即可,實(shí)際上的特權(quán)級(jí)檢查規(guī)則要比我說(shuō)的多好多內(nèi)容。

          ?

          我們目前正在執(zhí)行的代碼地址,是通過(guò) CPU 中的兩個(gè)寄存器 cs : eip 指向的對(duì)吧?cs 寄存器是代碼段寄存器,里面存著的是段選擇子,還記得它的結(jié)構(gòu)么?

          ?

          ?

          這里面的低端兩位,此時(shí)表示 CPL,也就是當(dāng)前所處的特權(quán)級(jí),假如我們現(xiàn)在這個(gè)時(shí)刻,CS 寄存器的后兩位為 3,二進(jìn)制就是 11,就表示是當(dāng)前處理器處于用戶態(tài)這個(gè)特權(quán)級(jí)。

          ?

          假如我們此時(shí)要跳轉(zhuǎn)到另一處內(nèi)存地址執(zhí)行,在最終的匯編指令層面無(wú)非就是 jmp、call 和中斷。我們拿 jmp 跳轉(zhuǎn)來(lái)舉例。

          ?

          如果是短跳轉(zhuǎn),也就是直接 jmp xxx,那不涉及到段的變換,也就沒(méi)有特權(quán)級(jí)檢查這回事。

          ?

          如果是長(zhǎng)跳轉(zhuǎn),也就是 jmp yyy : xxx,這里的 yyy 就是另一個(gè)要跳轉(zhuǎn)到的段的段選擇子結(jié)構(gòu)。

          ?

          ?

          這個(gè)結(jié)構(gòu)仍然是一樣的段選擇子結(jié)構(gòu),只不過(guò)這里的低端兩位,表示 RPL,也就是請(qǐng)求特權(quán)級(jí),表示我想請(qǐng)求的特權(quán)級(jí)是什么。同時(shí),CPU 會(huì)拿這個(gè)段選擇子去全局描述符表中尋找段描述符,從中找到段基址。

          ?

          ?

          那還記得段描述符的樣子么?

          ?

          ?

          你看,這里面又有個(gè) DPL,這表示目標(biāo)代碼段特權(quán)級(jí),也就是即將要跳轉(zhuǎn)過(guò)去的那個(gè)段的特權(quán)級(jí)。

          ?

          好了,我們總結(jié)一下簡(jiǎn)圖,就是這三個(gè)玩意的比較。

          ?

          ?

          這里的檢查規(guī)則比較多,簡(jiǎn)單說(shuō),絕大多數(shù)情況下,要求 CPL 必須等于 DPL,才會(huì)跳轉(zhuǎn)成功,否則就會(huì)報(bào)錯(cuò)。

          ?

          也就是說(shuō),當(dāng)前代碼所處段的特權(quán)級(jí),必須要等于要跳轉(zhuǎn)過(guò)去的代碼所處的段的特權(quán)級(jí),那就只能用戶態(tài)往用戶態(tài)跳,內(nèi)核態(tài)往內(nèi)核態(tài)跳,這樣就防止了處于用戶態(tài)的程序,跳轉(zhuǎn)到內(nèi)核態(tài)的代碼段中做壞事。

          ?

          這只是代碼段跳轉(zhuǎn)時(shí)所做的特權(quán)級(jí)檢查,還有訪問(wèn)內(nèi)存數(shù)據(jù)時(shí)也會(huì)有數(shù)據(jù)段的特權(quán)級(jí)檢查,這里就不展開了。最終的效果是,處于內(nèi)核態(tài)的代碼可以訪問(wèn)任何特權(quán)級(jí)的數(shù)據(jù)段,處于用戶態(tài)的代碼則只可以訪問(wèn)用戶態(tài)的數(shù)據(jù)段,這也就實(shí)現(xiàn)了內(nèi)存數(shù)據(jù)讀寫的保護(hù)。

          ?

          說(shuō)了這么多,其實(shí)就是,代碼跳轉(zhuǎn)只能同特權(quán)級(jí),數(shù)據(jù)訪問(wèn)只能高特權(quán)級(jí)訪問(wèn)低特權(quán)級(jí)。



          特權(quán)級(jí)轉(zhuǎn)換的方式


          ?

          誒不對(duì)呀,那我們今天要講的是,從內(nèi)核態(tài)轉(zhuǎn)變?yōu)橛脩魬B(tài),那如果代碼跳轉(zhuǎn)只能同特權(quán)級(jí)跳,我們現(xiàn)在處于內(nèi)核態(tài),要怎么樣才能跳轉(zhuǎn)到用戶態(tài)呢?

          ?

          Intel 設(shè)計(jì)了好多種特權(quán)級(jí)轉(zhuǎn)換的方式,中斷中斷返回就是其中的一種。


          處于用戶態(tài)的程序,通過(guò)觸發(fā)中斷,可以進(jìn)入內(nèi)核態(tài),之后再通過(guò)中斷返回,又可以恢復(fù)為用戶態(tài)

          ?

          就是剛剛的圖所表示的。



          系統(tǒng)調(diào)用就是這么玩的,用戶通過(guò) int 0x80 中斷指令觸發(fā)了中斷,CPU 切換至內(nèi)核態(tài),執(zhí)行中斷處理程序,之后中斷程序返回,又從內(nèi)核態(tài)切換回用戶態(tài)。

          ?

          但有個(gè)問(wèn)題是,我們當(dāng)前的代碼,此時(shí)就是處于內(nèi)核態(tài),并不是由一個(gè)用戶態(tài)程序通過(guò)中斷而切換到的內(nèi)核態(tài),那怎么回到原來(lái)的用戶態(tài)呢?答案還是,通過(guò)中斷返回。

          ?

          沒(méi)有中斷也能中斷返回?可以的,Intel 設(shè)計(jì)的 CPU 就是這樣不符合人們的直覺(jué),中斷和中斷返回的確是應(yīng)該配套使用的,但也可以單獨(dú)使用,我們看代碼。

          void?main(void)?{
          ????...????
          ????move_to_user_mode();
          ????...
          }

          #define?move_to_user_mode()?\
          _asm?{?\
          ????_asm?mov?eax,esp?\
          ????_asm?push?00000017h?\
          ????_asm?push?eax?\
          ????_asm?pushfd?\
          ????_asm?push?0000000fh?\
          ????_asm?push?offset?l1?\
          ????
          _asm?iretd?/*?執(zhí)行中斷返回指令*/?\
          _asm?l1:?mov?eax,17h?\
          ????_asm?mov?ds,ax?\
          ????_asm?mov?es,ax?\
          ????_asm?mov?fs,ax?\
          ????_asm?mov?gs,ax?\
          }

          你看,這個(gè)方法里直接就執(zhí)行了中斷返回指令 iretd。

          ?

          那么為什么之前進(jìn)行了一共五次的壓棧操作呢?因?yàn)橹袛喾祷乩碚撋暇褪菓?yīng)該和中斷配合使用的,而此時(shí)并不是真的發(fā)生了中斷到這里,所以我們得假裝發(fā)生了中斷才行。

          ?

          怎么假裝呢?其實(shí)就把棧做做工作就好了,中斷發(fā)生時(shí),CPU 會(huì)自動(dòng)幫我們做如下的壓棧操作。而中斷返回時(shí),CPU 又會(huì)幫我們把壓棧的這些值返序賦值給響應(yīng)的寄存器。

          ?

          ?

          去掉錯(cuò)誤碼,剛好是五個(gè)參數(shù),所以我們?cè)诖a中模仿 CPU 進(jìn)行了五次壓棧操作,這樣在執(zhí)行 iretd 指令時(shí),硬件會(huì)按順序?qū)倓倝喝霔V械臄?shù)據(jù),分別賦值給 SS、ESP、EFLAGS、CS、EIP 這幾個(gè)寄存器,這就感覺(jué)像是正確返回了一樣,讓其誤以為這是通過(guò)中斷進(jìn)來(lái)的。

          ?

          壓入棧的 CS 和 EIP 就表示中斷發(fā)生前代碼所處的位置,這樣中斷返回后好繼續(xù)去那里執(zhí)行。


          壓入的 SS 和 ESP 表示中斷發(fā)生前的棧的位置,這樣中斷返回后才好恢復(fù)原來(lái)的棧。


          其中,特權(quán)級(jí)的轉(zhuǎn)換,就體現(xiàn)在 CS 和 SS 寄存器的值里,都是細(xì)節(jié)!


          CS 和 SS 寄存器是段寄存器的一種,段寄存器里的值是段選擇子,其結(jié)構(gòu)上面已經(jīng)提過(guò)兩遍了,在?第六回 | 先解決段寄存器的歷史包袱問(wèn)題?中也專門講了這個(gè)結(jié)構(gòu)的作用。



          對(duì)著這個(gè)結(jié)構(gòu),我們看代碼。

          #define?move_to_user_mode()?\
          _asm?{?\
          ????_asm?mov?eax,esp?\
          ????
          _asm?push?00000017h?\ ; 給 SS 賦值
          ????_asm?push?eax?\
          ????_asm?pushfd?\
          ????
          _asm?push?0000000fh?\ ; 給 CS 賦值
          ????_asm?push?offset?l1?\
          ????
          _asm?iretd?/*?執(zhí)行中斷返回指令*/?\
          _asm?l1:?mov?eax,17h?\
          ????_asm?mov?ds,ax?\
          ????_asm?mov?es,ax?\
          ????_asm?mov?fs,ax?\
          ????_asm?mov?gs,ax?\
          }

          拿 CS 舉例,給它賦的值是,0000000fh,用二進(jìn)制表示為:


          0000000000001111


          最后兩位 11 表示特權(quán)級(jí)為 3,即用戶態(tài)。而我們剛剛說(shuō)了,CS 寄存器里的特權(quán)級(jí),表示 CPL,即當(dāng)前處理器特權(quán)級(jí)。


          所以經(jīng)過(guò) iretd 返回之后,CS 的值就變成了它,而當(dāng)前處理器特權(quán)級(jí),也就變成了用戶態(tài)特權(quán)級(jí)。



          除了改變特權(quán)級(jí)之外



          除了改變了特權(quán)級(jí)之外,還做了什么事情呢?

          剛剛我們關(guān)注段寄存器,只關(guān)注了特權(quán)級(jí)的部分,我們?cè)僭敿?xì)看看。

          剛剛說(shuō)了 CS 寄存器為?0000000000001111,最后兩位表示用戶態(tài)的含義。


          那繼續(xù)解讀,倒數(shù)第三位 TI 表示,前面的描述符索引,是從 GDT 還是 LDT 中取,1 表示 LDT,也就是從局部描述符表中取。

          前面的描述符索引為 1,表示從局部描述符表中取到代碼段描述符,如果你熟悉前面我講過(guò)的內(nèi)容,你將會(huì)直接得出上述結(jié)論。不過(guò)我還是幫你回憶一下。

          在?第18回 | 大名鼎鼎的進(jìn)程調(diào)度就是從這里開始的 中,將 0 號(hào) LDT 作為當(dāng)前的 LDT 索引,記錄在了 CPU 的 lldt 寄存器中。
          #define?lldt(n)?__asm__("lldt?%%ax"::"a"?(_LDT(n)))

          void?sched_init(void)?{
          ????...
          ????lldt(0);
          ????...
          }

          而整個(gè) GDT 與 LDT 表的設(shè)計(jì),經(jīng)過(guò)整個(gè)?第一部分 進(jìn)入內(nèi)核前的苦力活?和?第二部分 大戰(zhàn)前期的初始化工作?的設(shè)計(jì)后,成了這個(gè)樣子。



          所以,一目了然。


          再看這行代碼,把 EIP 寄存器賦值為了那行標(biāo)號(hào)的地址。

          void?main(void)?{
          ????...????
          ????move_to_user_mode();
          ????...
          }

          #define?move_to_user_mode()?\
          _asm?{?\
          ????_asm?mov?eax,esp?\
          ????_asm?push?00000017h?\
          ????_asm?push?eax?\
          ????_asm?pushfd?\
          ????_asm?push?0000000fh?\
          ????
          _asm?push?offset?l1?\
          ????_asm?iretd?/*?執(zhí)行中斷返回指令*/?\
          _asm?l1:?mov?eax,17h?\
          ????_asm?mov?ds,ax?\
          ????_asm?mov?es,ax?\
          ????_asm?mov?fs,ax?\
          ????_asm?mov?gs,ax?\
          }

          這里剛好設(shè)置的是下面標(biāo)號(hào) l1 的位置,所以 iretd 之后 CPU 就乖乖去那里執(zhí)行了。所以其實(shí)從效果上看,就是順序往下執(zhí)行,只不過(guò)利用了 iretd 做了些特權(quán)級(jí)轉(zhuǎn)換等工作。


          同理,這里的棧段 ss 和數(shù)據(jù)段 ds,都被賦值為了 17h,大家可以展開二進(jìn)制算一下,他們又是什么特權(quán)級(jí),對(duì)應(yīng)的描述符又是誰(shuí)。



          總結(jié)


          ?

          所以其實(shí),最終效果上看就是按順序執(zhí)行了我們所寫的指令,仿佛沒(méi)有經(jīng)過(guò)什么中斷和中斷返回的過(guò)程,但卻通過(guò)中斷返回實(shí)現(xiàn)了特權(quán)級(jí)的翻轉(zhuǎn),也就是從內(nèi)核態(tài)變?yōu)榱擞脩魬B(tài),順便設(shè)置了棧段、代碼段和數(shù)據(jù)段的基地址。


          好了,我們兜兜轉(zhuǎn)轉(zhuǎn)終于把這個(gè) mov_to_user_mode 講完了,特權(quán)級(jí)這塊的檢查細(xì)節(jié)非常繁瑣,為了理解操作系統(tǒng),我們只需要暫且記住如下一句話就好了:

          ?

          數(shù)據(jù)訪問(wèn)只能高特權(quán)級(jí)訪問(wèn)低特權(quán)級(jí),代碼跳轉(zhuǎn)只能同特權(quán)級(jí)跳轉(zhuǎn),要想實(shí)現(xiàn)特權(quán)級(jí)轉(zhuǎn)換,可以通過(guò)中斷和中斷返回來(lái)實(shí)現(xiàn)。

          ?

          OK,我們現(xiàn)在已經(jīng)進(jìn)入了用戶態(tài),也即表明了需要內(nèi)核態(tài)來(lái)完成的工作已經(jīng)全部安排妥當(dāng)了,其實(shí)就是整個(gè)?第一部分 進(jìn)入內(nèi)核前的苦力活 和?第二部分 大戰(zhàn)前期的初始化工作 的內(nèi)容,對(duì)全局描述符表、中斷描述符表、頁(yè)表等關(guān)鍵內(nèi)存結(jié)構(gòu)進(jìn)行設(shè)置,以及對(duì) CPU 特殊寄存器如 cr0 和 cr3 的設(shè)置,還有對(duì)外設(shè)如硬盤、鍵盤、定時(shí)器的設(shè)置等。

          ?

          看來(lái)我們又完成了一大堆苦力活呀,內(nèi)核態(tài)做的工作也真是枯燥乏味呢。接下來(lái)只需要在用戶態(tài)進(jìn)行工作即可了!

          ?

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



          ------- 關(guān)于本系列的完整內(nèi)容?-------



          本系列的開篇詞看這

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


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

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


          本系列全局視角



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


          公眾號(hào)更新系列文章不易,閱讀量越來(lái)越低,希望大家多多傳播,不方便的話點(diǎn)個(gè)小小的在看我也會(huì)很開心,我相信星火燎原的力量,謝謝大家咯。


          另外,本系列完全免費(fèi),希望大家能多多傳播給同樣喜歡的人,同時(shí)給我的 GitHub 項(xiàng)目點(diǎn)個(gè) star,就在閱讀原文,這些就足夠讓我堅(jiān)持寫下去了!我們下回見。

          瀏覽 63
          點(diǎn)贊
          評(píng)論
          收藏
          分享

          手機(jī)掃一掃分享

          分享
          舉報(bào)
          評(píng)論
          圖片
          表情
          推薦
          點(diǎn)贊
          評(píng)論
          收藏
          分享

          手機(jī)掃一掃分享

          分享
          舉報(bào)
          <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>
                  久久人妻影院 | 婷婷丁香激情综合 | 欧美精品三级在线观看 | 中文无码在线综合网 | av搬运一区二区三区在线观看 |