天天看點

MIT-JOS系列5:使用者環境(一)Part A:使用者環境和異常處理使用者環境建立進行中斷和異常

Part A:使用者環境和異常處理

注:根據MIT-JOS的lab指導手冊,以下不明确區分“環境”和“程序”

使用者環境建立

本節中我們将實作一些核心的基本工具來支援受保護的使用者程序的運作。我們将增加JOS核心的功能,為它增加一些資料結構來追蹤使用者程序的一些資訊;建立一個單一使用者的環境,并在其中加載運作一個程式。我們也會使JOS核心處理使用者程序做出的任何系統調用和它導緻的任何異常

核心利用

ENV

資料結構來記錄每一個環境的資訊。目前我們隻建立單一的使用者環境,以後再在此基礎上設計多使用者環境

kern/env.c

中,核心維護以下三個關于環境的全局變量:

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

一旦JOS系統開始運作,

envs

指針指向一個儲存目前系統中所有環境變量的

Env

結構體數組

  • JOS核心最多同時支援

    NENV

    個活躍的環境,并為每一個可能的環境(共

    NENV

    個)申請一個

    Env

    資料結構,存放在

    envs

    數組中
  • JOS核心将所有未使用的

    Env

    結構體放在

    env_free_list

    連結清單中,以便使用者環境的配置設定和回收
  • curenv

    指針指向JOS核心中任意時刻正在執行的環境。當核心啟動、還沒有任何使用者環境運作時,它被初始化為

    NULL

Env

資料結構設計如下:

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

env_tf

:當核心或其他環境執行時,儲存目前未執行的環境的寄存器變量。例如當核心從使用者态切換到核心态運作時,使用者态的重要寄存器将被儲存,以便在回到使用者态運作時恢複它們

env_link

:指向

env_free_list

中該環境的下一個未使用的環境。隻有在這個結構體也未使用時(也在

env_free_list

中時)這個域才有用

env_id

:辨別目前使用此

Env

結構體的環境的唯一值。當一個環境終止、核心将這個環境的

Env

重新配置設定給其他環境後,它們的

env_id

也不相同

env_parent_id

:存放建立此環境的環境的

env_id

env_type

:用于區分一些特殊環境。對于大多數環境,它的值都是

ENV_TYPE_USER

env_status

:該域取值為以下之一:

  • ENV_FREE

    :該

    Env

    結構體未被使用,應該被放在

    env_free_list

  • ENV_RUNNABLE

    :該

    Env

    結構體對應的環境就緒,等待被配置設定到處理器
  • ENV_RUNNING

    :該

    Env

    結構體對應的環境正在運作
  • ENV_NOT_RUNNABLE

    :該

    Env

    結構體對應的環境處于活躍狀态,但此時無法運作:例如他正在等待來自另一個環境的消息
  • ENV_DYING

    :該

    Env

    結構體對應的環境是一個僵屍環境,它将在系統下一次進入核心态時被回收

eng_pgdir

:儲存該

Env

結構體對應的環境的頁表目錄的虛拟位址

與Unix程序相類似,一個JOS環境結合了線程(thread)和位址空間(address space)的概念:線程主要通過儲存寄存器的值(

env_tf

)來定義,位址空間主要通過儲存頁表目錄和頁表(

eng_pgdir

)來定義。要運作一個環境,核心必須為它設定合适的寄存器的值和合适的位址空間

在JOS系統中,環境并沒有在核心中擁有各自獨立的棧。。因為任意時刻隻能有一個環境處于活躍狀态,是以JOS核心隻需要一個核心棧。

配置設定環境數組

mem_init()

補充對

envs

數組的記憶體配置設定。這個過程與配置設定頁面數組

pages

是一樣的

envs = (struct Env *) boot_alloc(NENV*sizeof(struct Env));
memset(envs, 0, NENV*sizeof(struct Env));
           

然後需要建立

envs

的映射關系:虛拟位址為

UENVS

,權限為使用者可讀

建立和運作環境

接下來要編寫運作一個使用者環境的必要代碼。由于現在還沒有檔案系統,是以JOS将一些使用者程式的靜态二進制檔案作為ELF檔案嵌入在核心中,以便被載入和執行

Lab3的

GNUmakefile

