01

作業系統層與資源保護

中介軟體底下的 OS 層做什麼?網路作業系統與分散式作業系統的差別。

先讀原文開場,旁邊就是白話

這是一本英文書。左邊放原文、右邊放白話導讀——你既讀得懂,也順手碰了原文。

原文 · 第 7 章 279 7 OPERATING SYSTEM SUPPORT 7. 2 The operating system layer 7. 4 Processes and threads 7. 5 Communication and invocation 7.
白話導讀

中介軟體底下的 OS 層做什麼?網路作業系統與分散式作業系統的差別。

作業系統在分散式系統中的角色

中介軟體底下的 OS 層做什麼?網路作業系統與分散式作業系統的差別。

STEP 1

深度探秘

中介軟體底下那層在做什麼

作業系統層的任務

分散式系統的核心是資源共享:client 呼叫另一個節點上的資源,靠的是**中介軟體(middleware)**幫忙跨節點溝通。但中介軟體不是憑空運作的,它底下還有一層——作業系統(OS)

任何作業系統的任務,就是把底層的實體資源(處理器、記憶體、網路、儲存)包裝成問題導向的抽象

例如 UNIX、Windows 給你的是檔案而不是磁碟區塊,是 socket 而不是赤裸的網路存取。OS 接管單一節點的硬體,透過 system call 介面把這些好用的抽象端出來。

中介軟體要跑得好,就要求 OS 提供:對實體資源高效又穩固的存取,以及實作各種資源管理政策的彈性

💡
關鍵

OS 把硬體包裝成檔案、socket 等好用抽象,供中介軟體使用。

STEP 2

生活妙喻

網路 OS vs 分散式 OS

兩種截然不同的『管家』

把每台電腦想成一棟房子,作業系統就是房子的管家

網路作業系統(network OS)——像 UNIX、Windows——是「每棟房子各有一位管家」。你可以透過 NFS 之類的分散式檔案系統透明地存取遠端檔案,也能用 ssh 登入別人家跑程式。但關鍵是:每個節點保有自主,各自管自己的行程,沒人統一指揮。這叫多個系統映像

分散式作業系統(distributed OS)則像「整個社區只有一位總管」。使用者完全不必管程式在哪裡跑、資源在哪裡。總管掌控所有節點,會自動把新行程丟到最閒的節點。這叫單一系統映像

現實是:分散式 OS 幾乎沒有普及,大家用的都是網路 OS。

💡
關鍵

網路 OS 各節點自主、多系統映像;分散式 OS 單一映像,但現實罕見。

STEP 3

實用超能力

為什麼分散式 OS 沒成功

為什麼大家不用分散式 OS?

兩個很實際的理由:

  1. 既有應用的包袱:使用者已經在現有軟體上投入大量心血,不會為了效率就換一個跑不動自己應用的新 OS。模擬舊核心又跑不快、維護成本巨大。
  2. 想保有自主:就算在同一個組織裡,大家還是想對自己的機器有掌控權,主要是為了效能。Jones 在寫文件時要好的互動反應,不希望 Smith 的程式把她拖慢。

現實的折衷

元件 提供什麼
網路 OS 讓你跑慣用的單機應用、保有自主
中介軟體 讓你享用分散式系統裡的各種服務
flowchart TD
  A[使用者需求] --> B[自主性]
  A --> C[網路透明資源存取]
  B --> D[網路作業系統]
  C --> E[中介軟體]
  D --> F[可接受的平衡]
  E --> F

網路 OS + 中介軟體的組合,剛好在「自主」與「透明存取」之間取得可接受的平衡。

💡
關鍵

網路 OS 加中介軟體=在自主與透明存取間的實用折衷。

🔆
生活妙喻:作業系統提供抽象 ≈ 餐廳廚房給你菜,而不是生食材

OS 端出檔案、socket 等成品,你不必直接面對磁碟區塊或網路封包。

🔆
生活妙喻:網路 OS vs 分散式 OS ≈ 每棟房子各有管家 vs 整個社區一位總管

前者各節點自主,後者單一指揮;現實世界幾乎都是前者。

本節字彙

Network operating system(網路作業系統)
具網路能力、可存取遠端資源,但各節點保有自主、有多個系統映像的 OS。
🧠 每台機器都是獨立王國,只是彼此能通話。
Distributed operating system(分散式作業系統)
對所有資源呈現單一系統映像、統一掌控全部節點的 OS。
🧠 一個總管管全社區,現實中幾乎見不到。
System call(系統呼叫)
應用程式請求 OS 服務的介面,例如開檔、收送網路資料。
🧠 打電話給 OS 櫃台請它幫忙做事。
你用 ssh 登入同事的機器跑了一個程式,但發現自己機器上的工作排程完全不受影響、兩台機器各管各的。這最能說明你用的是哪種系統?
OS 讓你用 open() 開檔、用 socket 收送資料,而不必直接操作磁碟區塊或網路晶片。這體現了 OS 的什麼任務?
書中指出分散式作業系統幾乎沒有普及,下列哪一項是主要原因?

核心 OS 功能與三大職責

行程、執行緒、記憶體、通訊四大管理員,與封裝、保護、並行三大要求。

STEP 1

深度探秘

核心的四個管理員

核心 OS 功能

把 OS 核心拆開來看,主要由幾個『管理員』組成,各司其職:

  • 行程管理員(Process manager):建立行程、操作行程。一個行程是資源管理的單位,包含一個位址空間與一或多個執行緒。
  • 執行緒管理員(Thread manager):建立、同步、排程執行緒。執行緒是依附在行程上的可排程活動
  • 通訊管理員(Communication manager):負責同一台電腦上、不同行程的執行緒之間的通訊;有些 kernel 也支援跨機通訊。
  • 記憶體管理員(Memory manager):管理實體與虛擬記憶體,並支援高效的資料複製與共享。
  • 監督程式(Supervisor):派送中斷、system call 陷阱與例外,控制記憶體管理單元與硬體快取。在 Windows 中稱為硬體抽象層。

為了可攜性,OS 大多用 C、C++ 等高階語言寫成,把機器相依的部分縮到最底層一小塊。

💡
關鍵

核心由行程、執行緒、記憶體、通訊管理員與監督程式組成。

STEP 2

生活妙喻

資源管理者的三條守則

比喻:圖書館的管理規範

把 kernel 與 server 想成圖書館,書籍就是資源。對任何資源管理者,我們至少要求三件事:

  1. 封裝(Encapsulation):提供好用的服務介面。讀者只要說『我要借這本書』,不必知道書放在哪一排、館內怎麼搬運。實作細節(記憶體、裝置管理)對 client 隱藏。
  2. 保護(Protection):資源要防止非法存取。沒有借閱權限的人不能拿走限閱書;應用行程不能亂碰裝置暫存器。
  3. 並行處理(Concurrent processing):多位讀者可同時使用館藏,管理者要做到並行透明——大家同時用,卻像各自獨享。

怎麼存取這些資源?

client 透過**呼叫機制(invocation mechanism)**存取被封裝的資源,例如對 server 物件做 RMI,或對 kernel 做 system call。

💡
關鍵

資源管理者三守則:封裝、保護、並行處理。

STEP 3

實用超能力

呼叫背後要做的兩件事

一次呼叫,背後忙什麼?

當 client 發出一次呼叫,函式庫、kernel 與 server 可能要合力完成兩類『呼叫相關』任務:

  • 通訊(Communication):把操作的參數與結果在資源管理者之間傳來傳去——可能跨網路,也可能在同一台電腦內。
  • 排程(Scheduling):當操作被呼叫時,必須在 kernel 或 server 內安排處理時機。
flowchart TD
  C[Client 發出呼叫] --> COMM[通訊 傳參數與結果]
  C --> SCHED[排程 安排處理時機]
  COMM --> R[資源管理者執行操作]
  SCHED --> R

為什麼這些劃分很重要

本章接下來會看到:把功能放在 kernel 還是使用者層、放在同行程還是不同行程,會直接影響效能與穩固性。理解這四個管理員與三大職責,是看懂後面所有取捨的地基。

💡
關鍵

呼叫背後要處理通訊(傳資料)與排程(安排時機)兩件事。

🔆
生活妙喻:封裝、保護、並行三守則 ≈ 圖書館的服務窗口、借閱權限、同時開放

好用介面、防止亂拿、多人同用卻互不干擾,正是資源管理者該做的三件事。

🔆
生活妙喻:核心的多個管理員 ≈ 公司各部門主管

行程、執行緒、記憶體、通訊各有主管,監督程式像總務處理突發中斷。

本節字彙

Encapsulation(封裝)
對外提供好用的服務介面,把資源的實作細節藏起來。
🧠 像膠囊把藥包住,你只吞膠囊、不碰內容物。
Invocation mechanism(呼叫機制)
存取被封裝資源的方式,如 RMI 或 system call,不論底層如何實作。
🧠 按下『呼叫鈕』,背後怎麼接通你不用管。
Concurrency transparency(並行透明)
多個 client 同時存取資源時,仍像各自獨享,不互相干擾。
🧠 大家同時用,卻感覺不到別人存在。
一個檔案伺服器讓 client 只能用 read 與 write 兩個操作,而 client 完全不知道資料實際存在哪個磁碟、怎麼搬運。這體現了資源管理者的哪個職責?
下列哪個元件負責『同一台電腦上、不同行程的執行緒之間的通訊』?
多個 client 同時對同一個資料庫伺服器查詢,伺服器要讓大家感覺像各自獨享、互不干擾。這是哪個職責?

保護與核心:使用者模式 vs 監督模式

為什麼需要 kernel?位址空間、特權模式與系統呼叫陷阱如何擋住非法存取。

STEP 1

深度探秘

什麼叫『非法存取』

兩種非法存取

資源要防止非法存取,而威脅不只來自惡意程式——一個有 bug 的善意程式也可能搞壞整個系統。

以一個只有 read 與 write 操作的檔案為例,保護分成兩個子問題:

  1. 權限問題:每個操作只能由有權限的人執行。Smith 擁有讀寫權,Jones 只能讀。若 Jones 想辦法寫了檔案,就是非法存取。(完整解法需要密碼學,留到第 11 章。)
  2. 繞過介面問題(本節重點):行為失常的 client 繞過資源對外公開的操作。例如 Smith 直接動到檔案指標變數,自製一個 setFilePointerRandomly 把指標設成亂數——這是個會破壞檔案正常使用的無意義操作。

怎麼擋住繞過介面?

