Long long time ago, I finished Lab2. And now, let me face Lab3數據結構
目標:創建用戶環境,能夠追蹤進程的運行狀況,能夠建立一個新的用戶環境。也要完成系統調用和可能引起的異常架構
在這門課看來,環境和進程是能夠對等的,都指程序運行期間的抽象。不直接叫進程是由於jos中實現的系統調用和UNIX是有差異的函數
描述環境的數據結構線程
首先是ID,定義一個新的類型,32位整型的,使用最低的10位來表示進程ID,也就是最多支持1024個進程/線程3d
進程的狀態有如下幾種指針
Env數據結構能夠完整的描述一個進程code
其中EnvType就一種blog
這個數據結構基本上保存了進程調度與運行所須要的一切,首先是寄存器的全部狀態,進程ID, 父進程ID,進程類型和狀態,以及很是重要的頁表目錄的地址進程
JOS的進程描述符仍是相對簡陋的,由於如今的JOS不支持多個進程,也不支持多核,在XV6中則有支持ip
進程描述符鏈表
進程描述符是在系統啓動的時候所有生成好了的
將全部的進程描述符所有連成一個鏈表,這樣能夠省去不少新建和釋放進程描述符的工做
這個和初始化內核區域的頁描述符是很是相似的,有了前面的代碼,這裏也就好說多了
在建立了這部分數據結構以後,須要注意的是,這些描述符屬於內核的數據結構了,須要將他們映射到特定的虛擬內存位置上,而且設定爲用戶可讀的
建立於運行進程
因爲如今尚未創建文件系統,因此說加載的可執行文件是嵌入到內核中的
在進行編譯連接的時候,鏈接器直接把一個Elf文件連接進了內核的可執行文件中
前面已經爲進程描述符表分配了內存空間,如今要初始化這些描述符,就是將全部的描述符的進程id置位0,狀態置位free,而後依次的放入到空閒列表中,初始化的工做在循環開始前的memset就直接完成了
就是要爲當前的進程分配一個頁,用來存放頁表目錄,同時將內核部分的內存的映射完成
全部的進程,不管是內核仍是用戶,在虛地址UTOP之上的內容都是同樣的。
而對於內核而言,UTOP之上的映射已經在LAB2中完成了,以下所示
這個函數中完成了虛地址UPAGES和UVPT和內核棧的映射
再看一下內存圖
其中的UVPT都是存放頁表目錄的地方,對於內核而言,是存放內核頁表目錄的地方,而對於用戶是存放用戶頁表目錄的地方
而UPAGES是存放內核頁表的地方
因此對應了那句話:UTOP之上的映射都是同樣的
下面看看實際函數是怎麼實現的
首先申請一個物理頁,而後將其做爲頁表目錄的所在,注意虛地址和實地址的轉換,由於在UTOP之上都是同樣的,因此能夠直接把kern_pgdir的內容所有拷貝過來
可是惟獨UVPT這個地方是不同的,由於要放的是本身的頁表目錄
就是根據給定的也表目錄,申請長度爲len的內存,並將其映射到虛地址va上去
首先須要將va和len進行對齊,而後將申請到的頁插入到頁表目錄中去
這個函數是但願將以前直接連接進內核的可執行二進制文件取出來執行
那麼首先要作的事情,就是解析elf文件的文件頭和各段的程序頭,而後根據程序頭文件中的信息,將指定字節的信息拷貝指定的虛地址處
可是這裏須要注意的是:
這裏的拷貝到指定的虛地址處,是指用戶空間的虛地址,而不是內核空間的虛地址,因此還須要用lcr3函數加載用戶空間的頁表目錄才能將地址轉換爲用戶空間地址
而載入elf文件並開始執行的程序段在boot/main.c中有,能夠參考那部分代碼
這裏的memsz和filesz不一致的緣由,是由於在編譯以後,elf文件中的bss段中存在一些沒有被初始化的靜態變量,這些變量不佔用文件存儲空間,可是在實際載入以後會佔用內存空間
注意上面還須要初始化用戶棧
建立一個新的進程須要作的事情基本都在這裏了
首先是申請一個進程描述符,調用前面的env_setup_vm函數來完成頁表目錄的設置和內核區域的映射,而後將制定的二進制文件載入到內存中來
先看env_alloc函數
而後就是env_create函數
最後一句env_pop_tf函數,就是將當前進程的trapframe經過彈棧的形式,切換當前的運行環境
前面這一部分就是進程建立的整個流程,能夠參考下面的調用關係圖
可是如今這個系統仍是跑不起來,由於尚未異常處理機制
當系統啓動完成,會加載連接到內部的那個程序,而後執行,可是由於這個程序內部會調用int指令,因此會產生中斷,可是尚未創建任何容許從用戶空間到內核空間的方式,因此會產生一次保護異常,而後發現保護異常也處理不了,而後又發生了一次異常,直到發生了三次保護異常,CPU就會重置
爲了可以讓在中斷或者異常發生的時候,當前運行的代碼不會隨機的選擇如何進入內核或者怎樣進入內核,而是由內核精細的控制的,基本上基於如下兩種方式
指的是用來暫時存儲引起中斷或者異常的進程的狀態。
當發生了終端或者異常的時候,若是發生了從用戶態到內核態的轉換,也須要切換運行棧。那麼任務狀態段(task state segment)指明瞭這個棧的段選擇子和地址。處理器會將各類寄存器和錯誤碼都壓入到新的棧中。而後從中斷描述符中加載CS EIP等,而後讓ESP和SS指向新的棧
在ucore中,入口向量是直接生成的,就是把所有的256項都列出來,可是jos不是這麼作的,在trapentry.S內先定義了兩個宏,而後利用這兩個宏來定義相應中斷的入口
給定一個全局函數名和中斷號,而後所作的就是和ucore中相似的了,若是須要壓錯誤碼的話,由CPU來完成這件事,可是若是沒有錯誤碼的話,就壓入一個0,這樣能夠保證結構體trapframe得結構保持一致
下面是利用上面給定的宏定義的針對特定中斷的處理例程
全部的中斷,都是要先壓入錯誤碼,而後壓入中斷號,接下來的事都是同樣的了,就是繼續壓棧,在棧上造成一個結構體,以esp爲指針
這裏的trapframe的定義與ucore中稍有不一樣
trapframe中少了fs和gs
而後就是在trap.c中初始化中斷向量表了
注意斷點和系統調用的權限設定,SETGATE的最後一個參數表示的是引起該中斷須要的特權級,很明顯,系統調用和斷點是能夠在用戶態下引起的,而其餘的則是由於錯誤而陷入到了內核中