obj/user/

目錄下生成一系列二進制檔案,它們通過

-b binary

指令作為原始二進制檔案被連結到核心中

在讀取和運作這些二進制檔案前,我們首先完成使用者環境的初始化

env_init()

函數功能:初始化所有

envs

數組中的

Env

結構體并把它們加入到

env_free_list

連結清單中

注意事項:

env_free_list

中結構體的順序應與

envs

數組相同,即第一次調用

env_alloc()

應該傳回

envs[0]

代碼實作如下:

void
env_init(void)
{
	// Set up envs array
	// LAB 3: Your code here.
	int i;
	
	for (i=NENV-1; i>=0; i--) {
		envs[i].env_status = ENV_FREE;
		envs[i].env_link = env_free_list;
		env_free_list = &envs[i];
	}

	// Per-CPU part of the initialization
	env_init_percpu();
}
           

函數最後調用

env_init_percpu()

配置段式記憶體管理系統,它所做的事包括

  • 重新載入GDT表
  • 初始化資料段寄存器GS、FS(留給使用者資料段使用)、ES、DS、SS(在使用者态和核心态切換使用)
  • 初始化核心的代碼段寄存器CS
  • 初始化LDT表為0

env_setup_vm()

函數功能:為新的環境配置設定頁表目錄,并在新環境的位址空間中初始化與核心相關的部分

相關部分是指:使用者和核心一樣理論上能通路完整的4G虛拟記憶體(這裡假設能通路到4G)。核心已建立UTOP以上的虛拟位址到實體位址的映射,是以核心拷貝一份這個映射到自己的頁表目錄中。對應的實體位址使用者究竟能不能通路靠頁表項中通過權限位控制

代碼實作如下:

static int
env_setup_vm(struct Env *e)
{
	int i;
	struct PageInfo *p = NULL;

	// Allocate a page for the page directory
	if (!(p = page_alloc(ALLOC_ZERO)))
		return -E_NO_MEM;

	// Now, set e->env_pgdir and initialize the page directory.
	//
	// Hint:
	//    - The VA space of all envs is identical above UTOP
	//	(except at UVPT, which we've set below).
	//	See inc/memlayout.h for permissions and layout.
	//	Can you use kern_pgdir as a template?  Hint: Yes.
	//	(Make sure you got the permissions right in Lab 2.)
	//    - The initial VA below UTOP is empty.
	//    - You do not need to make any more calls to page_alloc.
	//    - Note: In general, pp_ref is not maintained for
	//	physical pages mapped only above UTOP, but env_pgdir
	//	is an exception -- you need to increment env_pgdir's
	//	pp_ref for env_free to work correctly.
	//    - The functions in kern/pmap.h are handy.

	// LAB 3: Your code here.
	e->env_pgdir = (uintptr_t *)page2kva(p);
	p->pp_ref++;

	// 使用者和核心一樣能通路到4G的虛拟位址空間,其中
	// UTOP以下的位址空間是使用者自己的
	// UTOP以上的位址空間是核心的
	// 指派一份UTOP以上的核心頁表目錄給使用者目錄頁表,UTOP以下使用者自由發揮
	for (i=PDX(UTOP); i<NPDENTRIES; i++)
		e->env_pgdir[i] = kern_pgdir[i];
	// UVPT maps the env's own page table read-only.
	// Permissions: kernel R, user R
	e->env_pgdir[PDX(UVPT)] = PADDR(e->env_pgdir) | PTE_P | PTE_U;

	return 0;
}
           

和核心頁表目錄一樣這裡填寫了

UVPT

的目錄項,以便使用者程序在使用者空間通路到各頁面的頁表項,查詢到頁面的權限。具體在lab4中講到,原理也可以直接參考MIT-JOS系列:使用者态通路頁表項詳解

region_alloc()

函數功能:為環境配置設定

len

位元組的實體記憶體并映射到使用者的虛拟位址空間

  • 不需要對配置設定的空間初始化
  • 配置設定的頁使用者和核心具有寫權限
  • 需要對起始位址

    va

    和長度

    len

    進行4K頁面對齊

代碼實作如下:

