01

作業系統層與資源保護

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

先讀原文段落,旁邊就是白話

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

原文 · 作業系統層與資源保護 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

行程與位址空間

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

先讀原文段落,旁邊就是白話

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

原文 · 行程與位址空間 er has been selected, a new process requires an execution environment consisting of an address space with initialized contents (and perhaps other resources, such as default open files). There are two approaches to defining and initializing the address space of a newly created process. The first approach is used where the address space is of a statically defined format. For example, it could contain just a program text region, heap region and stack region.
白話導讀

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

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

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

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 與處理器瓶頸。

先讀原文段落,旁邊就是白話

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

原文 · 執行緒:並行的引擎 ible to combine the advantages of user-level and kernel-level threads implementations. One approach, applied, for example, to the Mach kernel [Black 1990], is to enable user-level code to provide scheduling hints to the kernel’s thread scheduler. Another, adopted in the Solaris 2 operating system, is a form of hierarchical scheduling. Each process creates one or more kernel-level threads, known in Solaris as ‘lightweight processes’.
白話導讀

用單執行緒到多執行緒到多處理器的吞吐量推演,理解 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 上、協定堆疊的開放與動態組合。

先讀原文段落,旁邊就是白話

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

原文 · 通訊與呼叫效能 serialized case, the client marshals the arguments, calls the Send operation and then waits until the reply from the server arrives – whereupon it Receives, unmarshals and then processes the results. After this it can make the second invocation. In the concurrent case, the first client thread marshals the arguments and calls the Send operation. The second thread then immediately makes the second invocation.
白話導讀

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 架構與虛擬化

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

先讀原文段落,旁邊就是白話

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

原文 · OS 架構與虛擬化 concept of privileged instructions in a machine architecture – that is, instructions that either execute in privileged mode or generate a trap (which can then take them into privileged mode). This leads to the following precise statement of the Popek and Goldberg condition: Condition for virtualization : A processor architecture lends itself to virtualization if all sensitive instructions are privileged instructions. Unfortunately, in the x86 family of processors, this is not the case: it is possible to identify 17 instructions that are sensitive but not privileged. For example, the LAR (load access rights) and LSL (load segment limit) instructions fall into this category.
白話導讀

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

單體核心 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 的差別是什麼?