mit6.828-lab3 用戶環(huán)境

lab3是實現(xiàn)用戶環(huán)境雪营,或者說是用戶進(jìn)程弓千。exercize代碼見 這里

1 概述

繼內(nèi)存管理之后献起,實驗3是實現(xiàn)用戶環(huán)境洋访,這里的用戶環(huán)境,其實就類比Unix/Linux下的進(jìn)程即可谴餐。因為JOS的環(huán)境與Unix進(jìn)程提供了不同的接口和語義姻政,所以用環(huán)境一詞代替進(jìn)程,在本文中進(jìn)程和環(huán)境兩個詞就不做區(qū)分了岂嗓。

2 進(jìn)程定義

inc/env.h中包含了一些用戶環(huán)境的基本定義汁展,JOS內(nèi)核使用 Env結(jié)構(gòu)體來追蹤用戶進(jìn)程。其中 envs變量是指向所有進(jìn)程的鏈表的指針厌殉,其操作方式跟實驗2的pages類似食绿,env_free_list是空閑的進(jìn)程結(jié)構(gòu)鏈表。注意下公罕,在早起的JOS實驗中器紧,pages和envs都是用的雙向鏈表,現(xiàn)在的版本用的單向鏈表操作起來更加簡單和清晰楼眷。

struct Env *envs = NULL;        // All environments
struct Env *curenv = NULL;      // The current env
static struct Env *env_free_list;   // Free environment list

注意铲汪,現(xiàn)代操作系統(tǒng)中通常都可以多進(jìn)程并發(fā)執(zhí)行的,這取決于 PCB 表的大小罐柳。在 JOS 系 統(tǒng)中掌腰,evns 數(shù)組就等價于 PCB 表,其共有 1024(NENV)個表項张吉,即 JOS 系統(tǒng)并發(fā)度為 1024辅斟。 其相關(guān)宏在 inc/Env.h 中定義:

// +1+---------------21-----------------+--------10--------+
// |0|          Uniqueifier             |   Environment    |
// | |                                  |      Index       |
// +------------------------------------+------------------+
//                                       \--- ENVX(eid) --/
#define LOG2NENV        10
#define NENV            (1 << LOG2NENV)
#define ENVX(envid)     ((envid) & (NENV - 1))

struct Env {
    struct Trapframe env_tf;    // Saved registers
    struct Env *env_link;       // Next free Env
    envid_t env_id;         // Unique environment identifier
    envid_t env_parent_id;      // env_id of this env's parent
    enum EnvType env_type;      // Indicates special system environments
    unsigned env_status;        // Status of the environment
    uint32_t env_runs;      // Number of times environment has run

    // Address space
    pde_t *env_pgdir;       // Kernel virtual address of page dir
};

進(jìn)程結(jié)構(gòu)體 Env 各字段定義如下:

  • env_tf: 當(dāng)進(jìn)程停止運行時用于保存寄存器的值,比如當(dāng)發(fā)生中斷切換到內(nèi)核環(huán)境運行了或者切換到另一個進(jìn)程運行的時候需要保存當(dāng)前進(jìn)程的寄存器的值以便后續(xù)該進(jìn)程繼續(xù)執(zhí)行芦拿。
  • env_link:指向空閑進(jìn)程鏈表 env_free_list 中的下一個 Env 結(jié)構(gòu)士飒。
  • env_id: 進(jìn)程ID。因為進(jìn)程ID是正數(shù)蔗崎,所以符號位是0酵幕,而中間的21位是標(biāo)識符,標(biāo)識在不同的時間創(chuàng)建但是卻共享同一個進(jìn)程索引號的進(jìn)程缓苛,最后10位是進(jìn)程的索引號芳撒,要用envs索引進(jìn)程管理結(jié)構(gòu) Env 就要用 ENVX(env_id)
  • env_parent_id: 進(jìn)程的父進(jìn)程ID未桥。
  • env_type:進(jìn)程類型笔刹,通常是 ENV_TYPE_USER,后面實驗中可能會用到其他類型冬耿。
  • env_status:進(jìn)程狀態(tài)舌菜,進(jìn)程可能處于下面幾種狀態(tài)
    • ENV_FREE:標(biāo)識該進(jìn)程結(jié)構(gòu)處于不活躍狀態(tài),存在于 env_free_list 鏈表亦镶。
    • ENV_RUNNABLE: 標(biāo)識該進(jìn)程處于等待運行的狀態(tài)日月。
    • ENV_RUNNING: 標(biāo)識該進(jìn)程是當(dāng)前正在運行的進(jìn)程。
    • ENV_NOT_RUNNABLE: 標(biāo)識該進(jìn)程是當(dāng)前運行的進(jìn)程缤骨,但是處于不活躍的狀態(tài)爱咬,比如在等待另一個進(jìn)程的IPC。
    • ENV_DYING: 該狀態(tài)用于標(biāo)識僵尸進(jìn)程绊起。在實驗4才會用到這個狀態(tài)精拟,實驗3不用。
  • env_pgdir:用于保存進(jìn)程頁目錄的虛擬地址虱歪。