static void
region_alloc(struct Env *e, void *va, size_t len)
{
	// LAB 3: Your code here.
	// (But only if you need it for load_icode.)
	//
	// Hint: It is easier to use region_alloc if the caller can pass
	//   'va' and 'len' values that are not page-aligned.
	//   You should round va down, and round (va + len) up.
	//   (Watch out for corner-cases!)
	uintptr_t start = ROUNDDOWN(va, PGSIZE);
	uintptr_t end = start + ROUNDUP(len, PGSIZE);
	struct PageInfo *p = NULL;
	int i;
	for (i=start; i<end; i+=PGSIZE) {
		p = page_alloc(0);
		if (!p)
			panic("Out of memory!\n");
		if (page_insert(e->env_pgdir, p, i, PTE_U | PTE_W) == -E_NO_MEM)
			panic("Out of memory!\n");
	}
}
           

load_icode()

函數功能:解析ELF二進制檔案,加載其内容到新環境的使用者位址空間中

  • 每個使用者程序都是一個ELF檔案。像boot loader所做的那樣,從ELF檔案加載使用者程序的初始代碼區、堆棧和處理器辨別位
  • 這個函數僅在核心初始化期間、第一個使用者态環境運作前被調用
  • 函數将ELF檔案中所有可加載的段載入到使用者位址空間中,設定

    e->env_tf.tf_eip

    為ELF檔案頭訓示的入口(虛拟位址),以便它之後能從這裡開始執行程式
  • 清零bss節
  • 映射程式的初始堆棧到一個頁面

代碼實作如下:

特别注意這裡要進行一下頁表目錄位址的更換以便為使用者空間做正确的虛拟位址映射,以及映射完要把頁表目錄換回來

static void
load_icode(struct Env *e, uint8_t *binary)
{
	// Hints:
	//  Load each program segment into virtual memory
	//  at the address specified in the ELF section header.
	//  You should only load segments with ph->p_type == ELF_PROG_LOAD.
	//  Each segment's virtual address can be found in ph->p_va
	//  and its size in memory can be found in ph->p_memsz.
	//  The ph->p_filesz bytes from the ELF binary, starting at
	//  'binary + ph->p_offset', should be copied to virtual address
	//  ph->p_va.  Any remaining memory bytes should be cleared to zero.
	//  (The ELF header should have ph->p_filesz <= ph->p_memsz.)
	//  Use functions from the previous lab to allocate and map pages.
	//
	//  All page protection bits should be user read/write for now.
	//  ELF segments are not necessarily page-aligned, but you can
	//  assume for this function that no two segments will touch
	//  the same virtual page.
	//
	//  You may find a function like region_alloc useful.
	//
	//  Loading the segments is much simpler if you can move data
	//  directly into the virtual addresses stored in the ELF binary.
	//  So which page directory should be in force during
	//  this function?
	//
	//  You must also do something with the program's entry point,
	//  to make sure that the environment starts executing there.
	//  What?  (See env_run() and env_pop_tf() below.)

	// LAB 3: Your code here.
	struct Elf *elf = (struct Elf*)binary;
	if (elf->e_magic != ELF_MAGIC)
		panic("load_icode() error: Not the ELF file.\n");
	
	// 設定程式的入口
	e->env_tf.tf_eip = elf->e_entry;
    // 接下來要将kernel的資料複制到使用者空間的虛拟位址,是以暫用一下使用者頁表目錄以找到正确的虛拟位址
	lcr3(PADDR(e->env_pgdir));

	struct Proghdr *ph, *eph;
	ph = (struct Proghdr*)(binary + elf->e_phoff);
	eph = ph + elf->e_phnum;

	for (; ph<eph; ph++) {
		if (ph->p_type == ELF_PROG_LOAD) {
			if (ph->p_filesz > ph->p_memsz)
				panic("load_icode() error: file length > memory length.\n");
			region_alloc(e, ph->p_va, ph->p_memsz);

			// 将elf檔案的段資料直接move到相應的虛拟位址空間
			memmove(ph->p_va, binary+ph->p_offset, ph->p_filesz);
			// 由于filesz<=memsz, 将多出來的memz置0
			memset(ph->p_va+ph->p_filesz, 0, ph->p_memsz-ph->p_filesz);
		}
	}

	// Now map one page for the program's initial stack
	// at virtual address USTACKTOP - PGSIZE.
	region_alloc(e, USTACKTOP-PGSIZE, PGSIZE);
	// LAB 3: Your code here.
    
    // 恢複核心的頁表目錄
	lcr3(PADDR(kern_pgdir));
}
           

