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

輾轉現象:當系統卡死、寸步難行

有時一台忙碌的機器會慢到像在爬,磁碟燈瘋狂閃爍,卻幾乎沒有真正的工作被完成。那就是輾轉現象——一個惡性的回饋漩渦,行程把所有時間都花在出錯,而不是執行。這篇導覽會解釋這個漩渦如何開始、為什麼加更多工作反而會讓它災難性地惡化,以及兩帖經典的解藥:工作集模型,與分頁錯誤頻率控制。

一台忙著什麼都沒做的機器

想像你讓太多廚師同時擠進廚房。檯面只有一小塊,而每道食譜都需要把食材攤在上面。三個廚師時,每人把自己那一小撮食材放在檯面上,工作順暢。加進第十個廚師,檯面就爆了:現在每個廚師在每一步之前,都得先把別人的食材清開、再從儲藏室把自己的拿來——一遍又一遍。廚房忙得人仰馬翻,菜卻幾乎沒往前推進。這正是一台電腦陷入輾轉現象時發生的事。

回想第四篇導覽:作業系統透過頁框配置,把一份固定的頁框池分給所有執行中的行程。只要每個行程至少分到足以容納它正在積極使用的那些分頁的頁框,虛擬記憶體就會順暢運轉,分頁錯誤率維持極小。輾轉現象這個名字,指的就是「太多行程共用太少頁框」時發生的事:每個行程都被餓到低於它所需的最低限度,於是它幾乎每次記憶體存取都出錯,而 CPU 大半時間閒置著等磁碟。

回饋漩渦:它如何自行越陷越深

讓輾轉現象如此危險的,是它會自我餵養。觸發點通常是一個盯著 CPU 使用率看的舊式排程器一個出於好意的決定。排程器這樣推理:「CPU 只忙了 20%,機器負載不足,讓我多收幾個行程進來,好讓它保持產出。」但 CPU 之所以閒置,恰恰是因為現有的行程全都卡在等磁碟換入分頁。多收行程只會讓情況更糟,而不是更好。

  1. 許多行程在跑;作業系統給每個只分幾個頁框。每個行程都剛好低於它實際所需的頁框數。
  2. 行程不停出錯。為了服務每次錯誤,作業系統必須趕走某個分頁——常常是另一個(或同一個)行程馬上又會用到的分頁,於是它又立刻錯回來。
  3. 因為大家都被擋住、等著磁碟,CPU 使用率往零滑落。同時磁碟則被換入與換出的流量塞爆。
  4. 一個用 CPU 使用率來判斷健康狀況的排程器,看到閒置的 CPU,便收進更多行程來「把它用掉」。
  5. 頁框被分得更薄,錯誤率攀得更高,處理量崩潰。漩渦越收越緊,直到幾乎沒有任何有用的工作被完成。

經典的圖像是一條曲線:當你拉高多重程式規劃的程度(你收進多少行程),CPU 使用率起初漂亮地往上爬、達到頂峰,然後就跌落懸崖。越過頂峰之後,你每多收一個行程都會讓整台機器更慢,因為這個邊際行程從每個人那裡偷走頁框,把系統推下邊緣。作業系統的任務,就是把你保持在那條曲線的頂端,而不是越過它。

為什麼區域性是出路

如果程式是隨機碰觸它的分頁,輾轉現象就無可避免,但真實程式並非如此。它們服從參考區域性:在任何一小段時間裡,一個行程只會碰觸一小撮、緩慢移動的分頁——內層迴圈的程式碼、它正在掃描的陣列、堆疊上那寥寥幾個變數。當程式從一個階段移到另一個階段(比方說跑完一個迴圈、進入一個新函式),那一撮會漂移到一組新的分頁,但在任何一瞬間它都很小。