3 進(jìn)程初始化及運行

進(jìn)程管理結(jié)構(gòu)envs對應(yīng)的1024個Env結(jié)構(gòu)體在物理內(nèi)存中緊接著pages存儲蜂绎。進(jìn)程初始化流程主要包括:

  • 給NENV個Env結(jié)構(gòu)體在內(nèi)存中分配空間,并將 envs 結(jié)構(gòu)體的物理地址映射到 從 UENV 所指向的線性地址空間实蔽,該線性地址空間允許用戶訪問且只讀荡碾,所以頁面權(quán)限被標(biāo)記為PTE_U。
  • 調(diào)用env_init函數(shù)初始化envs局装,將 NENV 個進(jìn)程管理結(jié)構(gòu)Env通過env_link串聯(lián)起來坛吁,注意,env_free_list要指向第一個 Env铐尚,所以這里要用倒序的方式拨脉。在env_init函數(shù)中調(diào)用了env_init_percpu函數(shù),加載新的全局描述符表宣增,設(shè)置內(nèi)核用到的寄存器 es, ds, ss的值為GD_KD玫膀,即內(nèi)核的段選擇子,DPL為0爹脾。然后通過ljmp指令asm volatile("ljmp %0,$1f\n 1:\n" : : "i" (GD_KT));設(shè)置CS為 GD_KT帖旨。這句匯編用到了unnamed local labels箕昭,含義就是跳轉(zhuǎn)到 GD_KT, 1:這個地址處,其中的 $1f的意思是指跳轉(zhuǎn)到后一個1:標(biāo)簽處解阅,如果是前一個落竹,用$1b,而這個后一個1:標(biāo)簽就是語句后面货抄,所以最終效果只是設(shè)置了CS寄存器的值為GD_KT而已述召。
  • 初始化好了envs和env_free_list后,接著調(diào)用 ENV_CREATE(user_hello, ENV_TYPE_USER) 創(chuàng)建用戶進(jìn)程蟹地。ENV_CREATEkern/env.h中的宏定義积暖,展開就是調(diào)用的 env_create,只是參數(shù)設(shè)置成了 env_create(_binary_obj_user_hello_start, ENV_TYPE_USER)。env_create也是我們要實現(xiàn)的函數(shù)怪与,它的功能就是先調(diào)用env_alloc函數(shù)分配好Env結(jié)構(gòu)夺刑,初始化Env的各個字段值(如env_id,env_type琼梆,env_status以及env_tf的用于存儲寄存器值的字段性誉,運行用戶程序時會將 env_tf 的字段值加載到對應(yīng)的寄存器中),為該用戶進(jìn)程分配頁目錄表并調(diào)用load_icode函數(shù)加載程序代碼到內(nèi)存中茎杂。
    • env_alloc調(diào)用env_setup_vm函數(shù)分配好頁目錄的頁表错览,并設(shè)置頁目錄項和env_pgdir字段)。
    • load_icode函數(shù)則是先設(shè)置cr3寄存器切換到該進(jìn)程的頁目錄env_pgdir煌往,然后通過region_alloc分配每個程序段的內(nèi)存并按segment將代碼加載到對應(yīng)內(nèi)存中倾哺,加載完成后設(shè)置 env_tf->tf_eip為Elf的e_entry刽脖,即程序的初始執(zhí)行位置。
  • 加載完程序代碼后,萬事俱備膳殷,調(diào)用 env_run(e) 函數(shù)開始運行程序茅姜。如果當(dāng)前有進(jìn)程正在運行击孩,則設(shè)置當(dāng)前進(jìn)程狀態(tài)為ENV_RUNNABLE,并將需要運行的進(jìn)程e的狀態(tài)設(shè)置為ENV_RUNNING撬腾,然后加載e的頁目錄表地址 env_pgdir 到cr3寄存器中,調(diào)用 env_pop_tf(struct Trapframe *tf) 開始執(zhí)行程序e恢恼。
  • env_pop_tf其實就是將棧指針esp指向該進(jìn)程的env_tf民傻,然后將 env_tf 中存儲的寄存器的值彈出到對應(yīng)寄存器中,最后通過 iret 指令彈出棧中的元素分別到 EIP, CS, EFLAGS 到對應(yīng)寄存器并跳轉(zhuǎn)到 CS:EIP 存儲的地址執(zhí)行(當(dāng)使用iret指令返回到一個不同特權(quán)級運行時牵署,還會彈出堆棧段選擇子及堆棧指針分別到SS與SP寄存器)喧半,這樣,相關(guān)寄存器都從內(nèi)核設(shè)置成了用戶程序?qū)?yīng)的值,EIP存儲的是程序入口地址爽柒。
  • env_id的生成規(guī)則很有意思浩村,注意一下在env_free中并沒有重置env_id的值,這就是為了用來下一次使用這個env結(jié)構(gòu)體時生成一個新的env_id酿矢,區(qū)分之前用過的env_id怎燥,從generation的生成方式就能明白了。

