JOVANA
Explore Library Glossary Getting Started Three Levels Fields How it works Mission
Join the mission
All guides

Windows、macOS、Android 與即時系統

上一篇導覽走過了 Unix 與 Linux 這一家;這一篇走的是真實世界的另一半。Windows、macOS、Android,以及躲在汽車與心律調節器裡那些小小的作業系統,各自對「核心要怎麼搭、又要為什麼而優化」做了非常不同的選擇。把它們並排來看,會把課本上「單體式對微核心」那個考題答案,變成一組你在任何地方都認得出來的真實工程取捨。

同一個問題,許多種答案

回到基礎篇,你認識過核心設計上那個大哉問:你要把每一項服務——排程、記憶體、檔案系統、驅動程式——全塞進一支特權巨大的程式裡,還是把特權核心保持得很小、把服務推到普通行程裡去?第一種答案是單體式核心,快,因為一切都只在一次函式呼叫之外,但龐大而緊密耦合。第二種是微核心,更乾淨、更隔離,但每一次服務請求都得用一則跨越保護邊界的訊息來付帳。上一篇談過的 Linux,是徹頭徹尾的單體式。

結果發現,真實系統很少坐在任一個極端。出貨的桌面與行動作業系統裡,主流設計是混合核心:一個務實的中間地帶,它借用微核心那種分層、模組化的想法,卻把效能要緊的部件留在核心空間裡待在一起,好避免在每一次操作上付出傳訊的稅。Windows 與 macOS 都是混合核心,只是從相反的起點走過來的。把「單體式對微核心」這條軸線放在腦中,就是讀懂本篇這四個系統的最佳鏡片——每一個其實都只是那條線上的一個不同的點。

Windows NT:熟悉外表下的層層結構

你每天用的 Windows,跑在一個叫 NT 的地基上,而它的 NT 架構,是一疊精心堆起的層。最底下坐著硬體抽象層,也就是 HAL——一片薄薄的墊片,把機器之間的差異(中斷控制器、計時器、匯流排細節)藏起來,好讓它上面的一切只需寫一次,就能跑在許多種硬體上。HAL 之上是真正的核心,一個小小的層,負責最原始的職責:排程執行緒、處理中斷、跨處理器同步。

在那個核心之上,坐著執行體(Executive),那一大組核心服務——記憶體管理、行程與執行緒、安全、輸入輸出,還有替系統裡每樣東西命名的物件管理員。再高一層,而且至關重要,是環境子系統:一個個獨立的「人格」,向應用程式呈現出一套作業系統介面。正是這種分層、帶子系統味道的設計,讓 NT 成為一個混合核心——它帶著微核心那種模組化的精神,有 HAL、有乾淨的分層,同時又把執行體與核心一起留在特權空間裡,好讓常走的路徑保持快速。

macOS 與 iOS:包在 BSD 裡的 Mach 核心

蘋果的 macOS 與 iOS 都跑在一個叫 XNU 的核心上,而它的故事,是通往同一個混合終點的另一條路。XNU 最裡層的核心是 Mach,最早那批研究用微核心之一,它掌管最深的基本元件:排程、虛擬記憶體,以及以訊息為基礎的行程間通訊。包在 Mach 外頭的,是一層衍生自 BSD(Unix 家族的一員)的東西,它供給程式設計師對一個 Unix 系統會有的期待——行程、訊號、檔案系統介面,還有網路堆疊。Mac 上那些熟悉的 Unix 工具,談話的對象正是那層 BSD。

這裡有個微妙又重要的地方:雖然 Mach 當初是當微核心設計的,XNU 卻不照課本的方式跑它。在一個純微核心裡,那半邊 BSD 會是一個獨立的使用者空間伺服器,隔著一條邊界和 Mach 交換訊息。XNU 反而把 Mach 與 BSD 一起編進同一支核心空間的程式裡,於是一次 Unix 系統呼叫,不必付出真正微核心那種傳訊的成本。那種融合,正是 XNU 被歸為混合核心的原因:微核心的骨頭,單體式的包裝。Windows 靠把一個小核心分層而抵達混合;蘋果則靠把一個微核心和一層 Unix 融起來而抵達——兩條路,同一個務實的中間地帶。

