解決復雜性的所有方法都基於一個基本原理:問題分解和各個擊破。也就是說,都是把大 型的、難以解決的問題(或系統)分解成一定數量的復雜度較低的子問題(或子系統), 再根據需要重復這一過程直到每一部分都小到可以解決為止,而各種方法只是這種原理的 一些不同運用而已。 計算機科學中有三種經典的方法比較適合於構建大型系統(我首先必須說明的是,這些定 義都是經過我深思熟慮的討論對象)。 1) 層次(Layer)―將解決方案分解成若干部分,在這些部分中存在一個問題域的最底層 ,它為上層的抽象層次較高的工作提供基礎。較高層建立在其低層基礎之上。OSI和 TCP/IP協議堆棧是眾所周知的層次化軟件設計的成功的例子。操作系統設計的層次化解決 方案可能會包含一個可以直接和硬件通訊的層次,然後在其上提供為更高層提供抽象支持 的層次。這樣更高層就可以對磁盤、網卡等硬件進行訪問,而並不需要了解這些設備的具 體細節。 層次化設計的一個特征是要逐步構建符號集(vocabulary)。隨著層次的升高,符號集的 功能將越來越強大。層次化設計的另外一個特征是完全可以在對其上下層透明的條件下替 換某一層次。在最理想的情況下,移植層次化的操作系統只需要重寫最底層的代碼。純層 次化模型實現的執行速度可能會很慢,因為高層必須(間接的)通過調用一系列連續的低 層才能處理完自己的任務―N層調用N-1層,N-1層調用N-2層,等等,直到實際的工作在0 層被處理完成。接著,結果當然是通過同樣的路徑反向傳遞回來。因此,層次化設計通常 會包含對某些高層直接和某些低層通訊的支持;這樣雖然提高了速度,但是卻使得各個層 次的替換工作更加困難(因為不止一個高層會直接依賴於這個你所希望進行替換的層次) 。 * 模塊(Module)―模塊將具體的一部分功能塊隱藏在抽象的接口背後。模塊的最大特點 是將接口和其實現分離開來,這樣就能夠保證一個模塊可以在不影響其他模塊的情況下進 行改變。這樣也將模塊之間的依賴關系僅僅限定於接口。模塊的范圍是試圖反映求解域內 一些方面的自然的概念性界限。純模塊化的操作系統因而就可能有一個磁盤子系統模塊, 一個內存管理子系統模塊,等等。純模塊化和純層次化的操作系統之間的主要區別是,一 個可以由其他模塊自由調用,模塊間沒有上層和下層的概念(從這個意義上來說,模塊是 廣義的層次。按照純粹的觀點,層次是最多可供一個其它模塊調用的模塊,這個模塊也就 是它的直接上層模塊)。 * 對象(Object)―對象和模塊不同,因為對於初學者來說它們具有不同的問題考慮方式 ,實現的方法也可能各自獨立。但是,就我們當前的目的來說,對象不過是結構化使用模 塊的方法。組件(component)作為對象思想的進一步改進,目前還沒有在操作系統設計 中廣泛使用。即便如此(按照我們的觀點),我們也沒有足夠的理由將其和模塊劃分在不 同的范疇中。 圖3-1強調了內核的層次化的視圖,而且是體系結構無關層次位於體系結構相關層次之上 (更為精確的視圖是在頂層增加一個附加的體系結構相關的層次。這是因為系統調用接口 位於應用程序和內核之間,而且是體系結構相關的)。圖3-2著重強調了更加模塊化的內 核視圖。 從合理的表述層次上看,這兩種觀點都是正確的。但也可以說這兩種觀點都是錯誤的。我 可以用大量的圖片向你證明內核是遵從所有你所能夠指出的設計原則集合的,因為它就是 從眾多思想中抽取出來的。簡單說來,事實是Linux內核既不是嚴格層次化的,也不是嚴 格模塊化的,也不是嚴格意義上的任何類型,而是以實用為主要依據的(實際上,如果要 用一個詞來概括Linux從設計到實現的所有特點,那麼實用就是最確切的)。也許最保守 的觀點是內核的實現是模塊化的,雖然這些模塊有時會為了追求速度而有意跨越模塊的界 限。 這樣,Linux的設計同時兼顧了理論和實際。Linux並沒有忽視設計方法;相反,在Linux 的開發基本思想中,設計方法的作用就像是編譯器:它是完成工作的有力工具。選擇一個 基本的設計原則(例如對象)並完全使用這種原則,不允許有任何例外,這對於測試該原 則的限制,或者構建以說明這些方法為目的的教學系統來說都是一個不錯的方法。但是如 果要用它來達到Linux的設計目標則會引起許多問題。而且Linux的設計目標中也並不包括 要使內核成為一個完全純化的系統。Linux開發者為了達到設計目標寧願違背妨礙目標實 現的原則。 實際上,如果對於Linux來說是正確的,那麼它們對於所有最成功的設計來說都是正確的 。最成功、應用最廣泛的實際系統必然是實用的系統。有些開發人員試圖尋找功能強大的 可以解決所有問題的特殊方法。他們一旦找到了這種方法,所有的問題就都迎刃而解了。 像Linux內核一樣的成功設計通常需要為系統的不同部分和描述上的不同層次使用不同的 方法。這樣做的結果可能不是很清晰,也不是很純粹,但是這種混合產物比同等功能的純 粹系統要強大而且優秀得多。 Linux大部分都是單內核的 操作系統內核可能是微內核,也可能是單內核(後者有時稱之為宏內核Macrokernel)。 按照類似封裝的形式,這些術語定義如下: * 微內核(microkernel)―在微內核中,大部分內核都作為獨立的進程在特權狀態下運 行,它們通過消息傳遞進行通訊。在典型情況下,每個概念模塊都有一個進程。因此,如 果在設計中有一個系統調用模塊,那麼就必然有一個相應的進程來接收系統調用,並和能 夠執行系統調用的其他進程(或模塊)通訊以完成所需任務。 在這些設計中,微內核部分經常只不過是一個消息轉發站:當系統調用模塊要給文件系統 模塊發送消息時,消息直接通過內核轉發。這種方式有助於實現模塊間的隔離(某些時候 ,模塊也可以直接給其他模塊傳遞消息)。在一些微內核的設計中,更多的功能,如I/O 等,也都被封裝在內核中了。但是最根本的思想還是要保持微內核盡量小,這樣只需要把 微內核本身進行移植就可以完成將整個內核移植到新的平台上。其他模塊都只依賴於微內 核或其他模塊,並不直接直接依賴硬件。 微內核設計的一個優點是在不影響系統其他部分的情況下,用更高效的實現代替現有文件 系統模塊將會更加容易。我們甚至可以在系統運行時將開發出的新系統模塊或者需要替換 現有模塊的模塊直接而迅速地加入系統。另外一個優點是不需要的模塊將不會被加載到內 存中,因此,微內核就可以更有效地利用內存。 * 單內核(monolithic kernel)―單內核是一個很大的進程。它的內部又可以被分為若 干模塊(或者是層次或其他)。但是在運行的時候,它是一個獨立的二進制大映象。其模 塊間的通訊是通過直接調用其他模塊中的函數實現的,而不是消息傳遞。 單內核的支持者聲稱微內核的消息傳遞開銷引起了效率的損失。微內核的支持者則認為因 此而增加的內核設計的靈活性和可維護性可以彌補任何損失。 我並不想討論這些問題,但必須說明非常有趣的一點是,這種爭論經常會令人想到前幾年 CPU領域中RISC和CISC的斗爭。現代成功的CPU設計中包含了所有這兩種技術,就像Linux 內核是微內核和單內核的混合產物一樣。Linux內核基本上是單一的,但是它並不是一個 純粹的集成內核。前面一章所介紹的內核模塊系統將微內核的許多優點引入到Linux的單 內核設計中。(順便提一下,我考慮過一種有趣的情況,就是Linux的內核模塊系統可以 將系統內核轉化成為簡單的不傳遞消息的微內核設計。雖然我並不贊成,但是它仍然是一 個有趣的想法。) 為什麼Linux必然是單內核的呢?一個方面是歷史的原因:在Linus的觀點看來,通過把內 核以單一的方式進行組織並在最初始的空間中運行是相當容易的事情。這種決策避免了有 關消息傳遞體系結構、計算模塊裝載方式等相關工作。(內核模塊系統在隨後的幾年中又 進行了不斷地改進。) 另外一個原因是充足的開發時間的結果。Linux既沒有開發時間的限制,也沒有來自於市 場壓力的發行進度。 所有的限制只有並不過分的對內核的修改與擴充。內核的單一設計 在內部實現了充分的模塊化,在這種條件下的修改或增加都並不怎麼困難。而且問題還在 於沒有必要為了追求尚未證實的可維護性的微小增長而重寫Linux的內核(Linus曾多次特 別強調了如下的觀點:為了這點利益而損耗速度是不值得的)。後面章節中將詳細討論充 足開發時間的效果。 如果Linux是純微內核設計,那麼向其他體系結構上的移植將會比較容易。實際上,有一 些微內核,如Mach微內核,就已經成功地證明了這種可移植性的優點。實際的情況是, Linux內核的移植雖然不是很簡單,但也絕不是不可能的:大約的數字是,向一個全新的 體系結構上的典型的移植工作需要30 000到60 000行代碼,再加上不到20 000行的驅動程 序代碼(並不是所有的移植都需要新的驅動程序代碼)。粗略計算一下,一個典型的移植 大約平均需要50 000行代碼。這對於一個程序員或者最多一個程序小組來說是力所能及的 ,可以在一年之內完成。雖然這比微內核的移植需要更多的代碼,但是Linux的支持者將 會提出,這樣的Linux內核移