Home
探索 Uedu
學生控制台
註冊會員/登入
研究知情同意中心
教師控制台
課程設定
支援與訊息
Uptime 數據

UeduGPTs

--

Jupyters

4

UG26 CISOSE26
臺北 AQI 46 · 臺中 AQI 28 · 臺南 AQI 24 · 高雄 AQI 33

AI 回覆桌面通知

AI 助教回覆完成時顯示桌面通知

聊天訊息通知

同學在討論區發送訊息時通知

聲音通知

每當有新通知時播放提示音

行程與排程

行程與排程

作業系統如何在眾多程式間飛快切換,製造出「同時運作」的假象

為什麼你的電腦能同時開十幾個分頁?

想像你坐在咖啡廳,筆電上同時開著瀏覽器的十幾個分頁、一個音樂播放器、背景下載的更新程式,還有一個正在打字的文件編輯器。直覺上你會以為這些程式「同時」在跑——但如果你的筆電只有一顆 CPU 核心,物理上它一次其實只能執行一條指令。那麼,這種「同時感」是哪裡來的?

答案藏在作業系統(operating system)最核心的兩個機制裡:行程(process) 的管理,以及決定「下一個輪到誰用 CPU」的 排程(scheduling)。作業系統就像一位手腳極快的餐廳服務生,在十幾桌客人之間飛快地切換——切換得夠快,每一桌都覺得自己被專心服務著。這篇文章就要拆解這位「服務生」是怎麼做決策的。

行程是什麼:執行中的程式

我們先把兩個容易混淆的詞分清楚。

  • 程式(program):躺在硬碟上的一個檔案,例如 chrome.exe。它是靜態的、死的,只是一堆機器碼。
  • 行程(process):當你雙擊那個檔案、把它載入記憶體開始執行時,就誕生了一個行程。它是動態的、活的。

一個行程不只是程式碼。作業系統為每個行程維護一份完整的「身分檔案」,稱為 行程控制區塊(Process Control Block, PCB),裡面記錄了:

  • 行程識別碼(PID)
  • 目前的狀態(state)
  • 程式計數器(program counter):下一條要執行的指令在哪裡
  • CPU 暫存器(register)的內容
  • 記憶體配置資訊
  • 開啟的檔案、I/O 裝置等資源清單

同一個程式可以同時跑出多個行程——你開兩個瀏覽器視窗,就是兩個各自獨立、互不干擾記憶體空間的行程。

作業系統概念示意圖

行程 vs 執行緒:更輕量的執行單位

行程之間彼此隔離,這很安全,但也有代價:建立一個行程很「重」,而且行程間要互相溝通(inter-process communication)很麻煩。

於是有了 執行緒(thread)。一個行程內部可以有多條執行緒,它們共享同一份記憶體空間、開啟的檔案等資源,但各自擁有獨立的程式計數器、暫存器與堆疊(stack)。

打個比方:行程是一間有獨立門禁的辦公室,執行緒則是這間辦公室裡的多名員工。員工共用辦公室的白板與檔案櫃(共享記憶體),但每個人手上有自己的待辦清單(獨立堆疊)。

比較項目 行程(process) 執行緒(thread)
記憶體空間 各自獨立 同行程內共享
建立成本
溝通方式 IPC(較慢、較安全) 直接讀寫共享記憶體(快、需同步)
一個崩潰的影響 通常不影響其他行程 可能拖垮整個行程

因為共享記憶體,多執行緒程式要特別小心 競爭條件(race condition)——兩條執行緒同時改同一份資料導致結果錯亂,這需要鎖(lock)等同步機制來防範。

行程的一生:狀態與轉換

一個行程從生到死,會在幾個狀態之間移動。最常見的三狀態模型是:

  • 就緒(ready):萬事俱備,只等 CPU 分配時間給它。
  • 執行(running):正握有 CPU、正在執行指令。
  • 等待/阻塞(waiting / blocked):在等某件事完成,例如等硬碟把資料讀進來、等使用者按鍵。此時就算給它 CPU 也沒用。

狀態之間的轉換像這樣:

            [被排程選中]
   ready ───────────────▶ running
     ▲                      │  │
     │ [I/O 完成]           │  │ [時間配額用盡 / 被搶佔]
     │                      │  └──────────▶ ready
   waiting ◀────────────────┘
            [發出 I/O 請求]

關鍵觀察:在單核心系統中,同一時刻只有一個行程處於 running。所謂「多工(multitasking)」,其實是作業系統讓眾多行程在 ready 與 running 之間快速輪替,製造出並行(concurrency)的假象。注意「並行」與「平行(parallelism)」不同:平行需要多個核心真正同時執行,並行則只需要在時間軸上交錯。