一種方式是用型別安全語言(如 Sing#、Modula-3):模組除非持有目標的參照,否則無法存取它,也不能亂造指標、只能做被允許的呼叫。另一種方式是用硬體支援——這就需要一個 kernel。

💡
關鍵

非法存取包含越權與繞過介面;後者可用型別安全語言或硬體擋住。

STEP 2

生活妙喻

兩種權限的鑰匙

比喻:保險箱與管理員

把硬體想成一間放滿貴重物的金庫。

  • kernel 是擁有萬能鑰匙的金庫管理員:它從系統啟動就常駐,以完全的存取權限執行,能控制記憶體管理單元、設定處理器暫存器,讓其他人只能用『被允許的方式』碰硬體。
  • 大多數處理器有個模式暫存器,決定能不能執行特權指令。kernel 跑在監督模式(privileged);其他行程則被安排跑在使用者模式(unprivileged)

位址空間:看不見的隔間牆

kernel 還會替每個行程設好位址空間——一組虛擬記憶體範圍,各自帶著唯讀或讀寫等存取權。

一個行程不能存取自己位址空間以外的記憶體。

這就像每個房客只能進自己的房間,牆把彼此隔開,誰也不能闖進別人或金庫管理員的房間亂翻。

💡
關鍵

kernel 以監督模式獨享硬體控制權,並用位址空間隔開各行程。

STEP 3

實用超能力

安全進入 kernel 的唯一通道

怎麼安全地請 kernel 幫忙?

當行程跑應用程式碼時,在自己的使用者層位址空間;當它要跑 kernel 程式碼時,必須切到 kernel 的位址空間。切換只能透過例外——例如中斷或 system call 陷阱(trap)

  • system call 陷阱由機器層的 TRAP 指令實作:它把處理器切到監督模式並換到 kernel 位址空間。
  • 一旦執行 TRAP,硬體強制處理器去跑 kernel 提供的處理函式,確保沒有行程能偷偷奪取硬體控制權。
flowchart LR
  U[使用者模式行程] -->|TRAP 指令| K[切到監督模式與 kernel 位址空間]
  K --> H[執行 kernel 指定的處理函式]
  H -->|返回| U

保護是要付代價的

切換位址空間要花掉許多處理器週期;system call 陷阱也比單純的函式呼叫昂貴得多。這些代價會在後面的呼叫成本分析中反覆出現——安全不是免費的

💡
關鍵

行程只能透過 TRAP 等例外安全進入 kernel,而這個保護有效能代價。

🔆
生活妙喻:監督模式的 kernel ≈ 握萬能鑰匙的金庫管理員

只有 kernel 能完全控制硬體,其他人只能透過它、用被允許的方式存取。

🔆
生活妙喻:位址空間 ≈ 公寓的隔間牆

每個行程只能進自己的房間,牆把彼此與 kernel 隔開,不能擅闖。

🔆
生活妙喻:system call 陷阱 ≈ 唯一的安全閘門

想進 kernel 只能走 TRAP 這道閘門,硬體會強制你照規矩走。

本節字彙

Kernel(核心)
從系統啟動就常駐、以完全特權執行、掌控硬體資源的程式。
🧠 kernel=果核,是系統最核心、最受保護的一塊。
Supervisor mode(監督/特權模式)
可執行特權指令的處理器模式,kernel 在此模式運行。
🧠 supervisor=主管,權限最大。
System call trap(系統呼叫陷阱)
由 TRAP 指令觸發、安全地把控制權轉入 kernel 的機制。
🧠 trap=陷阱,一踩就被硬體強制帶進 kernel。
Smith 沒有去呼叫檔案的 read/write,而是直接動到檔案指標變數,自製了一個 setFilePointerRandomly。這屬於哪種非法存取?
為什麼 kernel 必須以監督模式執行?
一個使用者層行程想請 kernel 幫它讀檔,正確且安全的進入方式是?
02

行程與位址空間

行程由執行環境與執行緒組成;位址空間由不重疊的區域構成,可共享。

行程、執行環境與位址空間

行程由執行環境與執行緒組成;位址空間由不重疊的區域構成,可共享。

STEP 1

深度探秘

行程=執行環境+執行緒

為什麼傳統行程不夠用

1980 年代發現:傳統那種『只跑單一活動』的行程,無法滿足分散式系統與需要內部並行的應用。問題在於——傳統行程讓相關活動之間的共享變得笨拙又昂貴。

解法是把行程的概念升級,讓它能對應多個活動。於是現代的行程由兩部分組成:

行程 = 一個執行環境(execution environment) + 一或多個執行緒(thread)

  • 執行緒是 OS 對『一個活動』的抽象(源自 thread of execution,執行的線)。
  • 執行環境資源管理的單位:一組由 kernel 管理的本地資源,主要包含:
    • 一個位址空間
    • 執行緒同步與通訊資源(如 semaphore、socket);
    • 更高階的資源(如開啟的檔案、視窗)。

執行環境建立與管理成本高,但多個執行緒可以共享它——也就是共享其中所有可存取的資源。換句話說,執行環境就是其中執行緒執行的保護領域

💡
關鍵

現代行程=執行環境(資源與保護領域)+一或多個執行緒。

STEP 2

生活妙喻

罐子裡的蒼蠅

比喻:一只蓋緊的罐子

這是 USENET 上一個有名(雖然有點噁)的比喻:

  • 執行環境=一只蓋緊的玻璃罐,加上裡頭的空氣與食物(資源)。
  • 執行緒=罐子裡的蒼蠅

一開始罐裡有一隻蒼蠅。牠可以生出更多蒼蠅、也能殺掉牠們,子孫亦然。任何蒼蠅都能消耗罐裡的任何資源(空氣或食物)。

  • 蒼蠅們可以排好隊有秩序地取用資源;要是沒這紀律,就會在罐裡互撞——也就是當牠們胡亂搶用同一資源時,產生不可預測的結果。
  • 蒼蠅能對別的罐子裡的蒼蠅送訊息,但沒有蒼蠅能逃出罐子,外面的也進不來

在這個比喻裡,最早的 UNIX 行程就是『一只罐子裡只有一隻不孕的蒼蠅』——單執行緒。

💡
關鍵

執行環境是罐子(保護邊界),執行緒是裡頭可共享資源的蒼蠅。

STEP 3

實用超能力

位址空間的區域與共享

位址空間長什麼樣

位址空間是行程虛擬記憶體的管理單位,很大(常見 2^32,甚至 2^64 位元組),由一或多個**區域(region)**組成,區域之間隔著不可存取的空隙、彼此不重疊。每個區域有:

  • 範圍(最低虛擬位址與大小);
  • 讀/寫/執行權限;
  • 能否向上或向下成長。

經典的 UNIX 位址空間有三區:text(程式碼,唯讀不可改)、heap(堆積,向高位址長)、stack(堆疊,向低位址長)。

為什麼要『不限數量的區域』?

  • 每個執行緒要各自的 stack,方便偵測堆疊溢位。
  • 檔案映射進記憶體(mapped file),當成位元組陣列存取。
  • 共享記憶體區域:背後是同一塊實體記憶體,可被多個位址空間共用。

共享區域的三大用途

用途 好處
函式庫 一份程式碼映射給多個行程共用,省記憶體
Kernel kernel 程式碼映射進每個位址空間同一位置,system call 不必換映射
資料共享與通訊 兩行程共享一塊記憶體比互傳訊息更有效率
💡
關鍵

位址空間由不重疊的區域組成;共享區域可省記憶體、加速通訊。

🔆
生活妙喻:執行環境與執行緒 ≈ 蓋緊的罐子與裡頭的蒼蠅

罐子是保護邊界與共享資源,蒼蠅是活動,逃不出也進不來,但能對別罐送訊息。

🔆
生活妙喻:位址空間的區域 ≈ 公寓裡不同用途的房間

text、heap、stack 各是一間房,中間留走道(空隙)防撞,各有門禁(權限)。

🔆
生活妙喻:共享記憶體區域 ≈ 兩戶人家共用的一面活動牆

同一塊實體記憶體被兩個位址空間映射,改一邊另一邊也看得到。

本節字彙

Execution environment(執行環境)
資源管理單位,含位址空間、同步與通訊資源、開檔等,是執行緒的保護領域。
🧠 想成那只罐子,裝著所有可共享的資源。
Address space(位址空間)
行程虛擬記憶體的管理單位,由不重疊的區域組成。
🧠 你能合法觸及的記憶體地圖。
Shared region(共享記憶體區域)
背後是同一塊實體記憶體、被多個位址空間共用的區域。
🧠 一塊記憶體,兩家共用。
在『罐子與蒼蠅』比喻中,執行環境對應罐子、執行緒對應蒼蠅。『蒼蠅胡亂搶同一資源會互撞、產生不可預測結果』對應到真實系統的什麼現象?
為什麼書中說『現代行程=執行環境+執行緒』比傳統單活動行程更好?
某大型函式庫若分別載入每個用它的行程會浪費大量記憶體。OS 怎麼用位址空間的特性解決?

建立新行程與行程配置政策

選主機(轉移政策、位置政策、負載分享)與建立執行環境兩件事。

STEP 1

深度探秘

建立行程的兩件事

建立新行程=兩件獨立的事

傳統上建立行程是 OS 提供的不可分割操作(如 UNIX 的 fork 複製一個行程、exec 把自己換成某程式)。但在分散式系統裡,因為要用到多台電腦,建立行程被拆成兩個獨立面向:

  1. 選擇目標主機:新行程要落在哪個節點?例如從一群當作運算伺服器的叢集中挑一台。這是行程配置決策,屬於政策問題。
  2. 建立執行環境(以及裡頭的初始執行緒)。

選主機的政策光譜很廣:從『一律在發起者的工作站跑』到『把負載分散到一群電腦』。Eager 等人把**負載分享(load sharing)**分成兩類政策:

  • 轉移政策(transfer policy):決定新行程要放本地還是遠端,例如看本地節點是輕載還是重載。
  • 位置政策(location policy):一旦決定要轉移,挑哪個節點來放,可能看各節點相對負載、機器架構或特殊資源。

選主機對程式設計師與使用者都是透明的。

💡
關鍵

建立行程=選主機(配置政策)+建立執行環境兩件獨立的事。

STEP 2

生活妙喻

餐廳帶位的各種策略

比喻:餐廳怎麼帶位

把『把新行程放到哪個節點』想成餐廳『帶客人入座』。帶位策略可以這樣分類:

  • 靜態 vs 適應(static vs adaptive)
    • 靜態=按事先設計好的規則,不看現場狀況(例如『A 桌的客人一律帶去 B 區』,或隨機帶去 B–E 區)。
    • 適應=看現場即時狀況(哪一區比較空)用啟發法決定。
  • 集中 / 階層 / 分散
    • 集中=只有一位領班統一帶位。
    • 階層=多位領班排成樹狀,盡量在低層就近決定。
    • 分散=各區服務生彼此交換資訊自己決定。Spawn 系統甚至把節點當成資源的『買方』與『賣方』,搞成市場經濟。
  • 送方發起 vs 收方發起
    • 送方發起=自己太忙(超過門檻)就主動把客人送出去。
    • 收方發起=自己太閒就對外廣告『我這裡有空位』,讓忙的地方把工作丟過來。
💡
關鍵

負載分享政策可分靜態/適應、集中/階層/分散、送方/收方發起。

STEP 3

實用超能力

行程遷移與簡單至上

不只在建立時搬

遷移式(migratory)負載分享系統可以在任何時候搬動負載,而不只在建立行程時。它用的是行程遷移(process migration):把一個正在執行的行程從一個節點搬到另一個節點。

flowchart TD
  A[負載分享系統] --> B[非遷移式 只在建立時決定主機]
  A --> C[遷移式 任何時候都能搬]
  C --> D[行程遷移 搬移執行中的行程]
  D --> E[昂貴且難以普及]

為什麼行程遷移沒普及

雖然有人做出來,但很少實際部署。主因是成本高,而且要從 kernel 內部抽取一個執行中行程的狀態搬到別處,極為困難。

一個重要結論:簡單至上

Eager 等人研究三種負載分享方法後得到一個關鍵啟示:

簡單是任何負載分享方案的重要特質。

因為太複雜的方案,光是收集狀態的開銷就可能抵銷它帶來的好處。寧可簡單有效,也不要複雜得划不來。

💡
關鍵

行程遷移可搬執行中行程但昂貴難普及;負載分享愈簡單往往愈划算。

🔆
生活妙喻:送方發起 vs 收方發起 ≈ 太忙的攤位把客人推開 vs 太閒的攤位喊『這裡有空位』

前者由負載過高者主動轉移,後者由閒置者主動招攬工作。

🔆
生活妙喻:集中/階層/分散負載管理 ≈ 一位領班 / 樹狀多領班 / 各服務生自決

決策權集中、分層或完全分散,對應三種負載管理架構。

🔆
生活妙喻:簡單至上原則 ≈ 為省幾塊錢繞遠路反而花更多油錢

過於複雜的方案,收集狀態的開銷可能抵銷它的好處。

本節字彙

Load sharing(負載分享)
把運算工作分散到多個節點,以避免個別節點過載的政策。
🧠 大家分擔工作量,別讓一個人累垮。
Transfer policy(轉移政策)
決定新行程放本地還是遠端的政策。
🧠 要不要把它送出去?
Process migration(行程遷移)
把正在執行的行程從一個節點搬到另一個節點的機制。
🧠 搬家——但搬的是『正在跑』的程式,所以很難。
一個節點偵測到自己負載超過門檻,於是主動把新行程送到別的節點。這屬於哪種負載分享演算法?
為什麼書中說行程遷移雖然被實作過、卻很少實際部署?
Eager 等人研究負載分享後強調『簡單是重要特質』,理由是?

Copy-on-write:聰明的延遲複製

fork 出的子行程如何先共享、寫入才真正複製,省下大量複製成本。

STEP 1

深度探秘

繼承位址空間的兩種方式

新行程的位址空間怎麼初始化

選好主機後,新行程需要一個內容已初始化的位址空間。有兩種做法:

  1. 靜態定義:位址空間格式固定(例如就是 text + heap + stack),依清單建立區域,從可執行檔載入內容或填零。
  2. 參照既有執行環境:以 UNIX fork 為例,子行程實體共享父行程的 text 區域,而 heap 與 stack 則是父行程的副本(範圍與初始內容都一樣)。

這個概念被一般化:父行程的每個區域都可以被子行程繼承或省略;繼承的區域可以是與父共享,或邏輯上從父複製

問題來了:複製很貴

如果『邏輯上複製』真的把每一頁都立刻一頁一頁實體複製,會非常浪費——很多頁可能根本不會被改到。Mach、Chorus 等系統用了一個漂亮的優化:copy-on-write(寫入時才複製)

💡
關鍵

子行程可繼承父行程區域,可共享或邏輯複製;複製可用 copy-on-write 優化。

STEP 2

生活妙喻

先共用一份文件,誰要改才印副本

比喻:辦公室的共用文件

主管把一份報告『複製』給兩位同事 A 和 B。

  • 笨方法:立刻印兩份完整副本。但如果 B 整份都沒改,那份副本就白印了。
  • 聰明方法(copy-on-write):先讓兩人看同一份正本,但貼上『唯讀』標籤。
    • 只要兩人都只是看,就一直共用,半張紙都不必多印。
    • 等到 B 真的要改某一頁時,才針對那一頁印一份副本給 B 改,其他沒改的頁仍共用。

這就是 copy-on-write 的精神:複製是『邏輯上』先成立,實體複製延遲到真正寫入的那一刻、而且只複製被改的那一頁

💡
關鍵

先讓雙方共用唯讀正本,誰要改哪一頁才實體複製那一頁。

STEP 3

實用超能力

page fault 觸發複製的流程

它在硬體層怎麼運作

假設區域 RA(行程 A)被『複製繼承』成行程 B 的 RB:

  1. 一開始兩行程的頁表都指向同一批共享頁框,且這些頁在硬體層被設成唯讀(即使邏輯上可寫)。
  2. 若任一行程想修改資料,硬體會丟出一個**page fault(頁錯誤)**例外。假設是 B 要寫。
  3. page fault 處理函式替 B 配置一個新頁框,把原頁框的資料逐位元組複製過去。
  4. 在其中一個行程的頁表把舊頁框號換成新頁框號(哪個都行),另一個保留舊的。
  5. 兩個對應頁都在硬體層重新設為可寫,然後讓 B 的那條修改指令繼續執行。
flowchart TD
  A[兩行程共享頁框 設為唯讀] --> B[B 嘗試寫入]
  B --> C[硬體觸發 page fault]
  C --> D[配置新頁框並複製內容]
  D --> E[更新頁表 兩頁設回可寫]
  E --> F[B 的寫入繼續執行]

不只用在 fork

copy-on-write 是通用技術:複製大訊息時也會用到它,避免無謂的整塊複製。

💡
關鍵

寫入觸發 page fault,才針對被改的那一頁配置新頁框複製。

🔆
生活妙喻:Copy-on-write ≈ 共用唯讀正本,誰要改才印那一頁

邏輯上先複製、實體複製延到真正寫入時,且只複製被改的那一頁,省下大量無謂複製。

🔆
生活妙喻:page fault 觸發複製 ≈ 想在唯讀文件上動筆,被系統攔下來先幫你印一份

硬體把共享頁設唯讀,一寫就觸發例外,由處理函式幫忙複製後再放行。

本節字彙

Copy-on-write(寫入時複製)
先共享記憶體並設唯讀,待有人寫入時才實體複製被修改的頁。
🧠 不寫不印,要寫才印那一頁。
Page fault(頁錯誤)
存取記憶體頁時觸發的硬體例外,可用來偵測寫入唯讀共享頁等情況。
🧠 踩到沒準備好的頁,硬體舉手喊『錯誤!』。
Page frame(頁框)
對應虛擬記憶體頁的實體記憶體單位。
🧠 頁是邏輯的格子,頁框是它真正住的實體房間。
用 fork 建立子行程後,父子立刻共享記憶體頁框且被設為唯讀,直到某一方寫入才實體複製。這樣做最直接的好處是?
在 copy-on-write 中,為什麼共享的頁框一開始要被設成『唯讀』,即使邏輯上是可寫的?
行程 B 嘗試寫入一個 copy-on-write 的共享頁,接下來正確的流程是?
03

執行緒:並行的引擎

用單執行緒到多執行緒到多處理器的吞吐量推演,理解 I/O 與處理器瓶頸。

執行緒如何提升伺服器吞吐量

用單執行緒到多執行緒到多處理器的吞吐量推演,理解 I/O 與處理器瓶頸。

STEP 1

深度探秘

用一個算式看懂多執行緒的威力

為什麼伺服器要多執行緒

多執行緒的核心目的,是最大化操作之間的並行,讓計算與 I/O 重疊,並能在多處理器上並行運算。對伺服器尤其有用:一個執行緒在等磁碟時,另一個可以繼續服務別的請求,避免伺服器變成瓶頸。

一個經典推演

假設每個請求平均要 2 毫秒處理 + 8 毫秒 I/O(讀磁碟、無快取),且跑在單處理器上:

  • 單執行緒:每個請求週轉時間 2+8=10 毫秒,最多 100 req/s
  • 兩執行緒:一個等 I/O 時另一個處理,吞吐提升。但若都卡在單一磁碟(每次 I/O 8 毫秒且序列化),上限變成 1000/8=125 req/s
  • 加入磁碟快取(75% 命中):平均 I/O 降到約 2 毫秒,理論上限升到 500 req/s;但若快取讓處理時間升到 2.5 毫秒,受處理器限制變成 1000/2.5=400 req/s
  • 改用雙處理器紓解處理器瓶頸:兩執行緒可達 444 req/s,三個以上受 I/O 限制達 500 req/s
💡
關鍵

多執行緒讓計算與 I/O 重疊,能顯著提升伺服器吞吐量。

STEP 2

生活妙喻

一個店員 vs 多個店員

比喻:飲料店的店員

把伺服器想成飲料店,請求是客人,處理=結帳,I/O=去後場等珍珠煮好。

  • 單店員:結完帳就傻站著等珍珠煮好,等完才能服務下一位。客人大排長龍。
  • 多店員(多執行緒):A 店員去等珍珠時,B 店員馬上服務下一位。等待時間被重疊起來,整體出杯速度大增。
  • 但只有一台煮珍珠的鍋(單磁碟):再多店員也得排隊用鍋,瓶頸卡在鍋子上。
  • 裝個保溫櫃放現成珍珠(快取):多數時候直接拿,不用等鍋,速度又上一層。
  • 再加一台收銀機(多處理器):連結帳都能同時做,瓶頸再被推開。

重點:瓶頸會移動——從磁碟到處理器;解一個瓶頸,下一個就浮現。

💡
關鍵

多執行緒像多店員,把等待重疊起來,但瓶頸會在資源間轉移。

STEP 3

實用超能力

client 端也能多執行緒

不只伺服器,client 也受益

執行緒對 client 同樣有用:

  • 一個 client 執行緒不斷產生要送出的結果,另一個執行緒負責做 RMI/RPC。即使遠端呼叫會阻塞呼叫者,第一個執行緒仍能繼續算下一批結果,把結果放進緩衝區,直到緩衝區滿才被擋。
  • 網頁瀏覽器是最好的例子:抓網頁常常很慢,瀏覽器必須同時處理多個網頁/圖片請求,使用者才不會卡在那裡乾等。
flowchart LR
  T1[執行緒一 持續產生結果] --> BUF[緩衝區]
  BUF --> T2[執行緒二 做遠端呼叫]
  T2 --> S[伺服器]

重點回顧

招式 解決什麼瓶頸
多執行緒 讓 I/O 等待與計算重疊
快取 減少昂貴的磁碟 I/O
多處理器 紓解處理器瓶頸、真正並行

多執行緒不是萬靈丹,而是把等待時間變成有用時間的關鍵手段。

💡
關鍵

client 也能用多執行緒重疊計算與遠端呼叫,瀏覽器就是典型例子。

🔆
生活妙喻:多執行緒提升吞吐 ≈ 飲料店多請幾個店員

一人去等珍珠時別人繼續服務客人,把等待重疊起來,整體出杯更快。

🔆
生活妙喻:瓶頸轉移 ≈ 解決排隊的鍋子後,收銀機又成了瓶頸

磁碟瓶頸用快取解掉後,處理器又成限制;解一個下一個就浮現。

🔆
生活妙喻:快取 ≈ 保溫櫃裡放現成珍珠

多數請求直接命中快取,省去昂貴的磁碟 I/O。

本節字彙

Throughput(吞吐量)
單位時間能處理的請求數,常以 req/s 衡量。
🧠 每秒能『吞吐』多少請求。
Bottleneck(瓶頸)
限制整體吞吐量的最慢資源,如磁碟或處理器。
🧠 瓶子最窄處,水流多快由它決定。
Cache hit rate(快取命中率)
請求的資料能在快取中找到的比例,命中率越高 I/O 越少。
🧠 找得到=命中,命中越多越省力。
在單處理器、每請求需 2ms 處理+8ms I/O 且無快取的例子裡,單執行緒伺服器最多能處理多少請求/秒?
把例子從單執行緒改成兩執行緒後,吞吐量被限制在 125 req/s。這個新瓶頸是什麼?
為什麼網頁瀏覽器(client 端)非常需要多執行緒?

伺服器執行緒架構

worker pool、thread-per-request、per-connection、per-object 各有取捨。

STEP 1

深度探秘

四種把請求對應到執行緒的方式

怎麼把『請求』分配給『執行緒』

Schmidt 整理 CORBA ORB 的執行緒架構,這些模式適用於各種伺服器。主要有四種:

  1. Worker pool(工作者池):伺服器啟動時建立固定的一池 worker 執行緒。一個 I/O 執行緒從各 socket 收請求、丟進共享請求佇列,worker 們從佇列取出處理。可加多佇列支援不同優先權。
  2. Thread-per-request(每請求一執行緒):I/O 執行緒為每個請求生一個 worker,處理完即自我銷毀。
  3. Thread-per-connection(每連線一執行緒):client 建連線時建一個 worker,連線關閉時銷毀;期間該連線的多個請求都由它處理。
  4. Thread-per-object(每物件一執行緒):每個遠端物件配一個執行緒,搭配每物件佇列

每種模式在吞吐量、建立成本、佇列競爭、負載均衡之間有不同取捨。

💡
關鍵

worker pool、per-request、per-connection、per-object 是四種主要伺服器執行緒架構。

STEP 2

生活妙喻

餐廳的四種人力配置

比喻:餐廳怎麼安排服務生

  • Worker pool:固定請 5 位服務生,所有點單丟進一張共用待辦清單,誰有空誰接。
    • 缺點:人數固定,尖峰時 5 人不夠用;而且大家搶同一張清單,爭用頻繁。
  • Thread-per-request:每來一張點單就臨時叫一位工讀生處理,做完就讓他下班。
    • 優點:沒人搶清單,吞吐潛力大。缺點:一直叫人、遣散,人事成本高。
  • Thread-per-connection:每來一桌客人配一位專屬服務生,這桌點幾道都他負責,客人走了他才下班。
  • Thread-per-object:每道招牌菜配一位專師傅,點到那道就排他的隊。

per-connection 與 per-object 的人事成本比 per-request 低,但可能出現『某位服務生忙翻、另一位閒著』的負載不均

💡
關鍵

四種架構像餐廳不同人力配置,在成本、爭用與均衡間取捨。

STEP 3

實用超能力

怎麼選?看你的取捨

取捨總表

架構 優點 缺點
Worker pool 簡單、執行緒數可控 不夠彈性、I/O 與 worker 搶共享佇列切換頻繁
Thread-per-request 不爭佇列、吞吐潛力最大 建立與銷毀執行緒的開銷高
Thread-per-connection 執行緒管理開銷低 連線間負載可能不均
Thread-per-object 執行緒管理開銷低 物件間負載可能不均
flowchart TD
  IO[I/O 執行緒收請求] --> Q[共享請求佇列]
  Q --> W1[Worker 一]
  Q --> W2[Worker 二]
  Q --> W3[Worker 三]

實務建議

  • 請求短而量大、想避免建立成本 → 偏好 worker pool
  • 請求處理很重、希望吞吐拉滿且能容忍建立成本 → thread-per-request
  • 連線長且互動多thread-per-connection
  • 不同遠端物件負載差異大 → 視情況用 per-object,但小心忙閒不均。

Schmidt 還討論了這些架構的混合版;下一節的 LRPC 則是另一種『client 執行緒直接進 server』的模型。

💡
關鍵

依請求特性與負載分佈選架構,常在成本、爭用與均衡間取捨。

🔆
生活妙喻:Worker pool ≈ 固定 5 位服務生共用一張待辦清單

誰有空誰接單,簡單但人數固定且大家搶同一張清單。

🔆
生活妙喻:Thread-per-request ≈ 每張單臨時叫工讀生、做完遣散

不爭清單、吞吐高,但一直叫人遣散,人事成本高。

🔆
生活妙喻:負載不均 ≈ 某位服務生忙翻、另一位閒著

per-connection/per-object 省管理成本,但工作可能集中在某些執行緒。

本節字彙

Worker pool(工作者池)
啟動時建立固定一池執行緒、從共享佇列取請求處理的架構。
🧠 一池固定工人,活兒丟進共用籃子。
Thread-per-request(每請求一執行緒)
每個請求生一個執行緒、處理完即銷毀的架構。
🧠 來一單叫一人,做完就解散。
Thread-per-connection(每連線一執行緒)
每條 client 連線配一個執行緒,連線存活期間都由它處理。
🧠 一桌一服務生,這桌走了他才下班。
某伺服器啟動時就建好固定 8 個執行緒,一個 I/O 執行緒把進來的請求放進共享佇列,8 個 worker 從佇列取出處理。這是哪種架構?
thread-per-request 架構的主要缺點是什麼?
thread-per-connection 與 thread-per-object 相較於 thread-per-request,共同優點與共同缺點分別是?

執行緒 vs 行程:成本與保護

為何偏好多執行緒?建立、切換成本,以及 context switch 與快取代價。

STEP 1

深度探秘

同樣能重疊,為什麼偏好執行緒

多執行緒 vs 多行程

你可能會問:用多個單執行緒行程也能達到計算與 I/O 重疊,何必用多執行緒?答案有兩點:

  1. 執行緒建立與管理比行程便宜
  2. 執行緒間共享資源比行程間更有效率,因為它們共享同一個執行環境。

比較兩者的狀態:

  • 執行環境有:位址空間表、通訊介面與開啟的檔案、semaphore 等同步物件、所屬執行緒清單。
  • 執行緒有:排程優先權、執行狀態(如 BLOCKED、RUNNABLE)、BLOCKED 時保存的處理器暫存器、軟體中斷處理資訊、所屬執行環境的識別碼。

四點總結:

  • 在既有行程內建新執行緒,比建一個行程便宜。
  • 更重要的是:在同行程內切換執行緒,比在不同行程的執行緒間切換便宜。
  • 同行程的執行緒能方便高效地共享資料與資源。
  • 但代價是:同行程的執行緒彼此不受保護
💡
關鍵

偏好執行緒,因建立與切換更便宜、共享更高效;代價是彼此不受保護。

STEP 2

生活妙喻

同間辦公室 vs 不同公司

比喻:同事 vs 外部廠商

  • 同行程的多執行緒=同一間辦公室的同事:
    • 多請一位同事(建執行緒)很快、很便宜。
    • 大家共用同一批檔案櫃與白板(共享資源),交接資料只要指一下白板,不必寄信。
    • 換人接手(切換執行緒)只要轉個椅子,幾乎沒成本。
    • :大家在同一空間,一個人亂改白板會害到別人——彼此不受保護
  • 多個行程=不同公司:
    • 開一間新公司(建行程)手續繁瑣昂貴。
    • 要交換資料得正式寄信(訊息傳遞),慢。
    • 但各公司有自己的門禁,互相保護,一家出包不會直接弄壞另一家。

數字感受

Anderson 等人在某架構上量到:建一個 UNIX 行程約 11 毫秒,建一個執行緒約 1 毫秒——差了約十倍。

💡
關鍵

執行緒像同辦公室同事,便宜好共享但互不保護;行程像不同公司,貴又要寄信但互相隔離。

STEP 3

實用超能力

context switch 與快取的隱形成本

切換成本:最關鍵的開銷

切換之所以重要,是因為一條執行緒一生會切換很多次

  • 處理器情境(context)=處理器暫存器(如 program counter)+當前硬體保護領域(位址空間與處理器保護模式)。
  • context switch=切換情境時保存舊暫存器、載入新暫存器;有時還要轉換保護領域(domain transition)
切換種類 相對成本 為什麼
同執行環境、純使用者層 最低 無 domain transition
同執行環境、經 kernel 有 domain transition,但 kernel 已映射進位址空間故仍低
不同執行環境之間 最高 跨位址空間,還有快取代價

aliasing 問題:為什麼跨行程更貴

記憶體管理單元有個 TLB 快取加速虛擬→實體位址轉換。但同一個虛擬位址在不同位址空間可能指向不同實體資料(aliasing 問題)。除非快取項目用情境識別碼標記,否則切換到不同位址空間時,TLB 與快取必須清空(flush)——這就是跨行程切換昂貴的隱形成本。

💡
關鍵

切換成本最關鍵;跨行程切換因 aliasing 需清空 TLB/快取而特別貴。

🔆
生活妙喻:執行緒 vs 行程的共享 ≈ 同辦公室同事用白板交接 vs 不同公司互相寄信

同行程執行緒共享記憶體交接資料極快,跨行程則要訊息傳遞,慢得多。

🔆
生活妙喻:彼此不受保護 ≈ 同辦公室有人亂改共用白板

同行程執行緒共享位址空間,一個出錯會波及其他執行緒。

🔆
生活妙喻:TLB 清空 ≈ 換班時要把白板全部擦掉重寫

切到不同位址空間因 aliasing 問題須清空 TLB/快取,造成額外成本。

本節字彙

Context switch(情境切換)
切換執行緒或進入 kernel 時,保存舊處理器狀態、載入新狀態的動作。
🧠 換人上場前,先把上一棒的狀態記下來。
Domain transition(保護領域轉換)
切換時跨越位址空間或處理器保護模式的轉換,成本較高。
🧠 跨過保護邊界那道牆,要繳過路費。
Aliasing problem(別名問題)
同一虛擬位址在不同位址空間指向不同資料,導致快取需清空。
🧠 同名不同人,快取會認錯,只好全部清掉重來。
既然多個單執行緒行程也能讓計算與 I/O 重疊,為什麼書中仍偏好多執行緒?
同行程的多執行緒共享位址空間,這帶來方便高效的資料共享。它的代價是什麼?
為什麼『切換到不同執行環境的執行緒』比『切換到同執行環境的執行緒』昂貴許多?

Java 執行緒、同步與排程

Java 執行緒生命週期、synchronized monitor 與 wait/notify,搶占式 vs 非搶占式。

STEP 1

深度探秘

執行緒生命週期與同步基本功

Java 執行緒的一生

執行緒程式設計就是並行程式設計,會用到幾個核心概念:競爭條件(race condition)、臨界區間(critical section)、monitor、條件變數、semaphore。

Java 執行緒在同一個 JVM 上建立,初始為 SUSPENDED;呼叫 start() 變成 RUNNABLE 後開始跑物件的 run() 方法。執行緒可設優先權,支援優先權的實作會優先跑高優先權者。執行緒從 run() 返回或被 destroy() 時結束生命。

為什麼需要同步

每條執行緒的區域變數是私有的(各有私有 stack),但靜態變數與物件實例變數不是私有的——大家共享。

回想前面 I/O 與 worker 共用的請求佇列:若多執行緒同時亂動佇列的指標,可能發生競爭條件,導致請求遺失或重複。所以必須協調。

💡
關鍵

Java 執行緒有 SUSPENDED/RUNNABLE 等狀態;共享變數需同步以避免競爭條件。

STEP 2

生活妙喻

只有一把鑰匙的更衣室

比喻:更衣室與排隊牌

synchronized(monitor)=一間只有一把鑰匙的更衣室:

  • 你把 QueueaddTo()removeFrom() 標成 synchronized,就等於規定『同一時間最多一人能進這間更衣室』。其他人想進就得在外面等,於是對佇列的操作自動互斥,不會撞在一起。

wait() / notify()叫號等待機制:

  • worker 發現沒請求可做,就呼叫佇列的 wait(),乖乖去旁邊睡(阻塞並讓出鑰匙)。
  • I/O 執行緒放進一個新請求後,呼叫 notify() 把一位 worker 叫醒回來搶鑰匙。notifyAll() 則叫醒全部。
sequenceDiagram
  participant W as Worker 執行緒
  participant Q as Queue 物件
  participant IO as I O 執行緒
  W->>Q: 沒事做 呼叫 wait 去睡
  IO->>Q: 放入新請求
  IO->>Q: 呼叫 notify
  Q-->>W: 叫醒 W 回來處理

注意:Java 物件的 monitor 只有一個隱含的條件變數,一般 monitor 可有多個。

💡
關鍵

synchronized 像只有一把鑰匙的更衣室提供互斥,wait/notify 像叫號等待。

STEP 3

實用超能力

搶占式 vs 非搶占式排程

兩種排程哲學

  • 搶占式(preemptive):執行緒隨時可能被暫停讓位給別人,即使它還想繼續跑。
  • 非搶占式(non-preemptive,又稱 coroutine):執行緒一直跑,直到它主動呼叫排程系統(如 system call)才可能被換下。
面向 搶占式 非搶占式
競爭條件 隨時可能被打斷,需小心同步 不含排程呼叫的程式段自動是臨界區間,方便避開競爭
多處理器 能善用 不能,因為執行緒互斥獨佔執行
長時間運算 沒問題 需手動插 yield() 讓別人有機會跑
即時應用 較適合 不適合(事件有絕對時限)

與群組/優先權

Java 還能把執行緒分群組:預設一群的執行緒不能管理另一群(安全用途),瀏覽器、伺服器藉此限制 applet/servlet 執行緒的最高優先權,且 applet 無法用 setPriority() 覆蓋管理者設的群組優先權上限。

同步原語還有 join()(等目標執行緒結束)與 interrupt()(提早喚醒等待中的執行緒)。

💡
關鍵

搶占式隨時可被換下、能用多處理器;非搶占式靠主動讓出、程式段天然互斥但無法並行。

🔆
生活妙喻:synchronized monitor ≈ 只有一把鑰匙的更衣室

同一時間只准一人進入,對共享資料的操作自動互斥。

🔆
生活妙喻:wait / notify ≈ 沒事先去睡,有貨來再被叫醒

worker 沒請求就 wait 阻塞,I/O 放入請求後 notify 喚醒,避免空轉。

🔆
生活妙喻:非搶占式排程的臨界區間 ≈ 你不主動讓座就一直坐著

不含排程呼叫的程式段不會被打斷,自然成為臨界區間,但長段需手動 yield。

本節字彙

Race condition(競爭條件)
多執行緒未協調地存取共享資料,導致結果不可預測(如請求遺失或重複)。
🧠 大家搶著改同一份資料,誰先誰後都亂套。
Monitor(synchronized)
保證同一時間最多一條執行緒能進入的同步構造,提供互斥。
🧠 一把鑰匙的房間,進去了別人就得等。
Preemptive scheduling(搶占式排程)
執行緒可在任何時刻被暫停、讓位給其他執行緒的排程方式。
🧠 隨時可能被『搶』下場。
把 Queue 的 addTo() 與 removeFrom() 標成 synchronized,最主要解決什麼問題?
一個 worker 執行緒發現佇列裡沒有請求可處理,依書中描述它應該怎麼做?
非搶占式(coroutine)排程的一個方便之處是什麼?

執行緒實作:使用者層 vs 核心層

兩種實作的優缺點,以及 scheduler activations 的混合式設計。

STEP 1

深度探秘

誰來排程執行緒

兩種執行緒實作

關鍵問題是:誰知道、誰排程這些執行緒?

  • 核心層執行緒(kernel-level):kernel 原生支援多執行緒,提供建立/管理的 system call,並獨立排程每條執行緒。Windows、Linux、Solaris、Mach、Mac OS X 都是這類。
  • 使用者層執行緒(user-level):有些 kernel 只有單執行緒行程抽象,多執行緒得靠連結到應用程式的函式庫實作。kernel 根本不知道這些執行緒,也無法獨立排程它們。由一個執行緒執行期函式庫自己排程,並利用 kernel 的非阻塞 I/O 與計時器來分時。

使用者層的兩大痛點

當 kernel 不支援多執行緒時,純使用者層實作有幾個問題:

  • 行程內的執行緒無法利用多處理器
  • 一條執行緒若觸發 page fault,會阻塞整個行程與其中所有執行緒。
  • 不同行程的執行緒無法用單一的相對優先權方案統一排程。
💡
關鍵

核心層執行緒由 kernel 獨立排程;使用者層執行緒 kernel 不知情、由函式庫排程。

STEP 2

生活妙喻

公司自己排班 vs 總公司排班

比喻:誰幫你排班

  • 使用者層執行緒部門自己排班
    • 好處:排班很有彈性、可依部門需求客製,調動同部門人員(切換執行緒)不必跑去總公司報備(不需 system call),很便宜;還能養比總公司願意配給的更多人手。
    • 壞處:總公司(kernel)根本不知道你部門有幾個人。一旦有一人去總公司辦事卡住(page fault/阻塞系統呼叫),總公司以為整個部門都停擺,全員被擋;而且部門間無法用統一標準排優先順序,也用不到多個分公司(多處理器)。
  • 核心層執行緒總公司統一排班:每個人總公司都認得、能獨立調度,能分派到不同分公司(多處理器),但每次調動都要報備,較貴

各自優勢一覽

面向 使用者層 核心層
切換成本 低(免 system call) 較高
客製排程 可自由客製 受 kernel 決定
多處理器 不能用 能用
page fault 影響 拖垮整個行程 只擋該執行緒
💡
關鍵

使用者層便宜可客製但會被阻塞拖垮、用不到多處理器;核心層可獨立排程卻較貴。

STEP 3

實用超能力

Scheduler activations:兩全其美

能不能兩全其美?

可以,用混合式設計。Solaris 用階層式排程(使用者層執行緒映射到 kernel 的『輕量行程』),但若一條阻塞在 kernel,映射上去的使用者層執行緒也全被擋——仍不夠彈性。

**Scheduler activations(排程器活化)**更進一步。關鍵洞見:使用者層排程器要的不只是『一組可映射的 kernel 執行緒』,還需要 kernel 通知它與排程相關的事件

  • kernel 負責配虛擬處理器給各行程;數量可隨需求增減。
  • **scheduler activation(SA)**是 kernel 對行程的一次呼叫,通知其排程器某事件發生——這種『從下層 kernel 進入上層程式碼』叫 upcall
  • kernel 會通知四類事件:虛擬處理器已配置、SA 阻塞、SA 解除阻塞、SA 被搶占。使用者層排程器據此把 READY 執行緒分派給目前在跑的 SA。
flowchart TD
  K[Kernel 配虛擬處理器] -->|upcall 通知事件| US[使用者層排程器]
  US -->|把 READY 執行緒指派給 SA| RUN[執行]
  US -->|P idle 或 P needed| K

彈性(政策全在使用者層,kernel 只送事件)又高效(只要有虛擬處理器可跑,就沒有 READY 執行緒會空等)。

💡
關鍵

scheduler activations 用 upcall 通知事件,兼得使用者層彈性與核心層的處理器利用。

🔆
生活妙喻:使用者層 vs 核心層執行緒 ≈ 部門自己排班 vs 總公司統一排班

前者靈活便宜但總公司不知情、一人卡住全員被擋;後者人人被認得能獨立調度但每次報備較貴。

🔆
生活妙喻:page fault 拖垮整個行程 ≈ 一人去總公司卡住,整個部門被當成停擺

kernel 不知道使用者層有多個執行緒,一條阻塞就擋住全行程。

🔆
生活妙喻:upcall(scheduler activation) ≈ 總公司主動打電話通知部門最新狀況

kernel 從下層『往上呼叫』通知事件,讓使用者層排程器即時調整。

本節字彙

User-level threads(使用者層執行緒)
由函式庫實作與排程、kernel 不知情的執行緒,切換便宜但會被阻塞拖累。
🧠 部門私下排班,總公司不知道有幾個人。
Kernel-level threads(核心層執行緒)
由 kernel 原生支援並獨立排程的執行緒,可用多處理器但切換較貴。
🧠 總公司認得每個人,能直接調度。
Scheduler activation / upcall(排程器活化/上呼叫)
kernel 主動呼叫行程通知排程相關事件的機制,結合兩種實作優點。
🧠 下層往上打電話通知,讓上層自己做決定。
一個純使用者層執行緒實作中,某執行緒觸發了 page fault。會發生什麼?
使用者層執行緒實作相較於核心層,最主要的效能優勢是什麼?
Scheduler activations 的核心洞見是什麼?
04

通訊與呼叫效能

kernel 該提供哪些通訊原語?為何中介軟體多半建在 socket 上、協定堆疊的開放與動態組合。

通訊原語、協定與開放性

kernel 該提供哪些通訊原語?為何中介軟體多半建在 socket 上、協定堆疊的開放與動態組合。

STEP 1

深度探秘

kernel 該提供多高階的通訊

呼叫與通訊

本節聚焦在呼叫(invocation)背後的通訊。呼叫是一種構造(如 RMI、RPC、事件通知),目的是讓另一個位址空間的資源執行某操作。我們問四個關於 OS 的問題:提供哪些通訊原語?支援哪些協定、開放程度如何?怎麼讓通訊盡量有效率?怎麼支援高延遲與斷線操作?本節先談前兩個。

通訊原語放哪裡

有些為分散式系統設計的 kernel 提供量身打造的原語。Amoeba 提供 doOperationgetRequestsendReply,並支援群組通訊。

把高階通訊功能放進 kernel 的好處是效率

例如中介軟體若用 TCP socket 做 RMI,每次遠端呼叫要做兩次通訊 system call(socket 的寫與讀);在 Amoeba 上只需一次 doOperation。群組通訊省下的更多。

實務上,多數高階通訊(RPC/RMI、事件通知、群組通訊)是由中介軟體而非 kernel 提供,因為在使用者層開發這類複雜軟體比在 kernel 簡單得多。

💡
關鍵

高階原語放 kernel 有效率,但實務上多由中介軟體在使用者層提供。

STEP 2

生活妙喻

通用插座 vs 客製專線

比喻:為什麼大家用 socket

中介軟體大多建在 socket 上(多用 TCP,有時用 UDP),主要理由是可攜性與互通性

  • 想像 socket 是各國通用的萬國插座。UNIX、Windows 都提供相似的 socket API,能接上 TCP/UDP。中介軟體要在盡可能多的 OS 上跑,用通用插座最保險。
  • 1980 年代有些研究 kernel 自製了調校過的 RPC 專用協定(Amoeba RPC、VMTP、Sprite RPC),就像客製專線——效率好,但只在自家研究環境通用,走不出去

所以 Mach 3.0、Chorus、L4 等 kernel 乾脆完全開放協定選擇:kernel 只負責本地行程間的訊息傳遞,把網路協定處理交給上層的 server。

因為大家天天要上網,TCP/UDP 相容性對幾乎所有連網裝置都是必備的。

💡
關鍵

中介軟體偏好 socket 是為了可攜與互通;客製 kernel 協定雖快卻難普及。

STEP 3

實用超能力

靜態安裝 vs 動態組合協定

還要能接納新協定

OS 不只要支援 TCP/UDP,還得讓中介軟體能用上新的低階協定——例如紅外線(IrDA)、藍牙、IEEE 802.11,而且最好不必改應用程式

協定通常排成層的堆疊。整合新層有兩種做法:

  • 靜態整合:把某層(如 IrDA)當成永久安裝的協定驅動程式。
  • 動態協定組合(dynamic protocol composition):協定堆疊可即時拼裝,配合應用需求與當前可用的實體連線。
flowchart TD
  APP[應用需求與當前連線] --> DC[動態協定組合]
  DC --> S1[選用無線層或乙太層]
  DC --> S2[選用客製請求回覆協定]

真實情境

  • 筆電在路上用廣域無線,回辦公室自動切到更快的乙太或 802.11
  • 在無線層上用客製的請求-回覆協定減少來回延遲——因為標準 TCP 在易丟封包的無線媒介上表現不佳。

支援協定組合的例子有 UNIX Streams、Horus、x-kernel,以及較新的 Cactus 上的 CTP。

💡
關鍵

OS 要能整合新協定,動態協定組合可隨應用與連線狀況即時拼裝堆疊。

🔆
生活妙喻:socket 的可攜性 ≈ 各國通用的萬國插座

幾乎每個 OS 都提供相似的 socket API,中介軟體用它最能到處跑。

🔆
生活妙喻:客製 kernel 協定難普及 ≈ 只在自家工廠通用的客製專線接頭

效率好但出了門沒人能接,所以走不出研究環境。

🔆
生活妙喻:動態協定組合 ≈ 依路況臨時組裝交通工具

在路上拼無線、回辦公室拼乙太,協定堆疊隨連線狀況即時拼裝。

本節字彙

Communication primitive(通訊原語)
OS 提供的基本通訊操作,如 doOperation 或 socket 的讀寫。
🧠 通訊的最基本積木。
Socket
存取 TCP/UDP 等協定的標準通訊端點,跨 OS 介面相似、可攜性高。
🧠 通用插座,到哪都能接。
Dynamic protocol composition(動態協定組合)
依應用需求與當前連線狀況,即時拼裝協定堆疊的技術。
🧠 現場拼裝協定,路況變就換組合。
Amoeba 把 doOperation 這類高階通訊原語放進 kernel,相較於中介軟體在 TCP socket 上做 RMI,主要好處是?
既然把通訊原語放 kernel 比較有效率,為什麼實務上多數高階通訊(RPC/RMI、群組通訊)卻由中介軟體提供?
中介軟體大多建在 TCP/UDP socket 上,書中給的主要理由是?

呼叫效能:延遲從哪裡來

null RPC、延遲與吞吐量,以及 marshalling、資料複製、排程切換等成本來源。

STEP 1

深度探秘

軟體開銷常大於網路傳輸

為什麼呼叫效能這麼重要

設計者把功能分散到越多位址空間,需要的遠端呼叫就越多。client 與 server 一生可能做數百萬次呼叫,所以零點幾毫秒都很要緊。

null RPC 揭露的真相

null RPC=沒有參數、執行空程序、不回傳值的 RPC,只交換系統資料、沒有使用者資料。在 LAN 上,一次 null RPC 約十分之一毫秒;而一次本機空函式呼叫只要不到一微秒

一次 null RPC 約傳 100 位元組,以 100 Mbps 計算,純網路傳輸只要約 0.01 毫秒。但實測延遲遠大於此——

大部分延遲其實來自作業系統 kernel 與使用者層 RPC 執行期程式碼的動作,而非網路本身。

這是 LAN/intranet 的情況。網際網路則相反:延遲高且變動大、吞吐低、伺服器負載常主導。書中舉例:跨美國地理區的 UDP 來回約 400 毫秒,而同樣電腦在單一乙太網路上只要約 0.1 毫秒

💡
關鍵

在 LAN 上,軟體(kernel 與 RPC 執行期)開銷往往大於純網路傳輸時間。

STEP 2

生活妙喻

寄一個包裹的隱形工序

比喻:寄包裹的隱藏成本

你以為寄包裹的時間都花在『運送途中』,其實大半花在打包與處理

  • Marshalling(封送)=把要寄的東西打包、轉換格式裝箱。資料越多,打包與拆包越花時間。
  • 資料複製(data copying)=包裹在過程中被搬來搬去好幾趟:使用者↔kernel 邊界一趟、每經一個協定層一趟、網路介面↔kernel 緩衝區一趟(這趟常由 DMA 代勞)。
  • 封包初始化(packet initialization)貼標籤、填寄件資訊、算檢查碼,成本部分與資料量成正比。
  • 執行緒排程與情境切換換不同窗口辦理:RPC 過程做多次 system call(即多次情境切換),還要排程 server 執行緒;若有獨立網路管理行程,每次 Send 又多一次切換。
  • 等待確認(acknowledgement)等簽收回條,大量資料時尤其影響延遲。
flowchart LR
  M[封送打包] --> C[多次資料複製]
  C --> P[封包初始化貼標]
  P --> SCH[排程與情境切換]
  SCH --> ACK[等待確認]
💡
關鍵

延遲來自封送、多次資料複製、封包初始化、排程切換與等待確認等工序。

STEP 3

實用超能力

延遲、吞吐量與共享記憶體

延遲 vs 吞吐量

  • 延遲(latency):null 呼叫成本衡量的是固定開銷。即使加大參數,這個固定延遲仍常佔可觀比例。
  • 吞吐量(throughput/頻寬):單次 RPC 傳大量資料時的傳輸速率。資料量小時固定開銷主導、吞吐低;資料量增大,固定開銷被攤薄、吞吐上升。

當請求資料量超過一個封包大小門檻,就得多送封包(可能還多一個確認封包),延遲圖會出現跳階

OS 能怎麼幫忙

  • 適當的執行緒支援可減少多執行緒開銷(前面已談)。
  • 記憶體共享可大幅減少複製成本:用共享區域在使用者行程與 kernel、或行程之間直接讀寫,資料不必複製進出 kernel 位址空間。但同步時仍需 system call 或軟體中斷,且共享區域要用得夠多才划得來。
  • 更激進的 U-Net 架構甚至讓使用者層程式直接存取網路介面,把資料送上網路而完全不複製。
💡
關鍵

延遲是固定開銷、吞吐隨資料量上升;共享記憶體可省下大量資料複製成本。

🔆
生活妙喻:marshalling ≈ 寄件前的打包裝箱

把資料轉成可傳輸格式並裝箱,資料越多越花時間。

🔆
生活妙喻:多次資料複製 ≈ 包裹在各站之間被搬來搬去好幾趟

資料跨使用者/kernel 邊界、各協定層、網路介面各複製一次,累積成本可觀。

🔆
生活妙喻:共享記憶體省複製 ≈ 兩家共用一個直通取貨櫃

雙方直接讀寫同一塊共享區域,不必把貨搬進搬出中間倉庫。

本節字彙

Null RPC(空呼叫)
無參數、執行空程序、不回傳值的 RPC,用來量測固定的呼叫延遲。
🧠 什麼都不做的呼叫,露出純開銷。
Marshalling(封送)
把呼叫參數打包與格式轉換成可傳輸訊息的過程。
🧠 寄件前的打包裝箱。
Latency vs throughput(延遲 vs 吞吐量)
延遲是固定起步開銷;吞吐量是單次傳輸大量資料的速率。
🧠 延遲=起步要多久,吞吐=跑起來多快。
在 LAN 上,一次 null RPC 約 0.1 毫秒,但純網路傳輸那 100 位元組只需約 0.01 毫秒。這個落差說明什麼?
下列哪一項『不是』書中列出的遠端呼叫延遲主要來源?
使用共享記憶體區域在 client 與 kernel 之間傳資料,主要省下什麼成本?

本機呼叫優化:LRPC

同一台機器上的跨位址空間呼叫如何用共享 A stack 與省去執行緒排程加速。

STEP 1

深度探秘

其實大多數呼叫發生在同一台機器

一個出乎意料的事實

Bershad 等人的研究發現:在他們觀察的安裝中,大多數跨位址空間呼叫其實發生在同一台電腦內,而不是想當然的跨機 client-server。

為什麼?因為服務功能越來越被放進使用者層 server,而且快取被積極使用——client 要的資料常就在本機 server裡。於是『本機 RPC』的成本越來越重要,值得特別優化

傳統做法的問題

傳統把同機的跨位址空間呼叫,當成跟跨機一模一樣處理,只是底層訊息傳遞剛好在本機發生。這其實很浪費。Bershad 等人為此設計了 LRPC(lightweight RPC,輕量級 RPC),針對兩個地方優化:資料複製執行緒排程

💡
關鍵

本機跨位址空間呼叫其實佔多數,值得用 LRPC 特別優化。

STEP 2

生活妙喻

共用桌面 vs 反覆轉抄

比喻:兩部門怎麼交件

假設 client 與 server 是同一棟樓的兩個部門。

傳統 RPC(即使在本機)=即便在隔壁,也照跨城市流程走:把文件抄四次——

  1. client stub 的桌面 → 訊息;2. 訊息 → kernel 緩衝;3. kernel 緩衝 → server 訊息;4. 訊息 → server stub 的桌面。

LRPC=在兩部門之間放一張共用辦公桌(A stack)

  • client 與 server 共用一塊私有的共享記憶體區域,裡頭有一或多個 A(argument)stack
  • 參數與回傳值直接放在 A stack 上傳遞,client 與 server 的 stub 共用同一個 stack。
  • 於是參數只複製一次(封送到 A stack 時),相較傳統 RPC 的四次,省很多。

每個本機 client 與 server 之間用**各自獨立(私有)**的區域,且一個區域可有多個 A stack,因為同一 client 的多個執行緒可能同時呼叫 server。

💡
關鍵

LRPC 用共享的 A stack 直接傳參數,把複製從四次降到一次。

STEP 3

實用超能力

讓 client 執行緒直接進 server

第二招:省下執行緒排程

回想 system call:多數 kernel 不會另排新執行緒,而是讓呼叫者自己的執行緒做情境切換去處理。但傳統 RPC 因為遠端程序可能在別台機器,得排一條不同的執行緒去跑。

本機情況下,更有效率的做法是:讓原本會被 BLOCKED 的 client 執行緒,直接進入 server 的位址空間去呼叫那個程序。

server 因此要寫得不一樣:它不是先建好執行緒在 port 上聽,而是匯出一組可被呼叫的程序。本機行程的執行緒只要先呼叫 server 匯出的程序,就能進入 server 的執行環境。

一次 LRPC 的步驟

sequenceDiagram
  participant C as Client 與 stub
  participant K as Kernel
  participant S as Server
  C->>C: 把參數複製到 A stack
  C->>K: trap 到 kernel 並出示 capability
  K->>S: 驗證後 upcall 切換情境進 server
  S->>S: 執行程序 並把結果寫回 A stack
  S->>C: 返回 經由 trap 切回 client

它有多好、代價是什麼

  • Bershad 等人實測:LRPC 延遲約是本機執行 RPC 的三分之一
  • 位置透明性不犧牲:client stub 在 bind 時看一個位元決定 server 是本機或遠端,自動選 LRPC 或 RPC,應用無感。
  • 遷移透明性可能較難:資源從本機 server 搬到遠端(或反之)時,得換呼叫機制。
  • 前提:呼叫要夠多,才能攤平設定共享記憶體的成本。
💡
關鍵

LRPC 讓 client 執行緒直接進 server 省排程,延遲約為本機 RPC 的三分之一。

🔆
生活妙喻:A stack 共享傳參數 ≈ 兩部門之間的一張共用辦公桌

參數直接放共用桌面傳遞,不必反覆轉抄四次,只複製一次。

🔆
生活妙喻:client 執行緒直接進 server ≈ 與其叫別人來辦,不如你親自走進對方辦公室辦完

省去另排一條執行緒,由原本要阻塞的 client 執行緒直接執行 server 程序。

🔆
生活妙喻:位置透明的自動選擇 ≈ 撥電話前先看對方是內線還是外線

bind 時看一個位元決定走 LRPC 或 RPC,應用程式完全無感。

本節字彙

LRPC(lightweight RPC,輕量級 RPC)
針對同機跨位址空間呼叫優化的機制,減少資料複製與執行緒排程。
🧠 同棟樓交件,免走跨城市流程。
A stack(argument stack)
client 與 server 共用、用來直接傳遞參數與回傳值的共享記憶體。
🧠 兩部門共用的一張辦公桌。
Capability(能力憑證)
client 出示給 kernel、用來獲准進入 server 程序的權限憑證。
🧠 進門的通行證,kernel 驗了才放行。
Bershad 等人發現多數跨位址空間呼叫其實在同一台電腦內發生,書中給的原因是?
LRPC 用共享的 A stack 傳參數。相較傳統 RPC,參數的複製次數變化是?
LRPC 在執行緒排程上的優化是什麼?

非同步操作對抗高延遲

並行呼叫、非同步呼叫(promise),以及斷線環境下的持久非同步呼叫 QRPC。

STEP 1

深度探秘

高延遲與斷線的世界

OS 幫得了的、幫不了的

前面看到 OS 能幫中介軟體把遠端呼叫做得有效率。但在網際網路環境,高延遲、低吞吐、高伺服器負載,常蓋過 OS 能提供的好處。再加上斷線與重連——可視為延遲極高的通訊(行動裝置進隧道就斷線)。

對付高延遲的招式:非同步操作

**非同步操作(asynchronous operation)**有兩種程式模型,主要屬於中介軟體領域:

  1. 並行呼叫(concurrent invocations):中介軟體只提供阻塞式呼叫,但應用開多個執行緒同時做多個阻塞呼叫。
  2. 非同步呼叫(asynchronous invocations):呼叫本身非阻塞,送出請求訊息就立刻返回。

這兩種模型把『等待』的時間重疊或藏起來,正是對抗高延遲的核心思路。

💡
關鍵

在高延遲與斷線環境,非同步操作(並行呼叫與非同步呼叫)是關鍵手段。

STEP 2

生活妙喻

一次點多杯 vs 領餐取餐單

比喻:飲料店點餐

並行呼叫一次把多杯一起點

  • 序列做法:點第一杯→站著等做好→拿到→才點第二杯。慢。
  • 並行做法:第一個執行緒點第一杯後,第二個執行緒馬上點第二杯,各自等各自的。總時間明顯縮短。瀏覽器抓一頁多張圖正是這樣——不必按順序拿,同時發多個請求。

非同步呼叫=拿到一張取餐單(promise)

  • 你點完餐立刻離開櫃台(非阻塞返回),手上拿著一張取餐單。
  • 餐好了,系統把結果放進這張單對應的位置。
  • 你之後用 claim取餐:若還沒好就在那等(阻塞);也能先用 ready 問一聲『好了沒?』(不阻塞,回 true/false)。
  • 有時根本不需回應(如 CORBA oneway,maybe 語意)。
flowchart LR
  CALL[非同步呼叫] --> PR[立即拿到 promise]
  PR --> READY[ready 問好了沒]
  PR --> CLAIM[claim 取結果 可能阻塞]
💡
關鍵

並行呼叫像一次點多杯,非同步呼叫像拿取餐單之後再 claim 取結果。

STEP 3

實用超能力

斷線也不怕:持久非同步呼叫

傳統非同步呼叫的弱點

Mercury、CORBA oneway 這類傳統非同步呼叫建在 TCP 串流上,串流一斷(網路掛掉或目標當機)就失敗。它們設計成『逾時幾次後就放棄』,但在斷線或極高延遲時,這種短期逾時並不合適。

持久非同步呼叫(persistent asynchronous invocation)

程式操作和 Mercury 類似,差別在失效語意

系統會無限期地持續嘗試,直到確定成功、確定失敗,或被應用取消。

典型例子是 Rover 工具箱的 Queued RPC(QRPC)

  • 沒連線時,把外送的呼叫請求排進穩定的記錄檔(log);有連線時再排程送出。
  • 同樣把回傳結果排進 client 的『信箱』,等 client 重連再領。
  • 排隊時可壓縮請求與結果,省低頻寬。
  • 可用不同連線送請求與收回覆(去程用行動數據、回程用乙太)。
  • 排程不一定 FIFO:應用可設優先權,連線出現時先送高優先權的;慢又貴的連線可能先不送、等更快更便宜的連線。

代價:使用者在結果未知前繼續操作,可能產生衝突更新等問題(留到第 18 章)。

💡
關鍵

持久非同步呼叫會排隊無限期重試,適合斷線操作,QRPC 是典型例子。

🔆
生活妙喻:並行呼叫 ≈ 一次把多杯飲料一起點

用多執行緒同時發多個阻塞呼叫,把等待重疊起來,總時間更短。

🔆
生活妙喻:promise(非同步呼叫) ≈ 點完餐拿到的取餐單

立即返回拿到 promise,之後用 claim 取結果、用 ready 問是否完成。

🔆
生活妙喻:持久非同步呼叫 QRPC ≈ 離線時先把信投進信箱,有網路再寄出

沒連線就排進穩定記錄檔,有連線再依優先權送出,斷線也不丟失。

本節字彙

Asynchronous invocation(非同步呼叫)
非阻塞的呼叫,送出請求即返回,之後再以 promise 取得結果。
🧠 點完就走,拿張取餐單。
Promise
非同步呼叫返回的物件,結果好了會放入其中,用 claim 取、ready 測。
🧠 一張『之後憑它取結果』的取餐單。
Persistent asynchronous invocation / QRPC(持久非同步呼叫)
會無限期重試直到成功或失敗的非同步呼叫,適合斷線環境。
🧠 排隊等連線,斷線也鍥而不捨地送。
瀏覽器抓一個含多張圖的網頁時,用多個執行緒同時發出多個阻塞式 HTTP 請求。這屬於哪種非同步操作模型?
非同步呼叫返回一個 promise。client 想『不阻塞地先看看結果好了沒』,該用哪個操作?
傳統非同步呼叫(如 Mercury、CORBA oneway)在斷線環境的主要弱點是什麼?
05

OS 架構與虛擬化

兩種核心設計把哪些功能放進核心?開放性、擴充性與效率的取捨。

單體核心 vs 微核心

兩種核心設計把哪些功能放進核心?開放性、擴充性與效率的取捨。

STEP 1

深度探秘

什麼該放進 kernel

從開放性出發

一個開放的分散式系統應該能夠:只在每台電腦跑它角色所需的系統軟體(別載入冗餘模組浪費記憶體);讓任一服務的實作能獨立替換;提供同一服務的不同替代版本;以及在不傷害既有服務下引入新服務。

核心設計的長期指導原則是:把固定的資源管理機制(mechanism),與隨應用而異的資源管理政策(policy)分開。理想上,kernel 只提供最基本的機制,server 模組則按需動態載入來實作政策。

兩大設計

核心設計有兩個關鍵範例:單體核心(monolithic)微核心(microkernel)。差別主要在於——哪些功能放進 kernel,哪些留給可動態載入的 server 行程

💡
關鍵

核心設計的核心問題是把機制與政策分開:哪些功能進 kernel、哪些留給 server。

STEP 2

生活妙喻

巨石 vs 樂高

比喻:兩種蓋房子的方式

單體核心=一塊巨大的整石(monolith)

  • 字典定義 monolith 是『單一石柱;龐大、不可分割』。UNIX kernel 就被稱為單體——它執行所有基本 OS 功能,達數 MB 程式碼,而且非模組化地寫在一起,導致難以更動:要改一個元件來適應新需求很困難。
  • 它的 kernel 位址空間內可含一些 server 行程(如檔案伺服器、部分網路功能),這些都是標準 kernel 配置的一部分。

微核心=一盒樂高積木

  • kernel 只提供最基本抽象:位址空間、執行緒、本地行程間通訊
  • 其他所有系統服務都由動態載入的 server提供——而且只載到分散式系統中真正需要它的那些電腦上。
  • client 透過 kernel 的訊息式呼叫機制存取這些服務。
flowchart TD
  subgraph 單體核心
  M[kernel 含 S1 S2 S3 S4 全包在內]
  end
  subgraph 微核心
  K[微核心 只有基本機制]
  K --> A[S1 server]
  K --> B[S2 server]
  K --> C[S3 server]
  end
💡
關鍵

單體核心像一塊難改的巨石;微核心像可動態組裝的樂高。

STEP 3

實用超能力

各自的優缺點與折衷

比較

面向 單體核心 微核心
呼叫效率 高(同位址空間呼叫便宜) 較低(跨使用者層 server 呼叫更貴)
擴充性 差,難替換模組 佳,server 可動態載入替換
模組保護 弱,bug 易污染他模組 強,靠記憶體保護邊界
體積與 bug 龐大、較易有 bug 小核心較可能無 bug

折衷與後續

  • 單體核心的『無結構』可用軟體工程手法緩解:分層(MULTICS)、物件導向(Choices);Windows 兩者並用,但仍『龐大』且多數功能無法常態替換。
  • 微核心設計者另一目標是二進位模擬標準 OS(如在 Mach 上跑 UNIX 與 OS/2),同平台可呈現多個 OS 介面。注意:OS 模擬不同於機器虛擬化(下節)。
  • 有些後續設計(SPIN 用型別安全語言、Nemesis/Exokernel/L4)想用更聰明的方式兼顧效率與保護。

一位微核心設計者說:『微核心的故事充滿好點子與死胡同。』最終,支援並隔離多個子系統的需求,被虛擬化接手——它取代微核心成為 OS 設計的關鍵創新。

💡
關鍵

單體快但難改、保護弱;微核心可擴充、保護強但本機呼叫較慢,後被虛擬化接棒。

🔆
生活妙喻:單體核心 ≈ 一塊難以雕琢的巨石

全部功能寫在一起、龐大且非模組化,要改一塊很困難。

🔆
生活妙喻:微核心 ≈ 一盒樂高積木

kernel 只留基本機制,服務當 server 按需動態載入組裝。

🔆
生活妙喻:機制與政策分離 ≈ 提供球場與規則手冊,但不規定怎麼打

kernel 給基本機制,政策留給上層按應用需求決定。

本節字彙

Monolithic kernel(單體核心)
把幾乎所有 OS 功能包在單一龐大、非模組化 kernel 中的設計。
🧠 mono-lith=單一巨石,難切難改。
Microkernel(微核心)
只提供位址空間、執行緒、本地 IPC 等基本機制,其餘服務當 server 動態載入。
🧠 micro=小,只留最核心的積木。
Mechanism vs policy(機制與政策)
機制是固定的底層能力,政策是隨應用而變的決策,兩者應分離。
🧠 機制=怎麼做得到,政策=決定要怎麼做。
微核心設計的核心原則是只在 kernel 提供哪些東西?
單體核心相較微核心的主要優勢是什麼?
為什麼 UNIX kernel 被形容為『intractable(難以更動)』?

系統虛擬化與 Xen

虛擬機器與 hypervisor,全虛擬化 vs paravirtualization,以及 Xen 的 domain 架構。

STEP 1

深度探秘

一台機器變很多台

系統虛擬化的目標

系統虛擬化(system virtualization)的目標:在一台實體機器上提供多台虛擬機器(virtual machine, VM),每台 VM 各跑一份獨立的作業系統。背後觀察是:現代電腦效能足以支撐大量 VM 並在它們之間多工分配資源。可以跑多份相同 OS,也可跑各種不同 OS。

為什麼要虛擬化

  • 伺服器整合:每個服務配一台 VM,再把 VM 最佳地分配到實體伺服器。VM 比行程更容易遷移,提升管理彈性、降低投資與能耗。
  • 雲端運算:IaaS 直接由虛擬化實現——把一或多台 VM 提供給雲端使用者。
  • 動態建立/銷毀:多人線上遊戲、分散式多媒體等需要快速、低開銷地建立與銷毀 VM。
  • 多 OS 桌面:例如在 Mac OS X 上用 Parallels 同時跑 Windows 或 Linux。

實作者:hypervisor

虛擬化由一層薄薄的軟體實作,叫**虛擬機器監督器(virtual machine monitor)**或 hypervisor,它在實體架構之上提供一個貼近底層硬體的介面。

💡
關鍵

系統虛擬化用 hypervisor 在一台實體機上多工出多台各跑獨立 OS 的虛擬機。

STEP 2

生活妙喻

整層改套房 vs 量身打造

比喻:一棟樓隔成多間套房

hypervisor 像把一棟大樓隔成多間獨立套房,每間住一戶(一個 OS),共用水電(實體資源)卻互不干擾。

全虛擬化(full virtualization)每間套房都做成跟原始樣品屋一模一樣

  • hypervisor 提供與底層實體架構完全相同的介面。
  • 好處:既有 OS 可原封不動、透明地跑。
  • 壞處:在某些架構(如 x86)上難有令人滿意的效能。

Paravirtualization(半虛擬化)稍微改一下格局,換取更好住起來的效能

  • 提供一個修改過的介面。
  • 壞處:OS 必須被**移植(port)**到這個改過的介面。
  • 好處:許多指令可直接在裸硬體上跑、不必模擬,效能更好。

注意:虛擬化不同於微核心的 OS 模擬。虛擬化是讓 OS(幾乎不改地)直接跑在虛擬硬體上,所以應用不必重寫或重編譯——這正是它勝過微核心的關鍵。

💡
關鍵

全虛擬化提供相同介面、OS 不必改;paravirtualization 改介面、OS 要移植但更快。

STEP 3

實用超能力

Xen 的 domain 架構

Xen 怎麼設計

Xen 的 hypervisor 是核心,負責虛擬化 CPU 與指令集、CPU 排程與實體記憶體,並確保各 VM 之間強隔離。它遵循 Exokernel 的精神:只實作最小的資源管理與隔離機制,把高階政策留給上層;hypervisor 本身不懂裝置,只當與裝置互動的管道。

為什麼要極簡?

  • hypervisor 一旦有 bug 會讓整台機器崩潰,所以必須最小、徹底測試、無 bug。
  • 它是執行在裸硬體上的必然開銷,越輕量越好。

Xen 的 VM 叫 domain

角色 說明
domain0 特權域,能存取硬體,當『控制平面』,分離機制與政策;跑 XenoLinux
domainU 非特權域,跑各種來賓 OS(guest OS),所有資源存取由 Xen 嚴格控管
flowchart TD
  HW[實體硬體 x86] --> HV[Xen hypervisor]
  HV --> D0[domain0 控制平面 有硬體特權]
  HV --> DU1[domainU 來賓 OS]
  HV --> DU2[domainU 來賓 OS]

x86 為何需要 paravirtualization

Popek 與 Goldberg 指出:一個架構可虛擬化的條件是所有敏感指令都是特權指令(才能被 hypervisor 攔截)。但 x86 有 17 條敏感卻非特權的指令(如 LAR、LSL),無法被攔。全虛擬化得對整個指令集做模擬層(貴);paravirtualization 則讓多數指令直接跑、特權指令陷入 hypervisor,那些『敏感非特權』指令則交由改過的來賓 OS自己處理。

💡
關鍵

Xen 用極簡 hypervisor 加 domain0 控制、domainU 跑來賓 OS,並以 paravirtualization 解 x86 難題。

🔆
生活妙喻:hypervisor 與虛擬機 ≈ 把一棟樓隔成多間獨立套房

每間住一個 OS,共用水電卻互不干擾,由 hypervisor 統籌分配。

🔆
生活妙喻:全虛擬化 vs paravirtualization ≈ 完全照樣品屋 vs 稍改格局換效能

前者 OS 不必改但可能慢,後者要移植 OS 但跑得更快。

🔆
生活妙喻:domain0 與 domainU ≈ 大樓的管理室與一般住戶

domain0 有硬體特權當控制平面,domainU 是受控管的一般來賓 OS。

本節字彙

Hypervisor / VMM(虛擬機器監督器)
在實體硬體上提供虛擬機介面、分配並隔離資源的薄軟體層。
🧠 管理整棟樓套房的總管。
Paravirtualization(半虛擬化)
提供修改過的介面、OS 需移植但效能較好的虛擬化方式。
🧠 para=旁邊改一點,換來更快。
Domain(Xen 的虛擬機)
Xen 中的虛擬機;domain0 有特權當控制平面,domainU 跑來賓 OS。
🧠 domain0 是管理室,domainU 是住戶。
全虛擬化(full virtualization)相較 paravirtualization,最主要的優點是什麼?
為什麼 x86 架構需要 paravirtualization 而非單純全虛擬化就好?
在 Xen 中,domain0 與 domainU 的差別是什麼?