env_create()

函數功能:調用

env_alloc()

建立一個新的環境,調用

load_icode()

向環境中載入ELF檔案

代碼實作如下:

void
env_create(uint8_t *binary, enum EnvType type)
{
	// LAB 3: Your code here.
	struct Env *env = NULL;
	int err = env_alloc(&env, 0);

	if (err == -E_NO_FREE_ENV)
		panic("env_create() error: no free environment.\n");
	if (err == -E_NO_MEM)
		panic("env_create() error: out of memory.\n");
	
	env->env_type = type;
	load_icode(env, binary);
}
           

env_run()

函數功能:運作一個給定的環境

  • 如果是環境切換(即有環境正在運作):
    1. 如果目前環境

      curenv

      env_status

      ENV_RUNNING

      ,設定它為

      ENV_RUNNABLE

    2. 設定

      curenv為

      新的環境
    3. 設定新的環境的

      env_status

      ENV_RUNNING

    4. 更新

      env_runs

      計數
    5. 利用

      lcr3()

      切換位址空間
  • 利用

    env_pop_tf()

    恢複環境的重要寄存器并進入使用者模式

代碼實作如下:

void
env_run(struct Env *e)
{
	// Step 1: If this is a context switch (a new environment is running):
	//	   1. Set the current environment (if any) back to
	//	      ENV_RUNNABLE if it is ENV_RUNNING (think about
	//	      what other states it can be in),
	//	   2. Set 'curenv' to the new environment,
	//	   3. Set its status to ENV_RUNNING,
	//	   4. Update its 'env_runs' counter,
	//	   5. Use lcr3() to switch to its address space.
	// Step 2: Use env_pop_tf() to restore the environment's
	//	   registers and drop into user mode in the
	//	   environment.

	// Hint: This function loads the new environment's state from
	//	e->env_tf.  Go back through the code you wrote above
	//	and make sure you have set the relevant parts of
	//	e->env_tf to sensible values.

	// LAB 3: Your code here.
	if (curenv != NULL && curenv->env_status == ENV_RUNNING) {
		curenv->env_status = ENV_RUNNABLE;
	}
	curenv = e;
	curenv->env_status = ENV_RUNNING;
	curenv->env_runs++;

	// 設定cr3中的頁表目錄位址:位址為實體位址
	lcr3(PADDR(curenv->env_pgdir));
	env_pop_tf(&curenv->env_tf);

	// panic("env_run not yet implemented");
}
           

env_run()

這個函數是永不傳回的(不是指沒有傳回值,而是指它跳轉去執行别的代碼了,而且永遠不會回到這裡了),它利用

env_pop_tf()

函數設定新環境的重要寄存器,然後進入使用者環境執行代碼,是以

env_pop_tf()

必須放在

env_run()

的最後一行執行。

env_pop_tf()

的代碼如下:

// 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 */
}
           

env_pop_tf()

tf

的内容視為棧,目前棧頂設定為

tf

的第一個字段,pop過程能順序通路

tf

中的每個資料,從逐個出棧

tf

裡面的資料到相應的寄存器

  1. movl %0,%%esp

    将目前棧指針指向輸入變量

    tf

  2. popal

    恢複所有

    r32

    寄存器,即

    tf.PushRegs

    裡的東西
  3. pop出

    tf_es

    指派到

    es

  4. pop出

    tf_ds

    指派到

    ds

  5. 跳過

    tf_trapno

    tf_err

    ,使

    esp

    指向

    tf_eip

  6. 調用

    iret

    ,跳轉到使用者程序執行代碼

    調用

    iret

    時:從

    esp

    指向的棧中順序出棧

    eip, cs, eflags(标志寄存器), esp, ss

    指派到相應寄存器,然後程式跳轉到

    cs:eip

    處繼續執行,是以這個函數正常狀态下不會傳回

使用者環境小結

至此為止,進入運作一個新環境的函數調用如下:

  • start

    (

    kern/entry.S

    )
  • i386_init

    (

    kern/init.c

    )
    • cons_init

    • mem_init

    • env_init

    • trap_init

      (還未實作)
    • env_create

    • env_run

      • env_pop_tf