Android:同一個 Linux,上面卻是不同的世界

Android 點出了一件容易被忽略的事:核心,和它上面的使用者空間,是可以分開來選的兩件事。每一支 Android 手機底下,坐著的就是上一篇你讀過的同一個 Linux 核心——單體式,做著排程、記憶體管理與驅動程式的工作,和它在一台 Linux 伺服器上做的一模一樣。讓 Android 感覺起來完全不像一台 Linux 桌機的,是疊在上面的那一切:一個受管理的執行期,在一個受控環境裡跑 app 的程式碼,還有一個豐富的應用框架,定義了 app 怎麼被搭建、被沙箱隔離,以及被允許怎麼和彼此、和硬體對話。

這是一個真正讓人豁然開朗的例子。我們習慣把「一個作業系統」講得好像核心和使用者空間是一件不可分割的東西,但 Android 正是它們並非如此的證明:拿一個原封不動的 Linux 核心,在上面栓一個截然不同的使用者空間,你就得到一個感覺起來像是另一個物種的系統。這一課反過來也成立——你體驗到的「在用 Android」這件事,大半是應用框架的政策,而不是核心的行為,這也是為什麼兩支跑著一模一樣核心的手機,可以表現得如此不同。

即時與嵌入式:當「準時」勝過「快」

上面那四個系統,到頭來都是為一台通用機器上的吞吐量與回應性而優化的。最後這一家,把優先順序徹底顛倒過來。一個即時作業系統,也就是 RTOS,被評判的標準不是它每秒做多少工作,而是它是否在對的時刻、做對的事,而且每一次都如此。關鍵的分野是硬即時對軟即時:一個軟的系統只是「偏好」趕上它的截止期限,而一個硬的系統則「必須」趕上——氣囊控制器或心律調節器裡錯過一個截止期限,那不是慢,那是一場災難。

正因如此,RTOS 看重一個通用系統樂於犧牲掉的性質:可預測性勝過純粹的速度。一個桌面排程器很樂意把平均情況做快,就算偶爾有個工作等上一段長而不可預測的時間也無妨;RTOS 則必須保證,任何關鍵工作被延遲的時間,有一個緊而可知的上界。這正是為什麼即時排程倚靠的是有可證明時序性的、由截止期限驅動的演算法,而不是通用排程器那些把吞吐量最大化的招式。當「遲到」意味著傷害時,「慢一點但永遠準時」就勝過「平均快但偶爾遲」。

即時系統通常住在一個嵌入式作業系統裡:那些微小、常常隱形的軟體,跑在恆溫器、無人機、汽車引擎、洗衣機裡的微控制器上。在這裡,限制又翻了個面——可能只有幾 KB 的記憶體、根本沒有磁碟,所以作業系統必須極其微小,而同一份程式碼可能原封不動地跑上十年,沒有任何使用者去重開它。佔用空間與可靠性,排在功能與彈性之上。一個嵌入式作業系統刻意地做得更少,而那份小,正是它整個的重點。

用取捨來讀懂任何一個作業系統

把這五個系統攤開,一張單一的地圖就浮現了。Linux 是單體式,為吞吐量與硬體的廣度而優化。Windows 與 macOS 都是混合,從相反的方向抵達那個中間——NT 靠在執行體底下分層一個小核心,XNU 靠把 Mach 微核心和一層 BSD Unix 融起來。Android 則靠在一個原封不動的 Linux 上頭擺一個全新的世界,顯示了核心和使用者空間是各自獨立的選擇。而 RTOS 則為了在微小的佔用空間裡保證時序,徹底放棄了吞吐量。沒有哪一個單純地「最好」——每一個都是對一個不同問題的正確答案。