用戶程序運行路徑如下所示:

start (kern/entry.S)
i386_init (kern/init.c)
    cons_init
    mem_init
    env_init
    trap_init (still incomplete at this point)
    env_create
        env_alloc
            env_setup_vm
        load_icode
            region_alloc
    env_run
        env_pop_tf

關(guān)于Trapframe

Trapframe結(jié)構(gòu)體存儲的是當(dāng)前進(jìn)程的寄存器的值策肝,可以看到env_pop_tf函數(shù)中便是將trapframe的起始地址賦值給esp之众,然后用的這個順序?qū)V性貜棾龅綄?yīng)寄存器中的依许。其中popal是彈出tf_regs到所有的通用寄存器中,接著彈出值到es膘婶,ds寄存器坦康,接著跳過trapno和errcode滞欠,調(diào)用iret分別將棧中存儲數(shù)據(jù)彈出到 EIP, CS, EFLAGS寄存器中古胆。

struct Trapframe {
    struct PushRegs tf_regs;
    uint16_t tf_es;
    uint16_t tf_padding1;
    uint16_t tf_ds;
    uint16_t tf_padding2;
    uint32_t tf_trapno;
    /* below here defined by x86 hardware */
    uint32_t tf_err;
    uintptr_t tf_eip;
    uint16_t tf_cs;
    uint16_t tf_padding3;
    uint32_t tf_eflags;
    /* below here only when crossing rings, such as from user to kernel */
    uintptr_t tf_esp;
    uint16_t tf_ss;
    uint16_t tf_padding4;
} __attribute__((packed));

//
// Restores the register values in the Trapframe with the 'iret' instruction.
// This exits the kernel and starts executing some environment's code.
//
// This function does not return.
//
void env_pop_tf(struct Trapframe *tf)
{
    asm volatile(
        "\tmovl %0,%%esp\n"
        "\tpopal\n"
        "\tpopl %%es\n"
        "\tpopl %%ds\n"
        "\taddl $0x8,%%esp\n" /* skip tf_trapno and tf_errcode */
        "\tiret\n"
        : : "g" (tf) : "memory");
    panic("iret failed");  /* mostly to placate the compiler */
}

關(guān)于CPL, RPL, DPL

CPL是當(dāng)前正在執(zhí)行的代碼所在的段的特權(quán)級逸绎,存在于CS寄存器的低兩位(對CS來說,選擇子的RPL=當(dāng)前段的CPL)巫糙。RPL指的是進(jìn)程對段訪問的請求權(quán)限参淹,是針對段選擇子而言的乏悄,不是固定的。DPL則是在段描述符中存儲的开呐,規(guī)定了段的訪問級別规求,是固定的阻肿。為什么需要RPL呢?因為同一時刻只能有一個CPL伤极,而低權(quán)限的用戶程序去調(diào)用內(nèi)核的功能來訪問一個目標(biāo)段時姨伤,進(jìn)入內(nèi)核代碼段時CPL 變成了內(nèi)核的CPL乍楚,如果沒有RPL,那么權(quán)限檢查的時候就會用CPL忿偷,而這個CPL 權(quán)限比用戶程序權(quán)限高臊泌,也就可能去訪問需要高權(quán)限才能訪問的數(shù)據(jù)渠概,導(dǎo)致安全問題嫂拴。所以引入RPL贮喧,讓它去代表訪問權(quán)限箱沦,因此在檢查CPL 的同時,也會檢查RPL灶伊。一般來說如果RPL 的數(shù)字比CPL大(權(quán)限比CPL的低)寒跳,那么RPL會起決定性作用冯袍,這個權(quán)限檢查是CPU硬件層面做的碾牌。

用戶程序代碼

實驗中用到的用戶程序代碼位于user目錄舶吗,如user_hello對應(yīng)的源文件是user/hello.c因為還沒有實現(xiàn)文件系統(tǒng)检激,所以這些用戶程序代碼通過一系列編譯命令后最終會編譯到內(nèi)核中腹侣。比如 user/hello.c 編譯到內(nèi)核中地址是 0xf011c356傲隶。詳見 kern/Makefrag,用命令make V=1可以顯示完整的編譯命令复濒。

