先級線程越多越好嗎?實時調(diào)度中的優(yōu)先級與資源競爭)
在工業(yè)控制、機器人運動控制和實時數(shù)據(jù)采集系統(tǒng)中為了讓關(guān)鍵任務(wù)及時運行工程師通常會使用 Linux 的實時調(diào)度策略例如SCHED_FIFO和SCHED_RR并為控制線程設(shè)置較高的實時優(yōu)先級。但在實際項目中有一種看似合理、實際上可能適得其反的做法只要某個線程比較重要就提高它的優(yōu)先級只要某個任務(wù)可能影響實時性就把它也設(shè)置成高優(yōu)先級。隨著系統(tǒng)功能逐漸增加實時線程的數(shù)量越來越多優(yōu)先級也越來越高最終反而出現(xiàn)任務(wù)饑餓、鎖等待、周期超時和系統(tǒng)響應(yīng)異常等問題。原因在于優(yōu)先級并不是一種能夠憑空增加 CPU 計算能力的資源。它決定的是符合調(diào)度條件的任務(wù)之間如何競爭 CPU。當多個線程同時處于可運行狀態(tài)時提高某個線程的優(yōu)先級往往意味著其他線程獲得 CPU 的機會減少。如果優(yōu)先級分配缺乏明確的任務(wù)模型系統(tǒng)就可能把資源集中給某些線程卻讓它們依賴的輔助任務(wù)無法及時執(zhí)行。例如一個機器人控制系統(tǒng)包含 EtherCAT 周期任務(wù)、運動學(xué)計算、傳感器處理、狀態(tài)監(jiān)控、日志記錄和網(wǎng)絡(luò)通信。如果所有線程都設(shè)置為高優(yōu)先級系統(tǒng)并不會因此讓所有任務(wù)同時運行。相反高優(yōu)先級線程可能不斷搶占其他線程導(dǎo)致低優(yōu)先級數(shù)據(jù)處理任務(wù)無法及時更新共享狀態(tài)控制線程隨后又等待這些狀態(tài)最終形成資源競爭和任務(wù)延遲。因此實時調(diào)度的核心不是盡可能提高所有任務(wù)的優(yōu)先級而是根據(jù)任務(wù)周期、截止時間、執(zhí)行時間、資源依賴和系統(tǒng)負載設(shè)計一套能夠被驗證的調(diào)度關(guān)系。本文從 Linux 實時調(diào)度機制出發(fā)分析優(yōu)先級分配中的常見誤區(qū)并介紹如何在工業(yè)控制系統(tǒng)中建立合理的實時調(diào)度方案。一、SCHED_FIFO 和 SCHED_RR 到底怎樣決定線程的執(zhí)行順序在討論優(yōu)先級規(guī)劃之前需要先理解 Linux 中常見的實時調(diào)度策略。對于使用SCHED_FIFO的線程調(diào)度器會優(yōu)先選擇當前可運行的最高優(yōu)先級實時線程。在同一優(yōu)先級的普通 FIFO 競爭關(guān)系中線程通常持續(xù)運行直到主動讓出 CPU、進入阻塞狀態(tài)、被更高優(yōu)先級線程搶占或者受到其他調(diào)度規(guī)則影響。它不會因為消耗了一個普通的時間片就像普通公平調(diào)度任務(wù)那樣自動輪換。這意味著如果一個高優(yōu)先級SCHED_FIFO線程始終處于可運行狀態(tài)卻沒有正確阻塞、主動讓出 CPU 或結(jié)束本次工作那么較低優(yōu)先級的線程可能長期得不到運行機會。下面是一個簡單的場景線程 A實時優(yōu)先級 90采用SCHED_FIFO。線程 B實時優(yōu)先級 70采用SCHED_FIFO。線程 C普通調(diào)度策略執(zhí)行日志和監(jiān)控任務(wù)。如果線程 A 持續(xù)占用 CPU線程 B 和線程 C 就可能無法及時運行。即使線程 B 負責準備下一周期所需的數(shù)據(jù)也不能僅憑它的業(yè)務(wù)重要性保證它得到 CPU。調(diào)度器主要依據(jù)實際調(diào)度屬性和可運行狀態(tài)進行決策而不是自動理解應(yīng)用程序中的業(yè)務(wù)依賴。因此SCHED_FIFO適合那些執(zhí)行路徑清晰、時間預(yù)算明確、能夠合理阻塞或完成工作的實時任務(wù)但不適合沒有明確執(zhí)行邊界、可能無限循環(huán)占用 CPU 的線程。SCHED_RR則在相同實時優(yōu)先級的可運行線程之間引入輪轉(zhuǎn)時間片。一個線程使用完相應(yīng)的輪轉(zhuǎn)時間后可以被安排到同優(yōu)先級隊列中的其他線程之后讓同級線程獲得運行機會。不過輪轉(zhuǎn)時間片并不意味著SCHED_RR會自動公平地分配所有 CPU 時間。不同優(yōu)先級之間仍然存在搶占關(guān)系低優(yōu)先級線程不會僅僅因為等待時間較長就自動越過持續(xù)可運行的高優(yōu)先級實時線程。因此兩種策略的主要區(qū)別在于同優(yōu)先級實時線程之間的運行方式而不是簡單的“一個實時性更高、另一個實時性更低”。在實際控制系統(tǒng)中選擇哪種策略應(yīng)根據(jù)線程是否需要持續(xù)執(zhí)行、同級任務(wù)之間是否需要輪轉(zhuǎn)以及任務(wù)的時間約束決定。對于短周期控制線程常見做法是優(yōu)先分析SCHED_FIFO是否適合其執(zhí)行模型如果存在多個同級實時工作線程也應(yīng)評估SCHED_RR的輪轉(zhuǎn)行為對任務(wù)截止時間的影響。需要注意Linux 實時優(yōu)先級的可用范圍和配置還受到系統(tǒng)實現(xiàn)、權(quán)限、資源限制及相關(guān)配置影響。不能僅憑一個優(yōu)先級數(shù)值就判斷任務(wù)一定能夠按時運行。二、為什么高優(yōu)先級線程太多反而可能讓系統(tǒng)更不穩(wěn)定優(yōu)先級分配的首要問題是多個線程之間存在競爭而系統(tǒng)的 CPU 時間是有限的。假設(shè)一個控制系統(tǒng)有四個實時線程線程功能周期單次執(zhí)行時間A讀取傳感器狀態(tài)1 ms100 μsB運動控制計算1 ms250 μsC更新執(zhí)行器輸出1 ms100 μsD狀態(tài)監(jiān)控與數(shù)據(jù)整理5 ms400 μs以上數(shù)值只是用于說明調(diào)度關(guān)系的假設(shè)數(shù)據(jù)不代表任何實測平臺結(jié)果。如果 A、B、C、D 都設(shè)置成很高的實時優(yōu)先級而且沒有根據(jù)截止時間和數(shù)據(jù)依賴安排優(yōu)先級那么 D 可能在某個時刻搶占 B導(dǎo)致控制計算延遲也可能出現(xiàn) B 已經(jīng)完成計算卻因為 C 依賴的某項數(shù)據(jù)尚未準備好而等待。如果 D 實際上只是用于整理監(jiān)控數(shù)據(jù)那么將它與關(guān)鍵控制線程設(shè)置成相同甚至更高的優(yōu)先級通常需要非常明確的工程理由。否則它會把 CPU 資源分配給相對不緊急的工作增加關(guān)鍵任務(wù)的調(diào)度競爭。這里需要區(qū)分兩個問題任務(wù)是否重要與任務(wù)是否必須立即運行并不是同一回事。日志記錄可能對故障分析非常重要但大部分日志處理不需要在每個控制周期內(nèi)立即完成。網(wǎng)絡(luò)狀態(tài)上報可能關(guān)系到系統(tǒng)可觀測性但如果它不是當前控制閉環(huán)的一部分就不應(yīng)輕易搶占周期控制任務(wù)。相反如果某個傳感器采樣線程的輸出直接決定下一次控制計算那么它的調(diào)度時機就可能具有更嚴格的約束。因此優(yōu)先級規(guī)劃應(yīng)基于任務(wù)的時間要求而不是單純根據(jù)業(yè)務(wù)重要程度排序。另一個常見問題是高優(yōu)先級任務(wù)的 CPU 需求超過了系統(tǒng)能夠提供的時間預(yù)算。例如某個 CPU 上有兩個高優(yōu)先級周期任務(wù)二者的周期分別為 1 ms 和 2 ms執(zhí)行時間分別為 700 μs 和 800 μs。若它們必須在同一個 CPU 上運行并且這些執(zhí)行時間是每次都需要的工作量那么僅從長期平均 CPU 需求看第一個任務(wù)的利用率為 700 μs / 1000 μs即 70%。第二個任務(wù)的利用率為 800 μs / 2000 μs即 40%。兩者合計達到 110%。這意味著長期所需的 CPU 時間已經(jīng)超過單核能夠提供的時間即使優(yōu)先級分配得再合理也無法讓這兩個任務(wù)始終按時完成。這個例子說明優(yōu)先級不能替代可調(diào)度性分析。即使系統(tǒng)整體 CPU 使用率很低如果關(guān)鍵任務(wù)被限制在同一個核心上或者某些任務(wù)必須串行執(zhí)行局部 CPU 時間仍然可能不足。當然對于具有不同截止時間、阻塞關(guān)系、釋放模式和搶占規(guī)則的復(fù)雜任務(wù)集僅用平均利用率并不能完整判斷系統(tǒng)是否可調(diào)度。還需要分析任務(wù)釋放時間、執(zhí)行時間上界、優(yōu)先級關(guān)系、阻塞時間和截止時間并在實際平臺上進行驗證。對于實時系統(tǒng)來說提高優(yōu)先級不能解決計算資源不足也不能讓超出時間預(yù)算的任務(wù)自動變得可調(diào)度。三、優(yōu)先級反轉(zhuǎn)與資源依賴為什么關(guān)鍵線程可能被低優(yōu)先級任務(wù)拖住實時調(diào)度中最容易被誤判的問題之一是優(yōu)先級反轉(zhuǎn)。假設(shè)一個系統(tǒng)有三個線程高優(yōu)先級控制線程 H、中優(yōu)先級業(yè)務(wù)線程 M 和低優(yōu)先級輔助線程 L。線程 L 負責更新共享狀態(tài)并在臨界區(qū)內(nèi)持有互斥鎖。線程 H 需要獲取同一把鎖才能執(zhí)行本周期的控制計算。當 H 試圖獲取鎖時由于 L 尚未釋放鎖H 進入等待狀態(tài)。此時線程 M 恰好處于可運行狀態(tài)。如果調(diào)度關(guān)系允許 M 搶占 L那么 L 就無法及時完成臨界區(qū)H 也只能繼續(xù)等待。最終的結(jié)果是一個低優(yōu)先級線程間接決定了高優(yōu)先級控制線程何時能夠繼續(xù)運行。這就是優(yōu)先級反轉(zhuǎn)的典型表現(xiàn)。在這個場景中簡單地進一步提高 H 的優(yōu)先級通常并不能直接消除根因因為 H 已經(jīng)由于鎖競爭而阻塞。它不能越過互斥鎖直接訪問被保護的數(shù)據(jù)??梢詮娜齻€方面改善這種問題。首先縮短臨界區(qū)。不要讓低優(yōu)先級線程持有共享鎖時執(zhí)行文件 I/O、網(wǎng)絡(luò)通信、復(fù)雜計算或其他可能長時間阻塞的操作。臨界區(qū)越短潛在的鎖等待時間通常越容易控制。其次評估優(yōu)先級繼承等同步機制。在 Linux 支持相應(yīng)協(xié)議的情況下可以使用具有優(yōu)先級繼承能力的互斥鎖使持鎖線程在特定競爭情況下臨時繼承等待線程的優(yōu)先級減少中優(yōu)先級任務(wù)對鎖釋放的干擾。但優(yōu)先級繼承并不等于鎖等待時間為零。持鎖線程仍然需要完成臨界區(qū)工作如果臨界區(qū)本身執(zhí)行很久或者存在復(fù)雜的多鎖依賴等待仍可能影響截止時間。優(yōu)先級繼承也不是所有同步原語都默認提供的能力應(yīng)用必須檢查實際配置與平臺支持情況。第三減少關(guān)鍵控制路徑對共享鎖的依賴。對于一些狀態(tài)交換場景可以評估使用固定容量隊列、雙緩沖、不可變數(shù)據(jù)快照或其他適合數(shù)據(jù)訪問模式的機制。例如非實時線程負責準備一份新的配置快照控制線程只讀取已經(jīng)發(fā)布的有效版本。這樣可以避免控制線程為了讀取配置而等待一個執(zhí)行復(fù)雜操作的業(yè)務(wù)線程。但這種設(shè)計需要明確數(shù)據(jù)一致性、緩沖區(qū)生命周期和更新時機不能只因為使用了原子變量或雙緩沖就假定整個數(shù)據(jù)交換過程不存在競爭。除了互斥鎖資源依賴還可能來自其他地方例如控制線程等待傳感器線程準備輸入數(shù)據(jù)。運動控制任務(wù)等待通信線程完成數(shù)據(jù)交換。實時線程等待設(shè)備驅(qū)動返回結(jié)果。高優(yōu)先級任務(wù)等待一個由普通業(yè)務(wù)線程填充的隊列。多個實時線程在周期邊界同時訪問共享狀態(tài)。這些場景不一定都表現(xiàn)為傳統(tǒng)意義上的優(yōu)先級反轉(zhuǎn)但它們都會影響實際任務(wù)的完成時間。因此設(shè)計實時調(diào)度策略時不能只看每個線程的優(yōu)先級還必須畫清楚線程之間的數(shù)據(jù)依賴與阻塞關(guān)系。一個高優(yōu)先級任務(wù)如果依賴多個低優(yōu)先級任務(wù)那么這些依賴路徑就需要納入整體時間預(yù)算。四、怎樣為實時線程分配優(yōu)先級從截止時間與任務(wù)模型出發(fā)合理的優(yōu)先級設(shè)計應(yīng)當先建立任務(wù)模型而不是先給線程分配一個看起來足夠高的數(shù)值。至少需要明確以下信息任務(wù)的釋放模式周期性、事件觸發(fā)還是混合模式。任務(wù)的周期或最小到達間隔。任務(wù)每次執(zhí)行所需的 CPU 時間以及在目標平臺上的變化范圍。任務(wù)的相對截止時間。任務(wù)是否會阻塞以及可能等待哪些共享資源。任務(wù)與其他線程之間的數(shù)據(jù)依賴關(guān)系。任務(wù)運行在哪個 CPU以及是否會受到其他任務(wù)和中斷的影響。這里尤其需要區(qū)分任務(wù)周期與截止時間。周期為 1 ms 的任務(wù)不代表它一定有完整的 1 ms 可以執(zhí)行。如果它必須在下一次控制更新之前完成而執(zhí)行鏈路中還包含數(shù)據(jù)交換和輸出提交那么真正留給計算步驟的時間可能明顯小于 1 ms。方法一先根據(jù)截止時間建立任務(wù)優(yōu)先級關(guān)系對于某些獨立的、周期性、可搶占任務(wù)集如果任務(wù)執(zhí)行時間和截止時間滿足特定假設(shè)可以使用經(jīng)典的固定優(yōu)先級調(diào)度分析方法。一個常見思路是根據(jù)任務(wù)的周期或截止時間安排相對優(yōu)先級。但這種理論方法有適用條件不能直接套用到所有工業(yè)控制程序。實際任務(wù)可能存在資源共享、非周期事件、不同截止時間、驅(qū)動阻塞和跨線程同步因此需要根據(jù)具體任務(wù)模型選擇分析方法。工程上可以先明確哪些任務(wù)直接參與關(guān)鍵控制閉環(huán)哪些任務(wù)屬于輔助工作再結(jié)合周期、截止時間和數(shù)據(jù)依賴決定優(yōu)先級而不是把所有線程都設(shè)置為同一級別。方法二分析 CPU 時間預(yù)算對于每個 CPU 上的任務(wù)應(yīng)估算它們在一個時間窗口內(nèi)需要多少計算時間并將任務(wù)阻塞、搶占以及內(nèi)核和中斷活動納入分析。對于周期任務(wù)一個初步指標是 CPU 利用率[U_i\frac{C_i}{T_i}]其中(C_i) 表示任務(wù)每次執(zhí)行需要的 CPU 時間(T_i) 表示任務(wù)周期。如果同一個 CPU 上有多個周期任務(wù)可以先計算[U_{\mathrm{total}}\sum_i \frac{C_i}{T_i}]這個總利用率有助于發(fā)現(xiàn)明顯的資源不足問題但它不是任意實時任務(wù)集都能滿足截止時間的充分條件。任務(wù)釋放時刻、截止時間、優(yōu)先級、阻塞、搶占以及中斷開銷都可能改變實際調(diào)度結(jié)果。因此利用率分析應(yīng)該作為初步篩查而不是最終驗收結(jié)論。方法三明確實時任務(wù)與輔助任務(wù)的邊界在工業(yè)控制系統(tǒng)中建議優(yōu)先識別真正影響控制閉環(huán)的任務(wù)。例如周期性運動控制計算、必要的數(shù)據(jù)采集以及必須按時完成的輸出更新可能屬于關(guān)鍵路徑。而日志格式化、歷史數(shù)據(jù)整理、界面刷新、非關(guān)鍵網(wǎng)絡(luò)上報和報表生成等工作通??梢园才旁谳^低優(yōu)先級或普通調(diào)度線程中。但是否能夠降低優(yōu)先級仍需確認這些任務(wù)沒有承擔關(guān)鍵的實時依賴。例如如果日志線程還承擔實時故障信號的唯一轉(zhuǎn)發(fā)職責那么不能在不了解其功能的情況下直接降低其優(yōu)先級。更好的設(shè)計通常是將實時故障檢測和必要的安全響應(yīng)與后續(xù)日志存儲、網(wǎng)絡(luò)上報拆分讓必須及時完成的動作具有明確的執(zhí)行路徑。方法四用測量結(jié)果修正任務(wù)模型任務(wù)模型不能只依靠源代碼閱讀或開發(fā)人員估計。應(yīng)當通過應(yīng)用埋點、調(diào)度跟蹤和壓力測試記錄每個關(guān)鍵任務(wù)的實際啟動延遲、執(zhí)行時間、阻塞時間及完成情況。如果某個任務(wù)偶爾執(zhí)行時間明顯增加需要進一步分析是計算量變化、緩存和內(nèi)存訪問、鎖等待、設(shè)備調(diào)用還是被其他任務(wù)搶占。隨后再決定是否需要調(diào)整優(yōu)先級、改變 CPU 綁定方式或者優(yōu)化執(zhí)行路徑。優(yōu)先級調(diào)整應(yīng)當建立在可解釋的原因之上。盲目把某個線程提高一級可能只是讓其他線程的問題變得更加明顯并沒有消除根本原因。五、在 Linux 中怎樣檢查調(diào)度配置并驗證優(yōu)先級方案在完成初步任務(wù)分析之后可以從當前運行狀態(tài)入手檢查線程的調(diào)度策略、優(yōu)先級和 CPU 分布。首先查看所有線程的調(diào)度信息ps -eLo pid,tid,psr,cls,rtprio,pri,ni,stat,comm結(jié)合輸出確認關(guān)鍵控制線程的 TID、調(diào)度類別、實時優(yōu)先級和最近運行的 CPU。對于多線程程序不要只檢查進程主線程因為真正執(zhí)行周期控制工作的可能是單獨創(chuàng)建的線程。接著可以使用chrt查詢或調(diào)整指定線程的實時調(diào)度屬性。例如chrt -p TID這條命令用于查詢目標線程的調(diào)度策略與相關(guān)優(yōu)先級信息。修改實時優(yōu)先級時需要具備相應(yīng)權(quán)限并確認系統(tǒng)資源限制和平臺策略允許該操作。在生產(chǎn)環(huán)境中更推薦通過程序初始化代碼統(tǒng)一設(shè)置線程的調(diào)度屬性并檢查每次配置操作的返回結(jié)果。不能假定請求了實時策略就一定設(shè)置成功也不能忽略線程創(chuàng)建、繼承屬性和系統(tǒng)資源限制造成的差異。如果懷疑線程之間存在調(diào)度等待可以進一步采集調(diào)度事件。例如sudo trace-cmd record \ -e sched:sched_wakeup \ -e sched:sched_switch \ sleep 10 sudo trace-cmd report實際使用時應(yīng)確認當前內(nèi)核支持相關(guān)事件并盡量通過目標 TID 過濾記錄避免采集過多無關(guān)數(shù)據(jù)。跟蹤開銷也可能影響被測系統(tǒng)因此應(yīng)在測試環(huán)境中驗證采集方式并控制記錄時長和緩沖區(qū)大小。分析時重點關(guān)注以下情況目標線程已經(jīng)被喚醒卻沒有立即獲得 CPU。高優(yōu)先級線程持續(xù)運行其他任務(wù)長時間處于可運行狀態(tài)。目標線程運行后又進入阻塞等待共享鎖或其他資源。某個低優(yōu)先級線程持有共享資源導(dǎo)致高優(yōu)先級線程無法繼續(xù)執(zhí)行。多個實時線程集中運行在同一 CPU局部計算需求超過該核心的時間預(yù)算。調(diào)度跟蹤只能提供內(nèi)核調(diào)度行為的證據(jù)不能單獨解釋所有用戶態(tài)計算和設(shè)備等待。因此建議在關(guān)鍵代碼路徑中添加時間戳記錄任務(wù)計劃釋放時間、實際開始時間、關(guān)鍵步驟耗時和最終完成時間再將這些數(shù)據(jù)與內(nèi)核事件對應(yīng)起來。最后使用相同硬件、內(nèi)核、驅(qū)動、業(yè)務(wù)負載和測試時長對不同優(yōu)先級方案進行對照。除了平均延遲還應(yīng)記錄 P95、P99、P99.9、最大觀測延遲、樣本數(shù)量和截止時間違約次數(shù)。若測試過程中沒有發(fā)生超時應(yīng)明確測試持續(xù)時間和樣本規(guī)模不能把有限時間內(nèi)未觀察到超時理解為任何條件下都不會超時。對于望獲OS或其他實時 Linux 平臺同樣應(yīng)通過統(tǒng)一的任務(wù)模型和測試口徑驗證優(yōu)先級配置。系統(tǒng)平臺能夠提供調(diào)度能力但具體應(yīng)用是否滿足實時要求仍取決于任務(wù)設(shè)計、資源依賴、CPU 分配和實際負載??偨Y(jié)來說實時調(diào)度不是一場不斷提高優(yōu)先級數(shù)值的競賽。優(yōu)先級的作用是協(xié)調(diào)任務(wù)競爭而不是創(chuàng)造額外的計算資源。只有先明確任務(wù)周期、截止時間、執(zhí)行時間和資源依賴再合理安排優(yōu)先級、減少關(guān)鍵路徑阻塞并通過壓力測試驗證才能建立真正可靠的實時調(diào)度方案。