這就是救我們的那個漏洞。如果我們能給每個行程剛好足夠的頁框來容納它「當下的那一撮」,它就只會在從一個階段切換到下一個階段時短暫出錯,接著一路無錯地跑,直到下一次切換。麻煩始於一個行程拿到的頁框少於它那一撮所需——那時它連自己的活躍分頁都裝不下,於是無止境地出錯。一句話講,輾轉現象就是:系統試圖同時跑的「區域性撮子」比它有頁框可裝的還多。

解藥一:工作集模型

第一帖解藥把區域性化成一個我們量得出來的數字。選一個窗口——一個行程最近做的 delta 次參考(比方說最近的 10,000 次分頁存取)。那個行程的工作集,說穿了就是它在那個窗口內碰過的「相異分頁」的集合。由於區域性,這個集合很小、相當穩定,而且是「這個行程此刻需要常駐多少分頁才不會出錯」的一個好估計。窗口大小 delta 是唯一的旋鈕:太小,你會漏掉行程真正需要的分頁;太大,你會留著一堆過期的分頁。

Window delta = 10 most-recent references.
Reference string (page numbers touched, in order):

  ... 2 6 1 5 7 7 7 5 1 6 | 2 3 4 4 4 3 4 3 4 4 ...
      \___ window here ___/

  Working set in this window = { 1, 2, 5, 6, 7 }  -> needs 5 frames

Sum the working-set sizes of ALL processes:

  total demand  D = WSS_1 + WSS_2 + ... + WSS_n

  if  D <= total frames available  -> safe, run them all
  if  D >  total frames available  -> overcommitted: suspend a process
工作集是最近 delta 次參考內碰過的相異分頁。把每個行程的工作集大小加總,就得到系統真正的頁框需求;當它超過你擁有的頁框時,你就快要輾轉了。

優雅的地方在這裡。作業系統把所有行程的工作集大小加起來,得到總頁框需求 D。如果 D 容得進可用的頁框,系統就安全,每個行程都能守住它的工作集。但如果 D 超過了頁框供給,作業系統就知道輾轉迫在眉睫,於是果斷出手:它「整個」暫停掉一個行程,把它換出、釋放它全部的頁框給其餘的人。被暫停的行程等著,存活下來的各自得以守住自己的工作集,於是漩渦根本不會開始。稍後,當需求下降,被暫停的行程再被恢復。這就是為什麼這個模型天生與局部置換搭配——不讓一個行程的錯誤去偷走另一個的頁框。

解藥二:分頁錯誤頻率控制

工作集模型有原則,卻有點間接——你量分頁來預測錯誤。第二帖解藥,分頁錯誤頻率(PFF),跳過了預測,直接量我們真正在乎的那個症狀:錯誤率本身。道理很簡單。高錯誤率意味著一個行程相對它當下的區域性、頁框太少;極低的錯誤率意味著它的頁框比所需更多、可以借出去一些。所以我們直接對著我們想控制的東西去操控。

具體來說,作業系統為每個行程設一個可接受錯誤率的上界與下界,並隨時間盯著它。如果一個行程的錯誤率升到上界之上,它就是頁框不足,於是作業系統給它更多頁框。如果它的錯誤率掉到下界之下,它就是供給過剩,於是作業系統收回它一些頁框給別人。這是一具恆溫器:熱(錯誤)太高,加頁框;太低,抽走頁框。唯一的逃生口,是當所有地方的錯誤率都高、又一個多餘的頁框也拿不出來時——這時,就跟工作集方案裡一模一樣,作業系統必須暫停掉一個行程,以紓解整個系統。

兩帖解藥到頭來都匯聚到同一個洞見:跳出輾轉現象唯一真正的辦法,是把彼此競爭的區域性數目減到容得下為止。你可以靠加總工作集來預防性地做,或靠盯著錯誤率來反應性地做,但無論哪種,槓桿都是准入與配置——多少行程在跑、每個守著多少頁框。這就讓整個階段的迴圈閉合了:我們問了誰會被趕出去、學了哪套策略趕得明智、學了如何在行程間瓜分頁框,如今我們看見了當瓜分根本不可能時會怎樣——以及如何防它發生。