之前有個疑惑就是 user/hello.c 是怎么編譯到kernel里面后在obj/kern/kernel.sym有了 _binary_obj_user_hello_start乒省、_binary_obj_user_hello_end以及_binary_obj_user_hello_size這幾個符號的袖扛。這個其實是 ld命令生成的。具體命令是下面這個晾嘶,ld -b binary會自動在最終的可kernel文件中生成對應(yīng)開始結(jié)束符號垒迂,以_binary_開頭,因為我們的用戶程序編譯后代碼目錄結(jié)構(gòu)是 obj/user/hello楷拳,所以符號名就是將目錄換成了下劃線吏奸。

ld -o obj/kern/kernel -m elf_i386 -T kern/kernel.ld -nostdlib obj/kern/entry.o obj/kern/entrypgdir.o ... /usr/lib/gcc/x86_64-linux-gnu/4.8/32/libgcc.a 
    -b binary  obj/user/hello ...

另外提下的是奋蔚,kern.sym 這個符號表文件里面存儲的是符號的地址。符號有類型坤按,大寫表示全局符號馒过,小寫則是局部符號腹忽。類型說明:

  • A:符號是絕對值。比如表示代碼長度的符號 _binary_obj_user_hello_size嘹锁。
  • T: 代碼段符號兼耀。
  • D:已初始化數(shù)據(jù)段符號求冷。
  • B:未初始化數(shù)據(jù)段符號。

用戶代碼中的系統(tǒng)調(diào)用

在 Env初始化后匠题,運行編譯好的 user_hello 進(jìn)程會報錯韭山,因為 user_hello里面調(diào)用了 cprintf 打印輸出冷溃,我們不能讓用戶程序來操作硬件設(shè)備似枕,因此cprintf最終要通過系統(tǒng)調(diào)用來實現(xiàn)年柠,此時系統(tǒng)調(diào)用功能還沒有實現(xiàn)冗恨。

注意 kernlib目錄下面都有 printf.c和syscall.c文件,kern目錄下面的是內(nèi)核專用的虐拓。而用戶要cprintf輸出蓉驹,就要使用lib目錄下面的printf.c中的函數(shù)揪利,最后經(jīng)由lib/syscall.csys_cputs(),最終通過該文件中的syscall()來實現(xiàn)輸出。

4 中斷和異常處理

4.1 中斷/異常概述

中斷和異常都是”保護(hù)控制轉(zhuǎn)移(PCT)”機(jī)制献汗,它們將處理器從用戶模式轉(zhuǎn)換到內(nèi)核模式罢吃。在英特爾的術(shù)語中昭齐,中斷是指處理器之外的異步事件導(dǎo)致的PCT阱驾,比如外部的IO設(shè)備活動。而異常則是當(dāng)前運行代碼同步觸發(fā)的PCT丧荐,如除0或者非法內(nèi)存訪問等喧枷。根據(jù)異常被報告的方式以及導(dǎo)致異常的指令是否能重新執(zhí)行,異常還可以細(xì)分為故障(Fault)渡冻,陷阱(Trap)和中止(Abort)忧便。JOS中斷在門描述符中的type為STS_IG32茬腿,異常的type為 STS_TG32。

  • Fault是通澄沾。可以被糾正的異常悴品,糾正后可以繼續(xù)運行。出現(xiàn)Fault時,處理器會把機(jī)器狀態(tài)恢復(fù)到產(chǎn)生Fault指令之前的狀態(tài)岖妄,此時異常處理程序返回地址會指向產(chǎn)生Fault的指令寂祥,而不是后一條指令丸凭,產(chǎn)生Fault的指令在中斷處理程序返回后會重新執(zhí)行。如Page Fault铛碑。
  • Trap處理程序返回后執(zhí)行的指令是引起陷阱指令的后一條指令亚茬。
  • Abort則不允許異常指令繼續(xù)執(zhí)行浓恳。

中斷描述符表將每個中斷向量和一個中斷門描述符對應(yīng)起來,中斷門描述符里面存儲中斷或異常的處理程序的入口地址以及DPL。x86 允許256個中斷和異常入口噪奄,每個對應(yīng)一個唯一的整數(shù)值勤篮,稱為中斷向量色罚。中斷描述符表的起始地址存儲在IDT寄存器中戳护,當(dāng)發(fā)生中斷/異常時,CPU使用中斷向量作為中斷描述符表的索引铺董,通過中斷門描述符中存儲的段選擇子和偏移量精续,可以到GDT中找到中斷處理程序的地址驻右。

       IDT                   trapentry.S         trap.c
   
