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