啟動核心,建立并進入一個環境的過程大約如下:

  1. 啟動核心,開啟分頁,設定棧區(

    kern/entry.S

    ,之前lab1做的)
  2. 初始化.bss段,初始化一系列硬體(

    cons_init

    ,之前lab1做的)
  3. 虛拟記憶體初始化(

    mem_init

    ,之前lab2做的,lab3剛剛對其進行補充:增加初始化envs數組)
    1. 用CMOS檢測可用的實體記憶體
    2. 為kernel的頁表目錄配置設定記憶體,将頁表目錄

      kern_pgdir

      作為頁表插入到頁表項

      UVPT

      處(以便核心以外的環境在

      UVPT

      處能夠查找到自己的頁表目錄,在lab4中會詳細講到)
    3. 初始化

      pages

      數組,将實體記憶體以頁為機關記錄到

      pages

      數組中并利用

      page_free_list

      管理空閑頁面,編寫頁表配置設定、釋放、映射的相關函數
    4. 完成實體記憶體前256M的映射,與此同時填寫了頁表目錄和二級頁表,并賦予頁面相應權限
    5. 設定

      cr3

      kern_pgdir

      的實體位址,并設定

      cr0

      的标志
  4. 為所有可能的環境進行初始化(

    env_init

    1. 初始化

      envs

      數組中的每一項狀态為

      ENV_FREE

      ,并放入未使用的環境

      env_free_list

      連結清單中
    2. 加載GDT表和初始化段描述子
  5. 中斷設定和異常處理(

    trap_init

    ,還沒做)
  6. 建立一個新環境(

    env_create

    1. 調用

      env_alloc()

      初始化一個環境:這個函數不是由我們動手編寫的,但調用了我們編寫的一些函數
      • env_free_list

        拿出一個未被使用的

        Env

        結構
      • 利用

        env_setup_vm()

        初始化新環境的虛拟位址空間:
        • 為新環境配置設定一頁記憶體作為頁表目錄
        • 将其位址填寫到新環境的

          env_pgdir

          域中
        • 拷貝核心頁表目錄中

          UTOP

          以上部分(核心位址空間映射情況)到新環境的頁表目錄
        • 将新的頁表目錄作為頁表插入到新環境目錄頁表項的

          UVPT

      • 為新環境生成一個唯一辨別

        env_id

      • 初始化新環境的其他域:

        env_parent_id

        env_type

        env_status

        env_runs

      • 初始化新環境的段寄存器

        env_tf.tf_ds/es/ss/cs

        關聯目前GDT表,初始化棧指針

        env_tf.tf_esp

        指向

        USTACKTOP

      • 修改

        env_free_list

        指向下一個未被使用的

        Env

        結構
    2. 調用

      load_icode()

      為新環境加載可執行二進制檔案,這個二進制是進入新環境後執行的程式:
      • 以elf格式讀取二進制的程式執行入口

        e_entry

        ,并用這個入口設定新環境的

        env_tf.tf_eip

        ,讓新環境能夠從二進制的入口開始執行程式
      • 利用

        region_alloc()

        為類型為

        ELF_PROG_LOAD

        的節配置設定記憶體并映射到

        p_va

        訓示的位置
      • 利用

        memmove()

        将這些節長度為

        p_filesz

        的檔案内容移動到

        p_va

        訓示的位置
      • 由于

        p_filesz<=p_memsz

        ,利用

        memset()

        填充兩者之間的空缺
      • 注意:此處操作的

        p_va

        是新環境的位址空間,有别于kernel的位址空間,是以需要在操作之前先臨時把

        cr3

        設定為新環境的頁表目錄位址

        e->env_pgdir

        ,操作結束後再恢複到核心的頁表目錄位址
    3. 為新環境配置設定一頁作為其棧區,映射到虛拟位址

      USTACKTOP-PGSIZE

      • 這裡我尋思着

        USTACKTOP-PGSIZE

        是在

        UTOP

        底下,也屬于使用者記憶體區,也得在使用者位址空間下執行?
  7. 進入一個新環境,執行其程式(

    env_run

    1. 若目前有環境在運作,設定該環境的

      env_status

      ENV_RUNNABLE

    2. 設定目前環境指針

      curenv

      指向新環境
    3. 修改新環境的運作狀态為

      ENV_RUNNING

      ,運作次數

      env_runs++

    4. 修改

      cr3

      為新環境的頁表目錄位址
    5. 調用

      env_pop_tf()

      進入新環境

代碼完成到這裡,編譯并啟動qemu運作後,系統就應該能夠順利進入使用者環境并執行

hello

這個二進制檔案。由于目前還沒實作對中斷和異常的處理,是以它将在

hello

進行系統調用

int $0x30

時出錯(這行能在

hello.asm

中找到)。JOS此時尚未設定硬體允許從使用者空間轉換到核心态,當CPU發現這個它沒辦法處理這個中斷時,它引發一個異常;當它又發現它沒辦法處理這個異常時,它又引發了一個異常;但它發現它還是沒辦法處理這個異常,隻好放棄,是以最終引發了“三重異常”,在qemu中得到

Triple fault

的輸出。

檢視

hello.asm

,我們可以在中斷處設定斷點

b *0x800b44

,如果程式能夠執行到這個位置沒有異常,就說明之前編寫的代碼是正确的:

gdb:

The target architecture is assumed to be i8086
[f000:fff0]    0xffff0:	ljmp   $0xf000,$0xe05b
0x0000fff0 in ?? ()
+ symbol-file obj/kern/kernel
(gdb) b *0x800b44
Breakpoint 1 at 0x800b44
(gdb) c
Continuing.
The target architecture is assumed to be i386
=> 0x800b44:	int    $0x30

Breakpoint 1, 0x00800b44 in ?? ()
(gdb) 
           

qemu:

[email protected]:~/1work/MIT-JOS/lab$ make qemu-nox-gdb
***
*** Now run 'make gdb'.
***
qemu-system-i386 -nographic -drive file=obj/kern/kernel.img,index=0,media=disk,format=raw -serial mon:stdio -gdb tcp::26000 -D qemu.log  -S
6828 decimal is 15254 octal!
Physical memory: 131072K available, base = 640K, extended = 130432K
check_page_alloc() succeeded!
check_page() succeeded!
check_kern_pgdir() succeeded!
check_page_installed_pgdir() succeeded!
[00000000] new env 00001000

           

接下來,在gdb中輸入

si

再往下執行一步,将能在qemu中收到

Triple fault

gdb:

Breakpoint 1, 0x00800b44 in ?? ()
(gdb) si
=> 0x800b44:	int    $0x30

Breakpoint 1, 0x00800b44 in ?? ()
(gdb) 
           

qemu:

[00000000] new env 00001000
EAX=00000000 EBX=00000000 ECX=0000000d EDX=eebfde88
ESI=00000000 EDI=00000000 EBP=eebfde60 ESP=eebfde54
EIP=00800b44 EFL=00000092 [--S-A--] CPL=3 II=0 A20=1 SMM=0 HLT=0
ES =0023 00000000 ffffffff 00cff300 DPL=3 DS   [-WA]
CS =001b 00000000 ffffffff 00cffa00 DPL=3 CS32 [-R-]
SS =0023 00000000 ffffffff 00cff300 DPL=3 DS   [-WA]
DS =0023 00000000 ffffffff 00cff300 DPL=3 DS   [-WA]
FS =0023 00000000 ffffffff 00cff300 DPL=3 DS   [-WA]
GS =0023 00000000 ffffffff 00cff300 DPL=3 DS   [-WA]
LDT=0000 00000000 00000000 00008200 DPL=0 LDT
TR =0028 f018eb60 00000067 00408900 DPL=0 TSS32-avl
GDT=     f011b300 0000002f
IDT=     f018e340 000007ff
CR0=80050033 CR2=00000000 CR3=003bc000 CR4=00000000
DR0=00000000 DR1=00000000 DR2=00000000 DR3=00000000 
DR6=ffff0ff0 DR7=00000400
EFER=0000000000000000
Triple fault.  Halting for inspection via QEMU monitor.
           

進行中斷和異常

見下一篇部落格MIT-JOS系列6:使用者環境(二)

繼續閱讀