National Central University
Uedu Main Site
Explore Uedu
Student Console
Register as Member/Login
Research Informed Consent Center
Survey Center
Educational Big Data Challenge
Claim or create a course
Try an AI Scenario Task
Support & Messages
Uptime Data

UeduGPTs

--

Jupyters

3

Local AI

--

Uedu Code

--

中央大學 AQI 72 24°C PM2.5 13
AI Reply Desktop Notifications

Show a desktop notification when the AI TA finishes replying

Chat Message Notifications

Notify me when classmates post messages in the forum

Sound notification

Play an alert sound whenever there is a new notification

Unit 06

多層回饋佇列與多核排程

不知道工作多長時怎麼排、多顆核心怎麼分工
Interaction實作 30 minute(s)

Learning Objectives

  • 說明 MLFQ 怎麼在不知道工作長度的情況下,同時照顧互動型與 CPU 型工作
  • 親手鑽出 MLFQ 舊規則的漏洞,並說明加分機制與累積配額各解決什麼問題
  • 用虛擬執行時間解釋 Linux 怎麼依權重分配 CPU,以及 nice 的作用
  • 比較共用佇列與每核一條佇列的取捨,說明負載平衡與親和性的代價

排程器不知道工作要跑多久

上一課的 SJF 很漂亮,但它要先知道每個工作還要跑多久,而作業系統不知道。你打開的編輯器,可能接下來一小時都只處理按鍵;也可能你按下存檔之後,它開始跑五分鐘的格式檢查。

作業系統能看到的只有過去:這個工作上次用了多少 CPU?是不是常常主動去等 I/O?多層回饋佇列(Multi-Level Feedback Queue,MLFQ)就是用過去的行為猜未來:

  • 常常主動放棄 CPU 的工作,大概是互動型的,讓它優先,回應才會快。
  • 一直用滿 CPU 的工作,大概是長時間運算,讓它排後面,但一次給長一點的時間片。

這個想法可以追溯到 Corbató 等人 1962 年描述的 CTSS(相容分時系統),是分時系統很早就面對的問題。

規則

系統有好幾層佇列,每層有自己的時間片,越高層時間片越短:

編號 規則
1 優先權高的層先跑;高層有人時,低層的工作不會跑
2 同一層之內用 RR 輪流
3 新工作從最高層開始
4 工作在某一層累積用滿配額,就降一層
5 每隔一段時間,所有工作都回到最高層(加分,priority boost)

規則 3 讓新工作先被當成短工作:真的很短就很快做完,不短就一層一層往下沉,所以等於用實際表現逼近 SJF,不需要事先知道長度。規則 4 和 5 換一種寫法就會出問題,下面親手試。

互動練習:MLFQ 的規則與漏洞

規則改了就重新模擬,所有數字都是模擬器算出來的。時間軸的紅色虛線是加分的時刻。

舊規則是很直覺的寫法:這次用滿時間片才降級,時間片用完前主動放棄 CPU 就留在原層。新規則不管中途放棄幾次,只要這一層累積用量到配額就降級。

Linux 的做法:虛擬執行時間

Linux 的一般行程不是用 MLFQ,而是追求比例公平:每個行程應該分到多少 CPU,就讓它分到多少。

從 Linux 2.6.23 起的 CFS(Completely Fair Scheduler)替每個行程記一個虛擬執行時間(vruntime)。行程跑了多久,就加到 vruntime 上;排程器永遠挑 vruntime 最小的來跑,也就是到目前為止被虧待最多的那個。官方文件的描述是 CFS 把可執行的工作依 vruntime 排在一棵紅黑樹上,取最左邊的節點。

權重讓「公平」可以不是平分。行程跑一段實際時間,虛擬時間增加的量大約是:

vruntime 增加量 = 實際執行時間 × 1024 / 權重

權重越大,虛擬時間長得越慢,排程器就越常選它。權重來自 nice 值,範圍是 −20(最優先)到 +19(最不優先),數字越大越「客氣」。sched(7) 說 nice 差 1 大約差 1.25 倍;核心原始碼裡的對照表 nice 0 是 1024,nice 5 是 335。

nice -n 5 ./long_job      # 用 nice 5 啟動
renice -n 10 -p 1234      # 調整執行中的行程

把 nice 調大誰都可以做;調小(要更多 CPU)傳統上需要特權,現在也可以用資源限制 RLIMIT_NICE 開放。

挑 vruntime 最小的工作

兩個一直要用 CPU 的工作,權重取自核心的對照表。注意看 nice 5 跑一次,虛擬時間跳多大,以及這讓 nice 0 連續跑了幾次。為了避免小數,程式裡把數字都放大了 100 倍。

實作:算出 CPU 分配比例

三個工作,兩個 nice 0、一個 nice 5。補完挑選與記帳,跑 3000 個單位,看 CPU 怎麼分,再和權重比 1024 比 335 對照。