換手的代價:上下文切換

當 CPU 從行程 A 切換去執行行程 B 時,作業系統必須:

  1. 把 A 目前的 CPU 暫存器、程式計數器等狀態,完整存回 A 的 PCB;
  2. 從 B 的 PCB 載入 B 之前存下的狀態;
  3. 讓 CPU 從 B 上次中斷的地方繼續跑。

這個過程稱為 上下文切換(context switch)。它是純粹的「行政開銷」——切換的那段時間,CPU 沒有替任何使用者程式做有用的計算。雖然單次切換只花微秒等級的時間,但若排程太頻繁,累積起來的浪費就很可觀。這也是後面排程演算法要權衡的重點之一。

排程演算法:誰先用 CPU?

當多個行程都在 ready 狀態排隊,作業系統的 排程器(scheduler) 必須挑一個來執行。挑選的策略就是排程演算法。我們先建立兩個重要概念:

  • 非搶佔式(non-preemptive):行程一旦拿到 CPU,就一直跑到自己主動讓出(結束或進入等待)為止。
  • 搶佔式(preemptive):作業系統可以強制中斷正在執行的行程,把 CPU 收回去給別人。現代桌面與手機作業系統幾乎都是搶佔式,這樣才能保證沒有任何一個程式能霸佔 CPU 不放。

下面介紹四種經典演算法。

FCFS(先到先服務)

先來先服務(First-Come, First-Served) 就像便利商店排隊結帳:誰先進就緒佇列,誰先被服務,做完才換下一個。非搶佔式,實作簡單、公平直覺。

缺點是 護航效應(convoy effect):如果一個超耗時的行程排在前面,後面一堆小行程都得苦等。就像一台推著滿車貨物的客人排在你前面結帳。

SJF(最短工作優先)

最短工作優先(Shortest Job First) 改成優先服務「預估執行時間最短」的行程。可以證明,SJF 能讓平均等待時間最小化

但它有兩個問題:一是作業系統其實無法事先準確知道一個行程要跑多久(只能用歷史資料預測);二是若短工作源源不絕地進來,長工作可能永遠排不到,造成 飢餓(starvation)

RR(輪流排程)

輪流排程(Round Robin) 是搶佔式的代表。系統給每個行程一段固定的 時間配額(time quantum / time slice),例如 10 毫秒。時間一到,不管行程做完沒,都被強制換下、排到佇列尾端,換下一個上場。

RR 的精神是公平良好的回應性——每個行程都不必等太久就能輪到一次。時間配額的設定是門藝術:太大就退化成 FCFS;太小則上下文切換太頻繁,開銷吃掉效能。

優先權排程

優先權排程(Priority Scheduling) 給每個行程一個優先權數字,永遠先服務優先權最高的。SJF 其實可看成「以執行時間長短為優先權」的特例。

優先權排程同樣有飢餓問題:低優先權行程可能一直被插隊。解法是 老化(aging)——讓一個行程等越久,它的優先權就慢慢提高,最終一定輪得到。

動手看一個例子

假設有三個行程同時在時間 0 抵達就緒佇列,各自需要的 CPU 時間(稱為 執行時間/突發時間 burst time)如下:

行程 突發時間
P1 8
P2 4
P3 2

我們比較 FCFS(依 P1、P2、P3 順序)SJF 兩種排程下的表現。

FCFS(執行順序 P1 → P2 → P3)

時間軸: |---- P1 ----|-- P2 --|- P3 -|
        0           8        12     14
  • P1 等待時間 = 0
  • P2 等待時間 = 8(要等 P1 跑完)
  • P3 等待時間 = 12(要等 P1、P2 跑完)
  • 平均等待時間 = $(0 + 8 + 12) / 3 \approx 6.67$

SJF(依突發時間排序,執行順序 P3 → P2 → P1)

時間軸: |- P3 -|-- P2 --|---- P1 ----|
        0     2        6            14
  • P3 等待時間 = 0
  • P2 等待時間 = 2
  • P1 等待時間 = 6
  • 平均等待時間 = $(0 + 2 + 6) / 3 \approx 2.67$

同樣三個行程、總工時相同(最後都在時間 14 全部做完),但 SJF 把短工作擺前面,平均等待時間從 6.67 降到 2.67。這直觀地說明了為什麼 SJF 在「平均等待時間」這個指標上是最優的:讓短工作先走,能減少後面所有人累積的等待。