+----------------+                        
|   &handler1    |---------> handler1:          trap (struct Trapframe *tf)
|                |             // do stuff      {
|                |             call trap          // handle the exception/interrupt
|                |             // ...           }
+----------------+
|   &handler2    |--------> handler2:
|                |            // do stuff
|                |            call trap
|                |            // ...
+----------------+
       .
       .
       .
+----------------+
|   &handlerX    |--------> handlerX:
|                |             // do stuff
|                |             call trap
|                |             // ...
+----------------+

x86 使用0-31號中斷向量作為處理器內(nèi)部的同步的異常類型堪夭,比如除零和缺頁異常拣凹。而32號之上的中斷向量用于軟件中斷(如int指令產(chǎn)生的軟件中斷)或者外部設(shè)備產(chǎn)生的異步的硬件中斷嚣镜。lab 3我們會用到0-31號以及48號(用于系統(tǒng)調(diào)用)中斷向量,在后面實驗中還會處理外部的時鐘中斷付呕,JOS 用到的中斷如下:

#define T_DIVIDE     0      // divide error
#define T_DEBUG      1      // debug exception
#define T_NMI        2      // non-maskable interrupt
#define T_BRKPT      3      // breakpoint
#define T_OFLOW      4      // overflow
#define T_BOUND      5      // bounds check
#define T_ILLOP      6      // illegal opcode
#define T_DEVICE     7      // device not available
#define T_DBLFLT     8      // double fault
/* #define T_COPROC  9 */   // reserved (not generated by recent processors)
#define T_TSS       10      // invalid task switch segment
#define T_SEGNP     11      // segment not present
#define T_STACK     12      // stack exception
#define T_GPFLT     13      // general protection fault
#define T_PGFLT     14      // page fault
/* #define T_RES    15 */   // reserved
#define T_FPERR     16      // floating point error
#define T_ALIGN     17      // aligment check
#define T_MCHK      18      // machine check
#define T_SIMDERR   19      // SIMD floating point error

// These are arbitrarily chosen, but with care not to overlap
// processor defined exceptions or interrupt vectors.
#define T_SYSCALL   48      // system call

4.2 中斷/異常處理流程

在用戶程序內(nèi)發(fā)生中斷/異常時徽职,CPU會自動將控制器轉(zhuǎn)移到中斷處理程序處姆钉。前面提到潮瓶,中斷門描述符存儲了中斷處理程序的信息,包括其所在的段選擇子埂伦、代碼地址等赤屋。CPU通過IDT寄存器找到中斷描述符表的起始地址壁袄,然后通過中斷向量(即中斷號)找到對應(yīng)的中斷門描述符嗜逻,接著通過中斷門描述符中存儲的段選擇子到GDT中找到段基址,加上偏移地址即可得到中斷處理程序的地址逆日。具體流程如下圖所示:

中斷處理流程

在跳轉(zhuǎn)到中斷處理程序執(zhí)行之前室抽,處理器需要一個地方保存處理器出現(xiàn)中斷/異常前的狀態(tài)坪圾,如調(diào)用異常處理程序之前的EIP 和 CS的值惑朦,這樣處理完中斷/異常后可以從出現(xiàn)中斷/異常前的位置繼續(xù)執(zhí)行漾月。需要注意的是,這塊區(qū)域不能被用戶模式的代碼訪問到蜓陌』つ危基于這個考慮,當(dāng)x86遇到異常/中斷導(dǎo)致特權(quán)級從用戶模式轉(zhuǎn)移到內(nèi)核模式時痴奏,它會將堆棧切換到內(nèi)核棧读拆。TSS就是存儲這個堆棧位置的結(jié)構(gòu)鸵闪,包括堆棧的段選擇子和地址等蚌讼。發(fā)生特權(quán)級別切換時,切換到內(nèi)核棧后芥喇,處理器會在內(nèi)核棧中壓入 SS, ESP, EFLAGS, CS, EIP继控。然后它從中斷門描述符將對應(yīng)的值到加載到寄存器器CS, EIP中胖眷,并將 ESP和SS設(shè)置為指向新的堆棧珊搀。盡管TSS有很多字段境析,但是在JOS中只用到了ESP0和SS0來存儲內(nèi)核棧的地址,其他字段都沒有使用眶拉。其中TSS的段選擇子通過ltr指令加載到TR寄存器中,TR寄存器是個段寄存器谒臼,內(nèi)容為段選擇子的值,注意段寄存器存的是段選擇子在全局描述符的偏移值拾氓,并不是索引值咙鞍。

ts.ts_esp0 = KSTACKTOP;
ts.ts_ss0 = GD_KD;

