xv6中文文档
操作系統(tǒng)的工作是
將計算機的資源在多個程序間共享,并且給程序提供一系列比硬件本身更有用的服務(wù)。
管理并抽象底層硬件,舉例來說,一個文字處理軟件(比如 word)不用去關(guān)心自己使用的是何種硬盤。
多路復(fù)用硬件,使得多個程序可以(至少看起來是)同時運行的。(4)最后,給程序間提供一種受控的交互方式,使得程序之間可以共享數(shù)據(jù)、共同工作。
操作系統(tǒng)通過接口向用戶程序提供服務(wù)。設(shè)計一個好的接口實際上是很難的。一方面我們希望接口設(shè)計得簡單和精準,使其
易于正確地實現(xiàn);另一方面,我們可能忍不住想為應(yīng)用提供一些更加復(fù)雜的功能。解決這種矛盾的辦法是讓接口的設(shè)計依賴
于少量的機制 (mechanism),而通過這些機制的組合提供強大、通用的功能。
本書通過 xv6 操作系統(tǒng)來闡述操作系統(tǒng)的概念,它提供 Unix 操作系統(tǒng)中的基本接口(由 Ken Thompson 和 Dennis Ritchie
引入),同時模仿 Unix 的內(nèi)部設(shè)計。Unix 里機制結(jié)合良好的窄接口提供了令人吃驚的通用性。這樣的接口設(shè)計非常成功,
使得包括 BSD,Linux,Mac OS X,Solaris (甚至 Microsoft Windows 在某種程度上)都有類似 Unix 的接口。理解 xv6 是
理解這些操作系統(tǒng)的一個良好起點
如圖0-1所示,xv6 使用了傳統(tǒng)的內(nèi)核概念 - 一個向其他運行中程序提供服務(wù)的特殊程序。每一個運行中程序(稱之為進程)都擁有包含指令、數(shù)據(jù)、棧的內(nèi)存空間。指令實現(xiàn)了程序的運算,數(shù)據(jù)是用于運算過程的變量,棧管理了程序的過程調(diào)用。
進程通過系統(tǒng)調(diào)用使用內(nèi)核服務(wù)。系統(tǒng)調(diào)用會進入內(nèi)核,讓內(nèi)核執(zhí)行服務(wù)然后返回。所以進程總是在用戶空間和內(nèi)核空間之間交替運行。
內(nèi)核使用了 CPU 的硬件保護機制來保證用戶進程只能訪問自己的內(nèi)存空間。內(nèi)核擁有實現(xiàn)保護機制所需的硬件權(quán)限
(hardware privileges),而用戶程序沒有這些權(quán)限。當一個用戶程序進行一次系統(tǒng)調(diào)用時,硬件會提升特權(quán)級并且開始執(zhí)行一
些內(nèi)核中預(yù)定義的功能。
內(nèi)核提供的一系列系統(tǒng)調(diào)用就是用戶程序可見的操作系統(tǒng)接口,xv6 內(nèi)核提供了 Unix 傳統(tǒng)系統(tǒng)調(diào)用的一部分,它們是:
這一章剩下的部分將說明 xv6 系統(tǒng)服務(wù)的概貌 —— 進程,內(nèi)存,文件描述符,管道和文件系統(tǒng),為了描述他們,我們給出
了代碼和一些討論。這些系統(tǒng)調(diào)用在 shell 上的應(yīng)用闡述了他們的設(shè)計是多么獨具匠心。
shell 是一個普通的程序,它接受用戶輸入的命令并且執(zhí)行它們,它也是傳統(tǒng) Unix 系統(tǒng)中最基本的用戶界面。shell 作為一個
普通程序,而不是內(nèi)核的一部分,充分說明了系統(tǒng)調(diào)用接口的強大:shell 并不是一個特別的用戶程序。這也意味著 shell 是
很容易被替代的,實際上這導(dǎo)致了現(xiàn)代 Unix 系統(tǒng)有著各種各樣的 shell,每一個都有著自己的用戶界面和腳本特性。xv6
shell 本質(zhì)上是一個 Unix Bourne shell 的簡單實現(xiàn)。它的實現(xiàn)在第 7850 行。
一個 xv6 進程由兩部分組成,一部分是用戶內(nèi)存空間(指令,數(shù)據(jù),棧),另一部分是僅對內(nèi)核可見的進程狀態(tài)。xv6 提供
了分時特性:它在可用 CPU 之間不斷切換,決定哪一個等待中的進程被執(zhí)行。當一個進程不在執(zhí)行時,xv6 保存它的 CPU
寄存器,當他們再次被執(zhí)行時恢復(fù)這些寄存器的值。內(nèi)核將每個進程和一個 pid (process identifier) 關(guān)聯(lián)起來。
一個進程可以通過系統(tǒng)調(diào)用 fork 來創(chuàng)建一個新的進程。 fork 創(chuàng)建的新進程被稱為子進程,子進程的內(nèi)存內(nèi)容同創(chuàng)建它的
進程(父進程)一樣。 fork 函數(shù)在父進程、子進程中都返回(一次調(diào)用兩次返回)。對于父進程它返回子進程的 pid,對于
子進程它返回 0??紤]下面這段代碼:
系統(tǒng)調(diào)用 exit 會導(dǎo)致調(diào)用它的進程停止運行,并且釋放諸如內(nèi)存和打開文件在內(nèi)的資源。系統(tǒng)調(diào)用 wait 會返回一個當前進程已退出的子進程,如果沒有子進程退出, wait 會等候直到有一個子進程退出。在上面的例子中,下面的兩行輸出
parent: child=1234 child: exiting可能以任意順序被打印,這種順序由父進程或子進程誰先結(jié)束 printf 決定。當子進程退出時,父進程的 wait 也就返回
了,于是父進程打印:
需要留意的是父子進程擁有不同的內(nèi)存空間和寄存器,改變一個進程中的變量不會影響另一個進程。
系統(tǒng)調(diào)用 exec 將從某個文件(通常是可執(zhí)行文件)里讀取內(nèi)存鏡像,并將其替換到調(diào)用它的進程的內(nèi)存空間。這份文件必
須符合特定的格式,規(guī)定文件的哪一部分是指令,哪一部分是數(shù)據(jù),哪里是指令的開始等等。xv6 使用 ELF 文件格式,第2
章將詳細介紹它。當 exec 執(zhí)行成功后,它并不返回到原來的調(diào)用進程,而是從ELF頭中聲明的入口開始,執(zhí)行從文件中加載
的指令。 exec 接受兩個參數(shù):可執(zhí)行文件名和一個字符串參數(shù)數(shù)組。舉例來說:
這段代碼將調(diào)用程序替換為 /bin/echo 這個程序,這個程序的參數(shù)列表為 echo hello 。大部分的程序都忽略第一個參數(shù),這個參數(shù)慣例上是程序的名字(此例是 echo)。
xv6 shell 用以上調(diào)用為用戶執(zhí)行程序。shell 的主要結(jié)構(gòu)很簡單,詳見 main 的代(8001)。主循環(huán)通過 getcmd 讀取命令行的輸入,然后它調(diào)用 fork 生成一個 shell 進程的副本。父 shell 調(diào)用 wait ,而子進程執(zhí)行用戶命令。舉例來說,用戶在
命令行輸入“echo hello”, getcmd 會以 echo hello 為參數(shù)調(diào)用 runcmd (7906), 由 runcmd 執(zhí)行實際的命令。對于 echo hello , runcmd 將調(diào)用 exec 。如果 exec 成功被調(diào)用,子進程就會轉(zhuǎn)而去執(zhí)行 echo 程序里的指令。在某個時刻 echo 會調(diào)用 exit ,這會使得其父進程從 wait 返回。你可能會疑惑為什么 fork 和 exec 為什么沒有被合并成一個調(diào)用,我們之后將會發(fā)現(xiàn),將創(chuàng)建進程——加載程序分為兩個過程是一個非常機智的設(shè)計。
xv6 通常隱式地分配用戶的內(nèi)存空間。 fork 在子進程需要裝入父進程的內(nèi)存拷貝時分配空間, exec 在需要裝入可執(zhí)行文件時分配空間。一個進程在需要額外內(nèi)存時可以通過調(diào)用 sbrk(n) 來增加 n 字節(jié)的數(shù)據(jù)內(nèi)存。 sbrk 返回新的內(nèi)存的地址。
xv6 沒有用戶這個概念當然更沒有不同用戶間的保護隔離措施。按照 Unix 的術(shù)語來說,所有的 xv6 進程都以 root 用戶執(zhí)行
I/O 和文件描述符
文件描述符是一個整數(shù),它代表了一個進程可以讀寫的被內(nèi)核管理的對象。進程可以通過多種方式獲得一個文件描述符,如打開文件、目錄、設(shè)備,或者創(chuàng)建一個管道(pipe),或者復(fù)制已經(jīng)存在的文件描述符。簡單起見,我們常常把文件描述符指向的對象稱為“文件”。文件描述符的接口是對文件、管道、設(shè)備等的抽象,這種抽象使得它們看上去就是字節(jié)流。
每個進程都有一張表,而 xv6 內(nèi)核就以文件描述符作為這張表的索引,所以每個進程都有一個從0開始的文件描述符空間。
按照慣例,進程從文件描述符0讀入(標準輸入),從文件描述符1輸出(標準輸出),從文件描述符2輸出錯誤(標準錯誤輸出)。我們會看到 shell 正是利用了這種慣例來實現(xiàn) I/O 重定向。shell 保證在任何時候都有3個打開的文件描述符(8007),他們是控制臺(console)的默認文件描述符。
系統(tǒng)調(diào)用 read 和 write 從文件描述符所指的文件中讀或者寫 n 個字節(jié)。 read(fd, buf, n) 從 fd 讀最多 n 個字節(jié)( fd 可能沒有 n 個字節(jié)),將它們拷貝到 buf 中,然后返回讀出的字節(jié)數(shù)。每一個指向文件的文件描述符都和一個偏移關(guān)聯(lián)。 read 從當前文件偏移處讀取數(shù)據(jù),然后把偏移增加讀出字節(jié)數(shù)。緊隨其后的 read 會從新的起點開始讀數(shù)據(jù)。當沒有數(shù)據(jù)可讀時, read 就會返回0,這就表示文件結(jié)束了。
write(fd, buf, n) 寫 buf 中的 n 個字節(jié)到 fd 并且返回實際寫出的字節(jié)數(shù)。如果返回值小于 n 那么只可能是發(fā)生了錯誤。
就像 read 一樣, write 也從當前文件的偏移處開始寫,在寫的過程中增加這個偏移。
下面這段程序(實際上就是 cat 的本質(zhì)實現(xiàn))將數(shù)據(jù)從標準輸入復(fù)制到標準輸出,如果遇到了錯誤,它會在標準錯誤輸出輸
出一條信息。
總結(jié)
- 上一篇: Django之 RESTful规范
- 下一篇: java脏字过滤_脏字过滤