重點回顧

  • 程式是靜態檔案,行程是執行中的程式;行程的完整狀態記錄在 PCB 中。執行緒是行程內更輕量、共享記憶體的執行單位。
  • 行程在 ready/running/waiting 三個狀態間轉換;單核心下同一時刻只有一個行程在 running,多工是快速輪替造成的並行假象。
  • 上下文切換是換手行程時的必要開銷,本身不產生有用計算,排程設計需盡量減少不必要的切換。
  • 四大經典排程:FCFS(簡單但有護航效應)、SJF(平均等待最短但需預測且會飢餓)、RR(搶佔式、公平、回應快)、優先權(可彈性但需老化防飢餓)。
  • 搶佔式排程讓作業系統能強制收回 CPU,是現代互動式系統保證回應性的關鍵。

深入探討(研究所視角)

排程指標的精確定義

要客觀評比排程演算法,需要量化指標。對一個行程而言,常用三個時間指標:

  • 周轉時間(turnaround time) = 完成時間 - 抵達時間。從進入系統到徹底做完,總共花了多久。
  • 等待時間(waiting time) = 周轉時間 - 實際執行(突發)時間。即在就緒佇列乾等的總時長。
  • 回應時間(response time) = 第一次被排上 CPU 的時間 - 抵達時間。即從送出到「開始有反應」的延遲。

這三者衡量的面向不同。批次運算系統在意 周轉時間吞吐量(throughput,單位時間完成的行程數);而互動式系統(桌面、手機)最在意 回應時間——使用者點一下按鈕,能多快看到畫面動起來,比「整體跑完多快」更影響體驗。這正是 RR 雖然平均周轉時間未必最佳,卻廣受互動式系統採用的原因:它的回應時間表現優異。

排程設計本質上是一個多目標權衡問題:最小化平均等待、最大化吞吐、保證公平、避免飢餓、控制切換開銷,這些目標彼此常有衝突,沒有單一演算法能全贏。

搶佔式排程的延伸:SRTF 與優先權反轉

SJF 的搶佔式版本稱為 最短剩餘時間優先(Shortest Remaining Time First, SRTF):每當有新行程抵達,排程器比較「新行程的突發時間」與「目前行程的剩餘時間」,若新來的更短就立刻搶佔。SRTF 在理論上能進一步壓低平均等待時間,但搶佔本身帶來更多上下文切換成本,且預測剩餘時間同樣困難。

搶佔式優先權排程還潛藏一個著名陷阱——優先權反轉(priority inversion):高優先權行程 H 需要一把鎖,但這把鎖正被低優先權行程 L 持有;偏偏中優先權行程 M 不斷搶佔 L,使 L 遲遲無法執行、無法釋放鎖,結果 H 被間接卡死。1997 年 NASA 的火星拓荒者號(Mars Pathfinder)就因此反覆重啟,最終靠遠端啟用 優先權繼承(priority inheritance) 協定修復——讓持鎖的低優先權行程暫時繼承等鎖者的高優先權,儘快做完釋放鎖。這個案例提醒我們:排程不能脫離同步機制孤立地看。

從理論到現代核心

真實系統遠比四大經典演算法複雜。典型作法是 多層回饋佇列(Multilevel Feedback Queue, MLFQ):設置多個不同優先權的佇列,行程依其行為動態升降——用滿時間配額(像是 CPU 密集型)就被降級,主動讓出 CPU(像是 I/O 密集型的互動程式)則維持高優先權。如此能在不預知突發時間的前提下,近似 SJF 的效果,同時兼顧互動回應。Linux 過去的 完全公平排程器(Completely Fair Scheduler, CFS) 則改用紅黑樹(red-black tree)追蹤每個行程累積的「虛擬執行時間」,總是挑虛擬時間最少者執行,以 $O(\log n)$ 的複雜度逼近理想中的「處理器資源完全均分」。

延伸思考可連結到本讀本的其他主題:排程器挑中行程後,記憶體管理(memory management)還得確保它的分頁(page)在實體記憶體中(否則觸發分頁錯誤又得阻塞等 I/O);多核心時代則進一步衍生 負載平衡(load balancing)快取親和性(cache affinity)——把行程固定在同一核心執行,可重用該核心快取(cache)中的資料,這又把排程與計算機組織的議題綁在了一起。排程,遠不只是「誰先用 CPU」這麼單純的排隊問題。

AI 共讀助教正在陪你讀:行程與排程
嗨!我是這篇文章的共讀助教,只根據〈行程與排程〉的內容回答。可以問我「解釋某段」「舉個例子」「出題考我」,或反白文中段落後點下方「解釋選取段落」。