中斷門描述符在 trap_init()中初始化趾徽,通過 SETGATE定義孵奶。大部分的中斷門描述符的DPL為0了袁,少量的需要允許用戶模式調(diào)用的設(shè)置為3载绿,如系統(tǒng)調(diào)用SYSCALL和斷點BRKPT.

 SETGATE(idt[T_DIVIDE], 0, GD_KT, &handler1, 0);
 ...
 SETGATE(idt[T_SYSCALL], 0, GD_KT, &handler48, 3);

注意,異常/中斷處理時切換堆棧到內(nèi)核棧是處理器執(zhí)行的臀脏,在內(nèi)核棧壓入SS, ESP, EFLAGS, CS, EIP等寄存器的值也是處理器做的揉稚。我們要做的是將TSS的ESP0和SS0設(shè)置為內(nèi)核棧地址搀玖,然后將錯誤碼和異常代號trapno壓入內(nèi)核棧驻呐,接著將ds含末,es佣盒,通用寄存器等寄存器的值壓入內(nèi)核棧中,切換ds和es寄存器的值到內(nèi)核數(shù)據(jù)段GD_KD(_alltraps中處理)盯仪,這樣棧中的數(shù)據(jù)滿足了Trapframe結(jié)構(gòu)全景,后面調(diào)用trap()函數(shù)統(tǒng)一處理。trap()函數(shù)最終通過trap_dispatch()函數(shù)根據(jù)中斷向量來分發(fā)中斷/異常處理滞伟,在lab 3中我們只處理了 T_PGFLT诗良,T_BRKPT鉴裹,T_SYSCALL 這三個中斷向量钥弯,其他的則直接銷毀env并進(jìn)入monitor()脆霎。

4.3 中斷/異常示例

用戶模式下發(fā)生除零中斷時睛蛛,處理器會先切換到 TSS 中存儲的esp0和ss0對應(yīng)的內(nèi)核棧鹦马,并在內(nèi)核棧壓入必要的信息,如下所示:

     +--------------------+ KSTACKTOP             
     | 0x00000 | old SS   |     " - 4
     |      old ESP       |     " - 8
     |     old EFLAGS     |     " - 12
     | 0x00000 | old CS   |     " - 16
     |      old EIP       |     " - 20 <---- ESP 
     +--------------------+             

然后處理器讀取 IDT 中的第0項并設(shè)置 CS 和 EIP指向第0項中斷處理程序的地址忆肾。而對于缺頁異常荸频,還會壓入一個error code,這一步不是處理器做的工作客冈,而是中斷處理程序做的旭从。

處理器可以處理用戶模式或者內(nèi)核模式下的異常/中斷。如果是內(nèi)核模式下發(fā)生了異常/中斷场仲,則因為不需要切換堆棧和悦,只需要內(nèi)核棧壓入 EFLAGS, CS, EIP的值即可渠缕,不用壓入SS和ESP的值鸽素。通過這種機(jī)制,處理器可以優(yōu)雅的處理內(nèi)核代碼出現(xiàn)的嵌套的異常/中斷亦鳞。

     +--------------------+ <---- old ESP
     |     old EFLAGS     |     " - 4
     | 0x00000 | old CS   |     " - 8
     |      old EIP       |     " - 12
     +--------------------+            

4.4 系統(tǒng)調(diào)用

在 JOS 中馍忽,使用 int $0x30 指令引起處理器中斷完成系統(tǒng)調(diào)用澜汤。用戶進(jìn)程通過系統(tǒng)調(diào)用讓內(nèi)核為其完成一些功能,如打印輸出cprintf舵匾,當(dāng)內(nèi)核執(zhí)行完系統(tǒng)調(diào)用后,返回用戶進(jìn)程繼續(xù)執(zhí)行谁不。

注意系統(tǒng)調(diào)用的中斷門描述符的DPL必須設(shè)置為3坐梯,允許用戶調(diào)用。如前面提過刹帕,在int n這類軟中斷調(diào)用時會檢查 CPL 和 DPL吵血,只有當(dāng)前的 CPL 比要調(diào)用的中斷的 DPL值小或者相等才可以調(diào)用,否則就會產(chǎn)生General Protection偷溺。用戶程序通過 lib/syscall.c觸發(fā)系統(tǒng)調(diào)用蹋辅,最終由kern/trap.c中的trap_dispatch()統(tǒng)一分發(fā),并調(diào)用kern/syscall.c中的syscall()處理挫掏。其參數(shù)必須設(shè)置到寄存器中侦另,其中系統(tǒng)調(diào)用號存儲在%eax,其他參數(shù)依次存放到 %edx, %ecx, %ebx, %edi, 和%esi尉共,返回值通過 %eax 來傳遞褒傅。

 asm volatile("int %1\n"
             : "=a" (ret)
             : "i" (T_SYSCALL),
               "a" (num),
               "d" (a1),
               "c" (a2),
               "b" (a3),
               "D" (a4),
               "S" (a5)
             : "cc", "memory");