你的輸出
預期輸出

6.6 之後:EEVDF

Linux 6.6(2023 年 10 月發布)把 CFS 換成了 EEVDF(Earliest Eligible Virtual Deadline First,最早合格虛擬截止時間優先)。演算法本身來自 1995 年的論文。它和 CFS 一樣用虛擬時間追求公平,只是多處理了「誰該先拿到反應」:

  • 每個工作有一個 lag:應得的 CPU 減去實際拿到的。lag 大於等於 0 的工作被欠了 CPU,才算合格(eligible)。
  • 排程器替合格的工作算虛擬截止時間,選最早的來跑。時間片要求越短,截止時間越早,所以對延遲敏感的工作可以比較快拿到 CPU,但一次跑得比較短。

它也要防短暫睡眠的漏洞:拿太多 CPU 的工作(lag 為負)如果睡一下就能重置 lag,等於作弊,所以官方文件說明它讓睡眠中的工作的 lag 隨虛擬時間慢慢衰減。和 MLFQ 補的洞是同一類:只要排程器對「放棄 CPU」有優待,就會有人想利用它。

手冊頁 sched(7) 目前還寫著預設排程器是 CFS。排程細節變動快,要查現況請看 核心文件。

寫程式時不需要記這些細節,但兩個判斷會一直有用:單純用 CPU 的程式會被公平分配,要影響分配就用 nice;而程式的等待習慣會影響它得到的待遇。

多顆核心怎麼分工

核心變多之後多了一個問題:不只決定「下一個是誰」,還要決定「在哪一顆跑」。

共用一條佇列最直接:所有核心從同一條佇列拿工作,不可能有核心閒著而別人排隊。缺點有兩個:

  1. 佇列要用鎖保護,核心越多,等鎖越久。
  2. 工作每次可能換到不同核心。每顆核心有自己的快取,換核心之後,原本快取裡的資料都要重新載入,這段時間程式跑得比較慢。

每核一條佇列沒有這兩個問題:不用搶鎖,工作也一直留在原來的核心。但它會負載不平衡:這顆核心的佇列空了,那顆還排著好幾個。

解法是負載平衡(load balancing):定期檢查,把工作從忙的核心移到閒的核心。Linux 的每顆核心有自己的執行佇列,並由週期性的負載平衡移動工作。移動一次就要付一次快取的代價,所以要看什麼時候值得。

親和性(CPU affinity)讓你指定行程只能在哪些核心上跑。sched_setaffinity(2) 的說明提到,把行程固定在一顆核心上,可以避免快取失效帶來的效能損失。代價是排程器失去了移動它的自由。

taskset -c 2 ./job        # 只能在核心 2 上跑

互動練習:共用佇列、每核一條、負載平衡

工作在核心上跑的時間軸由模擬器算出來。鎖和快取的代價都是教學用的示意值,所以請看取捨的方向,不要記數字。

確認一下

1.

只有舊規則(用滿時間片才降級)時,惡意程式怎麼一直留在最高層?

2.

MLFQ 少了加分(規則 5),一個原本 CPU 型、後來變成互動型的工作會怎樣?

3.

只有 nice 0 的 P 和 nice 5 的 Q 在同一顆核心上競爭 CPU,兩者都一直要用 CPU。大約會怎麼分?

4.

下面的程式模擬 CFS 的記帳。P 的權重是 Q 的兩倍,一開始虛擬時間都是 0,平手時 P 先。跑 6 個單位之後,P 和 Q 各用了幾個單位?

5.

一台 64 核心的伺服器,所有執行緒都很短、而且資料彼此無關。為什麼通常不用單一共用佇列?

第 1 次作答

重點整理

  • MLFQ 用過去的行為猜未來:新工作從最高層開始,用得多就往下沉,定期加分讓所有工作回到最高層。
  • 舊規則只看單次有沒有用滿時間片,會被故意提早放棄 CPU 的程式鑽漏洞;累積配額才補起來。加分解決飢餓,也讓行為改變的工作有機會被重新判斷。
  • Linux 用虛擬執行時間追求比例公平:權重越大,vruntime 長得越慢。nice 差 1 約差 1.25 倍。從 6.6 起改用 EEVDF,加入 lag 與虛擬截止時間。
  • 多核心的取捨:共用佇列負載平衡但有鎖競爭與快取損失;每核一條反過來,要靠負載平衡補救。親和性能留住快取,用錯會浪費核心。

延伸閱讀:免費公開的 OSTEP 第 8 章(MLFQ)、第 10 章(多處理器排程)。

下一課看另一種讓程式同時做多件事的方法:同一個行程裡的多個執行緒。

這一課完成了!
Next lesson:執行緒
你目前沒有登入,學習進度只存在這個瀏覽器。 Login 後,進度會跟著帳號,換電腦也看得到。