注意,在kern/trap.c 中對syscall()的返回值要保存在Trapframe的tf_regs.reg_eax字段中袄友,這樣在返回用戶程序執(zhí)行時殿托, env_pop_tf將reg_eax值彈出到 %eax寄存器中,從而實現(xiàn)了返回值傳遞剧蚣。

5 用戶模式開啟

用戶程序的入口在 lib/entry.S支竹,在其中設(shè)置了 envs,pages鸠按,uvpt等全局變量以及_start符號礼搁。_start是整個程序的入口,鏈接器在鏈接時會查找目標(biāo)文件中的_start符號代表的地址目尖,把它設(shè)置為整個程序的入口地址叹坦,所以每個匯編程序都要提供一個_start符號并且用.globl聲明。entry.S中會判斷 USTACKTOP 和 寄存器esp的值是否相等卑雁,若相等募书,則表示沒有參數(shù),則會默認(rèn)在用戶棧中壓入兩個0测蹲,然后調(diào)用libmain函數(shù)莹捡。當(dāng)然lab 3中的用戶程序代碼都沒有傳參數(shù)的。

而libmain()則需要設(shè)置 thisenv 變量(因為測試的用戶程序里面會引用thisenv的一些字段)扣甲,然后調(diào)用umain函數(shù)篮赢,而umain函數(shù)就是我們在 user/hello.c這些文件中定義的主函數(shù)齿椅。最后,執(zhí)行完umain启泣,會調(diào)用 exit退出涣脚。exit就是調(diào)用了系統(tǒng)調(diào)用 sys_env_destroy,最終內(nèi)核通過 env_destroy()銷毀用戶進(jìn)程并回到monitor()寥茫。

內(nèi)存保護(hù)可以確保用戶進(jìn)程中的bug不能破壞其他進(jìn)程或者內(nèi)核遣蚀。當(dāng)用戶進(jìn)程試圖訪問一個無效的或者沒有權(quán)限的地址時,處理器就會中斷進(jìn)程并陷入到內(nèi)核纱耻,若錯誤可修復(fù)芭梯,則內(nèi)核就修復(fù)它并讓用戶進(jìn)程繼續(xù)執(zhí)行;如果無法修復(fù)弄喘,那么用戶進(jìn)程就不能繼續(xù)執(zhí)行玖喘。許多系統(tǒng)調(diào)用接口運行把指針傳給 kernel,這些指針指向用戶buffer蘑志,為防止惡意用戶程序破壞內(nèi)核累奈,內(nèi)核需要對用戶傳遞的指針進(jìn)行權(quán)限檢查。內(nèi)存保護(hù)由 user_mem_check()和 user_mem_assert()實現(xiàn)急但。檢查用戶進(jìn)程訪存權(quán)限费尽,并檢查是否越界。

最后編輯于
?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請聯(lián)系作者
  • 序言:七十年代末羊始,一起剝皮案震驚了整個濱河市旱幼,隨后出現(xiàn)的幾起案子,更是在濱河造成了極大的恐慌突委,老刑警劉巖柏卤,帶你破解...
    沈念sama閱讀 216,470評論 6 501
  • 序言:濱河連續(xù)發(fā)生了三起死亡事件,死亡現(xiàn)場離奇詭異匀油,居然都是意外死亡缘缚,警方通過查閱死者的電腦和手機(jī),發(fā)現(xiàn)死者居然都...
    沈念sama閱讀 92,393評論 3 392
  • 文/潘曉璐 我一進(jìn)店門敌蚜,熙熙樓的掌柜王于貴愁眉苦臉地迎上來桥滨,“玉大人,你說我怎么就攤上這事弛车∑朊剑” “怎么了?”我有些...
    開封第一講書人閱讀 162,577評論 0 353
  • 文/不壞的土叔 我叫張陵纷跛,是天一觀的道長喻括。 經(jīng)常有香客問我,道長贫奠,這世上最難降的妖魔是什么唬血? 我笑而不...
    開封第一講書人閱讀 58,176評論 1 292
  • 正文 為了忘掉前任望蜡,我火速辦了婚禮,結(jié)果婚禮上拷恨,老公的妹妹穿的比我還像新娘脖律。我一直安慰自己,他們只是感情好腕侄,可當(dāng)我...
    茶點故事閱讀 67,189評論 6 388
  • 文/花漫 我一把揭開白布小泉。 她就那樣靜靜地躺著,像睡著了一般兜挨。 火紅的嫁衣襯著肌膚如雪。 梳的紋絲不亂的頭發(fā)上眯分,一...
    開封第一講書人閱讀 51,155評論 1 299
  • 那天拌汇,我揣著相機(jī)與錄音,去河邊找鬼弊决。 笑死噪舀,一個胖子當(dāng)著我的面吹牛,可吹牛的內(nèi)容都是我干的飘诗。 我是一名探鬼主播与倡,決...
    沈念sama閱讀 40,041評論 3 418
  • 文/蒼蘭香墨 我猛地睜開眼,長吁一口氣:“原來是場噩夢啊……” “哼昆稿!你這毒婦竟也來了纺座?” 一聲冷哼從身側(cè)響起,我...
    開封第一講書人閱讀 38,903評論 0 274
  • 序言:老撾萬榮一對情侶失蹤溉潭,失蹤者是張志新(化名)和其女友劉穎净响,沒想到半個月后,有當(dāng)?shù)厝嗽跇淞掷锇l(fā)現(xiàn)了一具尸體喳瓣,經(jīng)...
    沈念sama閱讀 45,319評論 1 310
  • 正文 獨居荒郊野嶺守林人離奇死亡馋贤,尸身上長有42處帶血的膿包…… 初始之章·張勛 以下內(nèi)容為張勛視角 年9月15日...
    茶點故事閱讀 37,539評論 2 332
  • 正文 我和宋清朗相戀三年,在試婚紗的時候發(fā)現(xiàn)自己被綠了畏陕。 大學(xué)時的朋友給我發(fā)了我未婚夫和他白月光在一起吃飯的照片配乓。...
    茶點故事閱讀 39,703評論 1 348
  • 序言:一個原本活蹦亂跳的男人離奇死亡,死狀恐怖惠毁,靈堂內(nèi)的尸體忽然破棺而出犹芹,到底是詐尸還是另有隱情,我是刑警寧澤鞠绰,帶...
    沈念sama閱讀 35,417評論 5 343
  • 正文 年R本政府宣布羽莺,位于F島的核電站,受9級特大地震影響洞豁,放射性物質(zhì)發(fā)生泄漏盐固。R本人自食惡果不足惜荒给,卻給世界環(huán)境...
    茶點故事閱讀 41,013評論 3 325
  • 文/蒙蒙 一、第九天 我趴在偏房一處隱蔽的房頂上張望刁卜。 院中可真熱鬧志电,春花似錦、人聲如沸蛔趴。這莊子的主人今日做“春日...
    開封第一講書人閱讀 31,664評論 0 22
  • 文/蒼蘭香墨 我抬頭看了看天上的太陽孝情。三九已至鱼蝉,卻和暖如春,著一層夾襖步出監(jiān)牢的瞬間箫荡,已是汗流浹背魁亦。 一陣腳步聲響...
    開封第一講書人閱讀 32,818評論 1 269
  • 我被黑心中介騙來泰國打工, 沒想到剛下飛機(jī)就差點兒被人妖公主榨干…… 1. 我叫王不留羔挡,地道東北人洁奈。 一個月前我還...
    沈念sama閱讀 47,711評論 2 368
  • 正文 我出身青樓,卻偏偏與公主長得像绞灼,于是被迫代替她去往敵國和親利术。 傳聞我的和親對象是個殘疾皇子,可洞房花燭夜當(dāng)晚...
    茶點故事閱讀 44,601評論 2 353

推薦閱讀更多精彩內(nèi)容

  • lab4 是實現(xiàn)多處理器支持以及搶占式任務(wù)調(diào)度低矮,exercize代碼見 這里印叁。 1 多處理器啟動流程 1.1 多處...
    __七把刀__閱讀 4,644評論 0 4
  • 計算機(jī)系統(tǒng)漫游 代碼從文本到可執(zhí)行文件的過程(c語言示例):預(yù)處理階段,處理 #inlcude 军掂, #defin...
    willdimagine閱讀 3,581評論 0 5
  • Spring Cloud為開發(fā)人員提供了快速構(gòu)建分布式系統(tǒng)中一些常見模式的工具(例如配置管理喉钢,服務(wù)發(fā)現(xiàn),斷路器良姆,智...
    卡卡羅2017閱讀 134,651評論 18 139
  • 操作系統(tǒng)概論 操作系統(tǒng)的概念 操作系統(tǒng)是指控制和管理計算機(jī)的軟硬件資源肠虽,并合理的組織調(diào)度計算機(jī)的工作和資源的分配,...
    野狗子嗷嗷嗷閱讀 11,928評論 3 34
  • 2018.05.28 星期一 晴 中午下班接上兒子玛追,我在路上跟兒子說我給他買了《哈利波特》全集税课,算是對他...
    博涵媽媽閱讀 171評論 0 1