程調(diào)度核心解析:從nice值到CFS完全公平調(diào)度器)
1. 從排隊(duì)說起為什么進(jìn)程調(diào)度值得花一篇文章去講你打開電腦同時(shí)開著瀏覽器、編輯器、終端和一堆后臺(tái)服務(wù)系統(tǒng)依然流暢得像什么都沒發(fā)生一樣。你有沒有想過CPU 是怎么在這么多任務(wù)之間“分身”的答案就是進(jìn)程調(diào)度。但在 Linux 里調(diào)度并不是“輪著來”這么簡(jiǎn)單——它要回答幾個(gè)核心問題誰先跑跑多久憑什么這背后就是進(jìn)程優(yōu)先級(jí)和調(diào)度算法的設(shè)計(jì)。從早期 Linux 的 nice 值、時(shí)間片輪轉(zhuǎn)到 2.6 內(nèi)核引入的 O(1) 調(diào)度器再到后來取代它的 CFS 調(diào)度器整個(gè)演進(jìn)過程都是圍繞“公平”和“效率”這兩個(gè)目標(biāo)的博弈。這篇內(nèi)容我會(huì)從優(yōu)先級(jí)的概念開始講清楚接著拆解 nice 值如何參與計(jì)算再看 O(1) 調(diào)度器的核心設(shè)計(jì)最后落到現(xiàn)代 CFS 調(diào)度器怎么工作。文章還會(huì)穿插大量實(shí)操命令和排查技巧適合剛學(xué)完 Linux 基礎(chǔ)、想深入理解系統(tǒng)行為的開發(fā)者也適合在性能調(diào)優(yōu)時(shí)被“為什么這個(gè)進(jìn)程老搶 CPU”折磨的運(yùn)維朋友。我們要聊的是一條從“優(yōu)先級(jí)”開始的完整技術(shù)鏈路這條鏈路能解釋你在top里看到的NI列、ps -l里的PRI字段、甚至renice命令背后的全部原理。2. 先搞懂優(yōu)先級(jí)Linux 里到底誰說了算2.1 普通優(yōu)先級(jí)和實(shí)時(shí)優(yōu)先級(jí)是兩套體系Linux 的優(yōu)先級(jí)不是一個(gè)單一數(shù)字而是分成了兩個(gè)體系實(shí)時(shí)優(yōu)先級(jí)和普通優(yōu)先級(jí)。這兩個(gè)體系互不干擾但調(diào)度時(shí)的地位完全不同。實(shí)時(shí)優(yōu)先級(jí)范圍是 0 到 99數(shù)字越大優(yōu)先級(jí)越高。實(shí)時(shí)進(jìn)程的特點(diǎn)是“說跑就跑”內(nèi)核會(huì)給它專門的調(diào)度策略比如SCHED_FIFO和SCHED_RR它們不需要跟普通進(jìn)程搶時(shí)間片而是可以直接搶占 CPU。普通優(yōu)先級(jí)范圍是 100 到 139數(shù)字越小優(yōu)先級(jí)越高對(duì)應(yīng)我們常說的 nice 值從 -20 到 19。普通進(jìn)程使用的是SCHED_OTHER也叫SCHED_NORMAL策略大家一起瓜分 CPU 時(shí)間。這里有個(gè)容易踩坑的地方你以為優(yōu)先級(jí)就是top里看到的那個(gè)PR值其實(shí)它只是一個(gè)計(jì)算后的展示結(jié)果真正的優(yōu)先級(jí)計(jì)算要經(jīng)過好幾層轉(zhuǎn)換。后面我會(huì)專門講這個(gè)換算過程。2.2 為什么需要優(yōu)先級(jí)而不只是“輪流來”如果所有進(jìn)程都一樣重要那“每人跑一秒輪著來”也說得通。但現(xiàn)實(shí)顯然不是這樣。你的輸入法、鼠標(biāo)響應(yīng)、心跳檢測(cè)這些交互型進(jìn)程必須秒級(jí)響應(yīng)而一個(gè)大文件拷貝任務(wù)晚幾秒完成卻沒人關(guān)心。如果沒有優(yōu)先級(jí)機(jī)制一個(gè)持續(xù)占用 CPU 的后臺(tái)進(jìn)程完全可以餓死所有交互進(jìn)程——你敲一下鍵盤可能要等半分鐘才有反饋這誰受得了所以優(yōu)先級(jí)解決的是“資源分配的不對(duì)稱需求分配”問題。它讓內(nèi)核可以回答當(dāng) CPU 不夠用的時(shí)候先犧牲誰先保證誰。這就像一個(gè)餐廳在高峰期只有十個(gè)座位VIP 客人先進(jìn)普通客人排隊(duì)但排隊(duì)太久也不行得想辦法讓大家都能吃上飯。調(diào)度器就是要制定這套“排隊(duì)規(guī)則”。2.3 好那 nice 值是什么nice值是 Linux 最早引入的優(yōu)先級(jí)調(diào)整手段之一名字挺有意思你把自己的進(jìn)程優(yōu)先級(jí)調(diào)低把 CPU 讓給別人這叫“nice”。nice 值的范圍是 -20 到 19默認(rèn)是 0。數(shù)值越低優(yōu)先級(jí)越高越“不 nice”數(shù)值越高優(yōu)先級(jí)越低越“nice”。為什么要有負(fù)值因?yàn)橛行┻M(jìn)程確實(shí)需要搶占更多資源比如音頻合成、視頻編碼它們需要更低的 nice 值來獲得更高的調(diào)度權(quán)重。調(diào)整 nice 值需要權(quán)限普通用戶只能把 nice 值調(diào)高讓出自己的資源要調(diào)低搶別人資源必須有 root 權(quán)限。這個(gè)設(shè)計(jì)挺好理解——不能允許任何用戶隨意搶占資源否則系統(tǒng)公平性無從談起。在早期 Linux 2.4 及之前的內(nèi)核中nice 值直接決定了進(jìn)程能拿到的時(shí)間片長(zhǎng)度。一個(gè) nice 值為 0 的進(jìn)程和 nice 值為 19 的進(jìn)程兩者的時(shí)間片可能差出好幾倍。但這種方式有個(gè)天生的問題時(shí)間片長(zhǎng)度固定后進(jìn)程切換的粒度和交互響應(yīng)性很難同時(shí)兼顧。這個(gè)矛盾正是后來調(diào)度器反復(fù)重構(gòu)的根本原因。3. 經(jīng)典調(diào)度器時(shí)代時(shí)間片輪轉(zhuǎn)和它的問題3.1 從樸素循環(huán)隊(duì)列說起早期 Linux 用的是很樸素的調(diào)度方式每個(gè)進(jìn)程有一個(gè)固定時(shí)間片比如 10ms 到 200ms調(diào)度器維護(hù)一個(gè)循環(huán)隊(duì)列每次都選時(shí)間片最長(zhǎng)或者優(yōu)先級(jí)最高的進(jìn)程來跑。跑完一個(gè)時(shí)間片就放到隊(duì)尾繼續(xù)選下一個(gè)。這種方式實(shí)現(xiàn)簡(jiǎn)單在進(jìn)程數(shù)量少的時(shí)候也很好用。但進(jìn)程一多問題就來了假設(shè)你有 100 個(gè)進(jìn)程每個(gè)進(jìn)程分到 100ms 時(shí)間片那么一個(gè)進(jìn)程最長(zhǎng)要等 9.9 秒才能再輪到一次。這延遲放到交互場(chǎng)景里簡(jiǎn)直不可接受——你的編輯器光標(biāo)可能每隔好幾秒才動(dòng)一下。為了改善這個(gè)問題內(nèi)核把時(shí)間片越調(diào)越小但時(shí)間片太小又會(huì)導(dǎo)致頻繁的上下文切換白白浪費(fèi) CPU 在切換上的開銷。這就引出了調(diào)度器設(shè)計(jì)的核心矛盾響應(yīng)延遲要足夠低但切換開銷又要足夠小。這兩者在固定的時(shí)間片模式下幾乎不可兼得。3.2 O(n) 調(diào)度器的痛點(diǎn)在哪里在 2.4 內(nèi)核時(shí)代調(diào)度器遍歷所有進(jìn)程來找“最該運(yùn)行的那一個(gè)”時(shí)間復(fù)雜度是 O(n)。進(jìn)程少無所謂但服務(wù)器上可能同時(shí)跑著上千個(gè)線程每次調(diào)度都要掃描一遍全列表成本就已經(jīng)非??捎^了。更麻煩的是如果系統(tǒng)里的進(jìn)程大多是睡眠狀態(tài)比如等著 I/O調(diào)度器掃到它們的時(shí)候還要跳過這個(gè)跳過判斷本身也要花時(shí)間。而且當(dāng)時(shí)調(diào)度器和全局鎖的耦合比較深多核 CPU 想擴(kuò)展非常吃力。我印象很深的是曾經(jīng)在一臺(tái)雙核機(jī)器上跑編譯任務(wù)負(fù)載一上來系統(tǒng)就明顯卡頓連命令行輸入都遲鈍。后來排查了半天發(fā)現(xiàn)問題之一就是調(diào)度器掃描長(zhǎng)任務(wù)隊(duì)列時(shí)占用了大量鎖時(shí)間CPU 之間的競(jìng)爭(zhēng)非常嚴(yán)重。這段歷史是必要的鋪墊因?yàn)?O(1) 調(diào)度器的每一個(gè)設(shè)計(jì)點(diǎn)幾乎都是對(duì)著 O(n) 調(diào)度器的痛點(diǎn)來的。3.3 時(shí)間片計(jì)算中的數(shù)學(xué)你看到的數(shù)字是從哪來的2.4 內(nèi)核里時(shí)間片和 nice 值之間是個(gè)線性關(guān)系但不同內(nèi)核版本的換算系數(shù)還有差異。大致來說每個(gè)進(jìn)程的基礎(chǔ)時(shí)間片基于(20 - nice)計(jì)算nice 越小時(shí)間片越大。假設(shè)基礎(chǔ)時(shí)間片是 200ms那 nice 0 的進(jìn)程能跑 200msnice 19 的進(jìn)程可能只能跑 10ms??此坪侠淼屑?xì)一想這會(huì)讓低 nice 值的進(jìn)程大幅拉長(zhǎng)其他進(jìn)程的等待時(shí)間系統(tǒng)的整體交互體驗(yàn)會(huì)變得很差。后來有人提出過比例份額proportion share的思路但實(shí)現(xiàn)起來復(fù)雜不說還需要支持內(nèi)核搶占這在當(dāng)時(shí)的技術(shù)條件下問題不少。我提這一段是想說明調(diào)度器的每一步演進(jìn)背后都是對(duì)真實(shí)問題的權(quán)衡而不是拍腦袋出來的新功能。4. O(1) 調(diào)度器的核心設(shè)計(jì)從掃描到索引的進(jìn)化4.1 兩個(gè)隊(duì)列的巧妙結(jié)構(gòu)active 和 expiredO(1) 調(diào)度器在 2.6 內(nèi)核中亮相它最大的特點(diǎn)就是名字本身——“O(1)”意味著每次調(diào)度的時(shí)間是常數(shù)級(jí)不管系統(tǒng)有多少進(jìn)程選下一個(gè)進(jìn)程的耗時(shí)都一樣。它靠什么做到這一點(diǎn)答案是兩個(gè)優(yōu)先級(jí)隊(duì)列數(shù)組的結(jié)構(gòu)。調(diào)度器維護(hù)兩個(gè)隊(duì)列數(shù)組一個(gè)叫 active活躍隊(duì)列一個(gè)叫 expired過期隊(duì)列。每個(gè)數(shù)組有 140 個(gè)鏈表頭對(duì)應(yīng) 140 個(gè)優(yōu)先級(jí)0 到 139。當(dāng)進(jìn)程還有剩余時(shí)間片時(shí)它在 active 隊(duì)列里當(dāng)它時(shí)間片用完了就被挪到 expired 隊(duì)列并重新計(jì)算新的時(shí)間片。調(diào)度的時(shí)候只需要從 active 隊(duì)列里找到優(yōu)先級(jí)最高且非空的那個(gè)鏈表取出第一個(gè)進(jìn)程就行。找到最高優(yōu)先級(jí)非空鏈表的過程依賴一個(gè)bitmap——用 140 個(gè) bit 標(biāo)記每個(gè)優(yōu)先級(jí)是否有進(jìn)程在等待。內(nèi)核用一條__ffs指令快速找到第一個(gè)為 1 的 bit這個(gè)操作的耗時(shí)和進(jìn)程總數(shù)完全無關(guān)。這個(gè)設(shè)計(jì)很精妙值得反復(fù)品味。4.2 時(shí)間片的計(jì)算不再線性映射表的作用O(1) 調(diào)度器里進(jìn)程時(shí)間片不是直接由 nice 線性換算的而是通過一個(gè)映射表prio_to_timemax和prio_to_timemult來查表計(jì)算。它會(huì)把靜態(tài)優(yōu)先級(jí)轉(zhuǎn)換成兩個(gè)參數(shù)然后算出實(shí)際的時(shí)間片長(zhǎng)度。這么做的好處是時(shí)間片隨優(yōu)先級(jí)的變化不是簡(jiǎn)單的直線而是一條根據(jù)交互性需求調(diào)整的曲線反應(yīng)更平緩也不至于讓低優(yōu)先級(jí)進(jìn)程的時(shí)間片小到完全無法工作。舉個(gè)例子感受一下假設(shè) nice 0 的進(jìn)程每次能分到 100msnice 19 的進(jìn)程可能也能分到 5ms 左右。雖然差距依然明顯但至少低優(yōu)先級(jí)進(jìn)程不會(huì)因?yàn)闀r(shí)間片短到 1ms 以下而頻繁被切換出去。O(1) 調(diào)度器還引入了一個(gè)“交互進(jìn)程識(shí)別”機(jī)制——如果一個(gè)進(jìn)程經(jīng)常因?yàn)榈却?I/O 而睡眠它會(huì)被標(biāo)記為交互型進(jìn)程放到 active 隊(duì)列的更高優(yōu)先級(jí)位置保證它能頻繁被調(diào)度從而獲得更快的響應(yīng)。4.3 真正改善的是什么O(1) 調(diào)度器核心改善可以歸納為三點(diǎn)。第一調(diào)度決策時(shí)間恒定不再受進(jìn)程數(shù)影響第二多核之間的負(fù)載均衡有了專門機(jī)制調(diào)度器可以定期把進(jìn)程從一個(gè) CPU 遷移到另一個(gè) CPU減少 CPU 空閑第三交互型進(jìn)程能更頻繁地?fù)屨?CPU體驗(yàn)明顯提升。但話說回來O(1) 也有自己的問題。它格外依賴“交互進(jìn)程”這個(gè)啟發(fā)式判斷而啟發(fā)式判斷本質(zhì)上就是一種猜測(cè)——有時(shí)候猜錯(cuò)了就會(huì)出現(xiàn)某類進(jìn)程長(zhǎng)期得不到調(diào)度的情況。2004 年前后內(nèi)核社區(qū)已經(jīng)有人在討論調(diào)度器“公平性”和“響應(yīng)性”之間的平衡問題。直到 2.6.23CFS 調(diào)度器橫空出世這套基于時(shí)間片和優(yōu)先級(jí)的隊(duì)列結(jié)構(gòu)才正式退役。5. 現(xiàn)代 CFS 調(diào)度器把“公平”變成數(shù)學(xué)公式5.1 不再是“分時(shí)間片”而是“分配 CPU 比例”CFSCompletely Fair Scheduler完全公平調(diào)度器的思路和 O(1) 完全不同。它不強(qiáng)調(diào)時(shí)間片而是強(qiáng)調(diào)“權(quán)重”。每個(gè)進(jìn)程的權(quán)重由 nice 值通過一組查表計(jì)算得到nice 越低權(quán)重越大。調(diào)度目標(biāo)是把 CPU 時(shí)間按權(quán)重比例分配給所有可運(yùn)行進(jìn)程。有一個(gè)最簡(jiǎn)單的類比假設(shè)兩個(gè)進(jìn)程一個(gè)權(quán)重是 1一個(gè)權(quán)重是 3那它們對(duì) CPU 時(shí)間的使用比例就是 1:3。CFS 通過一個(gè)叫vruntime虛擬運(yùn)行時(shí)間的字段來記賬一個(gè)進(jìn)程實(shí)際運(yùn)行了 10ms它的 vruntime 增長(zhǎng)量不是 10ms而是10ms / 權(quán)重。這樣低權(quán)重的進(jìn)程 vruntime 漲得快高權(quán)重的漲得慢。調(diào)度器每次選 vruntime 最小的進(jìn)程來運(yùn)行就保證了所有進(jìn)程在虛擬時(shí)間上盡量“齊頭并進(jìn)”實(shí)際時(shí)間則按權(quán)重分配。這套邏輯漂亮就漂亮在它不需要啟發(fā)式判斷進(jìn)程是不是“交互型”一切只靠 vruntime 記賬。交互進(jìn)程因?yàn)榻?jīng)常睡眠vruntime 漲得慢醒來后自然會(huì)排在隊(duì)列前面從而獲得及時(shí)響應(yīng)。5.2 紅黑樹與進(jìn)程選擇為什么是 O(log n)CFS 用一棵紅黑樹來管理所有可運(yùn)行進(jìn)程鍵值就是 vruntime。要選出下一個(gè)跑誰就是取樹的最左節(jié)點(diǎn)——也就是 vruntime 最小的進(jìn)程。紅黑樹的插入和刪除都是 O(log n)所以 CFS 的調(diào)度復(fù)雜度不是 O(1)但已經(jīng)足夠快同時(shí)還能精確維護(hù)所有進(jìn)程的“相對(duì)公平”。紅黑樹的設(shè)計(jì)解決了 O(1) 的兩個(gè)大問題一是去掉了難以維護(hù)的交互進(jìn)程識(shí)別機(jī)制二是讓調(diào)度的公平性更精確、更可控。內(nèi)核參數(shù)sched_min_granularity_ns可以控制進(jìn)程運(yùn)行的最小粒度sched_latency_ns控制調(diào)度周期的目標(biāo)延遲。調(diào)這幾個(gè)參數(shù)能直接改變你對(duì)系統(tǒng)“流暢度”和“吞吐量”的感受。5.3 nice 值在 CFS 里的查表計(jì)算CFS 里 nice 值對(duì)應(yīng)權(quán)重并非線性遞增。一組典型數(shù)值是nice 0 對(duì)應(yīng)權(quán)重 1024nice -20 對(duì)應(yīng)權(quán)重 88761nice 19 對(duì)應(yīng)權(quán)重 15。每個(gè) nice 值上下差一級(jí)權(quán)重約相差 1.25 倍。這意味著 nice 從 0 調(diào)到 1CPU 權(quán)重下降 20% 左右從 0 調(diào)到 19權(quán)重相差幾百倍。所以 CFS 下的 nice 值影響和早期完全不同它不再直接換算時(shí)間片時(shí)長(zhǎng)而是影響進(jìn)程在調(diào)度周期里的時(shí)間占比。這個(gè)差異理解起來不復(fù)雜但很多朋友在調(diào)優(yōu)時(shí)習(xí)慣套用舊經(jīng)驗(yàn)結(jié)果和預(yù)期完全對(duì)不上。我實(shí)測(cè)過把某后臺(tái)編解碼任務(wù)的 nice 調(diào)低到 -10它在高負(fù)載下能明顯壓過其他普通進(jìn)程但系統(tǒng)整體響應(yīng)反而可能受影響——權(quán)重拉得太高CPU 資源幾乎被它吸走。5.4 調(diào)度周期、最小粒度、延遲三個(gè)關(guān)鍵參數(shù)寫 CFS 就繞不開這幾個(gè)可調(diào)參數(shù)。sched_latency_ns決定了一個(gè)調(diào)度周期的目標(biāo)長(zhǎng)度默認(rèn)在桌面內(nèi)核里通常是 6ms 到 24ms 之間它會(huì)根據(jù)可運(yùn)行進(jìn)程數(shù)量動(dòng)態(tài)調(diào)整。sched_min_granularity_ns是每個(gè)進(jìn)程至少運(yùn)行的時(shí)間避免進(jìn)程切換太頻繁。sched_wakeup_granularity_ns控制喚醒進(jìn)程是否立即搶占當(dāng)前進(jìn)程。調(diào)這三者的關(guān)系有點(diǎn)像調(diào)一個(gè)水池的進(jìn)水和出水如果sched_latency_ns太小進(jìn)程切換頻繁響應(yīng)好但吞吐下降如果sched_min_granularity_ns太大交互型進(jìn)程容易被長(zhǎng)耗時(shí)進(jìn)程擋住。我自己在調(diào)桌面響應(yīng)延遲時(shí)把sched_min_granularity_ns從默認(rèn)值降到約 1ms系統(tǒng)在高負(fù)載下的鼠標(biāo)卡頓就明顯少了但代價(jià)是整體吞吐率略微下降。6. 實(shí)操如何在命令行觀察和調(diào)整優(yōu)先級(jí)6.1top和ps里的優(yōu)先級(jí)信息怎么看在top里PR列是內(nèi)核實(shí)際使用的優(yōu)先級(jí)NI列是 nice 值VIRT、RES這些是內(nèi)存相關(guān)。注意普通進(jìn)程的PR和NI有對(duì)應(yīng)關(guān)系通常PR 20 NI但實(shí)時(shí)進(jìn)程會(huì)顯示為RT。比如一個(gè) nice 為 0 的普通進(jìn)程PR 通常是 20nice 調(diào)到 -5PR 就變成 15。ps -l命令給出的PRI字段也是類似含義NI字段直接顯示 nice 值。如果你執(zhí)行ps -eo pid,comm,ni,pri,rtprio,cls可以更詳細(xì)地看到進(jìn)程的調(diào)度類別和實(shí)時(shí)優(yōu)先級(jí)。這張表對(duì)快速定位“誰在搶 CPU”非常有用。6.2 調(diào)整 nice 值的三種常用方法啟動(dòng)時(shí)指定nice -n 5 ./my-program表示以 nice 5 啟動(dòng)。運(yùn)行中調(diào)整renice -n 5 -p PID但普通用戶只能調(diào)高自己的進(jìn)程調(diào)低需要 root。啟動(dòng)時(shí)用更底層的方式指定調(diào)度策略chrt命令可以設(shè)置SCHED_FIFO或SCHED_RR。比如chrt -f 50 ./realtime-task。需要提醒的是renice對(duì)線程組起作用如果你要調(diào)整某線程的具體優(yōu)先級(jí)得用chrt -p配合線程 ID 來操作。這里我也踩過坑用renice調(diào)了一個(gè)多線程應(yīng)用的 nice 值發(fā)現(xiàn)只有主線程生效子線程都有自己的調(diào)度參數(shù)線程級(jí)優(yōu)先級(jí)調(diào)整需要用別的方式。6.3 一個(gè)提升交互體驗(yàn)的實(shí)測(cè)方案我在一臺(tái)日常使用的 Linux 桌面機(jī)上做過一個(gè)測(cè)試后臺(tái)開著視頻轉(zhuǎn)碼任務(wù)占滿全部 CPU同時(shí)正常瀏覽網(wǎng)頁、編輯文檔。默認(rèn)情況下轉(zhuǎn)碼進(jìn)程會(huì)頻繁搶占 CPU鼠標(biāo)點(diǎn)擊明顯有延遲感。我把轉(zhuǎn)碼進(jìn)程的 nice 調(diào)高到 10讓出資源再把桌面環(huán)境相關(guān)的進(jìn)程 nice 調(diào)低到 -5 到 -10延遲感立刻減輕很多。核心原則就一句話前臺(tái)交互進(jìn)程應(yīng)該保持較高優(yōu)先級(jí)批處理任務(wù)應(yīng)該主動(dòng)“讓路”。7. 優(yōu)先級(jí)相關(guān)的常見問題與排查技巧7.1 為什么我調(diào)了 nice 值進(jìn)程還是搶占 CPU很多人調(diào)完成功但進(jìn)程依然搶得很兇原因通常有兩個(gè)。一是某些進(jìn)程走的是實(shí)時(shí)優(yōu)先級(jí)SCHED_FIFO或SCHED_RR它們的優(yōu)先級(jí)高于所有普通進(jìn)程nice 值根本無法影響它們的調(diào)度行為。二是進(jìn)程是多線程的你只調(diào)了主線程的 nice 值其他線程還保持默認(rèn)優(yōu)先級(jí)。排查時(shí)先看ps -eo pid,comm,cls,rtprio確認(rèn)調(diào)度類型再用taskset或chrt跟進(jìn)線程級(jí)設(shè)置。7.2 如何確認(rèn)某個(gè)進(jìn)程是不是實(shí)時(shí)進(jìn)程執(zhí)行chrt -p PID可以直接顯示該進(jìn)程的調(diào)度策略和實(shí)時(shí)優(yōu)先級(jí)。如果輸出顯示SCHED_FIFO或SCHED_RR基本就可以確定它是實(shí)時(shí)進(jìn)程。要注意調(diào)度策略不是只能在啟動(dòng)時(shí)設(shè)置運(yùn)行中也可以改但改的時(shí)候要有權(quán)限而且操作實(shí)時(shí)進(jìn)程優(yōu)先級(jí)時(shí)要格外小心不要把一個(gè)普通進(jìn)程設(shè)成最高實(shí)時(shí)優(yōu)先級(jí)否則它可能會(huì)餓死系統(tǒng)里其他所有進(jìn)程。7.3 頻繁切換會(huì)不會(huì)導(dǎo)致系統(tǒng)變慢會(huì)的。CFS 的調(diào)度周期過短會(huì)導(dǎo)致進(jìn)程頻繁上下文切換切換本身涉及保存和恢復(fù)寄存器、刷新 TLB、更新內(nèi)核棧等操作這些開銷雖然單次很小但頻率上來之后吞吐量一定會(huì)下降??梢杂胿mstat觀察cscontext switch列如果它持續(xù)飆高同時(shí)系統(tǒng)吞吐也不理想說明調(diào)度粒度太細(xì)了。解決方案就是調(diào)大sched_min_granularity_ns實(shí)測(cè)可以顯著降低切換次數(shù)。7.4 CPU 綁核是干擾優(yōu)先級(jí)判斷的一個(gè)大坑如果兩個(gè)進(jìn)程綁在同一顆 CPU 上即使其中一個(gè)優(yōu)先級(jí)更高也可能因?yàn)榱硪粋€(gè)進(jìn)程不釋放 CPU 而排隊(duì)等待。這時(shí)你看到的不是調(diào)度策略問題而是 CPU 資源競(jìng)爭(zhēng)問題。排查步驟是先看taskset -p PID確認(rèn)進(jìn)程的 CPU 親和性再結(jié)合mpstat看每顆 CPU 的使用率。很多時(shí)候“調(diào)了優(yōu)先級(jí)沒效果”的真相是 CPU 綁死在了某個(gè)核心上。7.5 怎樣在負(fù)載高的時(shí)候快速定位“誰在搶 CPU”top進(jìn)入后按P按 CPU 排序能看到當(dāng)前最吃 CPU 的進(jìn)程。接著ps -eo pid,comm,ni,pri,rtprio,cls查看它們的優(yōu)先級(jí)屬性。如果發(fā)現(xiàn)有大量普通進(jìn)程在競(jìng)爭(zhēng)可以用perf sched或者pidstat做更細(xì)的采樣分析。pidstat可以按進(jìn)程顯示 CPU 使用率、上下文切換次數(shù)和優(yōu)先級(jí)變化定位問題比看 top 更高效。8. 我的實(shí)操心得與經(jīng)驗(yàn)總結(jié)這篇文章從 nice 值講到 O(1) 調(diào)度器再講到 CFS跨度雖然大但主線其實(shí)只有一個(gè)Linux 是怎么決定 CPU 時(shí)間該給誰。我自己的體會(huì)是理解調(diào)度器最好的方式不是背參數(shù)而是多制造“沖突場(chǎng)景”去觀察。比如故意用高負(fù)載任務(wù)加低 nice 值任務(wù)在同一系統(tǒng)里跑再記錄它們的響應(yīng)時(shí)間差比讀十篇文檔都有用。另一個(gè)經(jīng)常被忽略的點(diǎn)是調(diào)度器參數(shù)不是調(diào)完就萬事大吉。內(nèi)核每個(gè)版本對(duì) CFS 的默認(rèn)參數(shù)都有微調(diào)我見過有些朋友直接復(fù)制網(wǎng)上的調(diào)優(yōu)參數(shù)結(jié)果在該內(nèi)核版本上反而引發(fā)卡頓。所以我建議凡是涉及調(diào)度參數(shù)的修改都要先做好記錄改完觀察一段時(shí)間確認(rèn)沒有副作用再固化到配置里。最后再分享個(gè)臨時(shí)調(diào)試技巧在排查 CPU 競(jìng)爭(zhēng)問題時(shí)可以用echo直接修改/proc/sys/kernel/sched_min_granularity_ns這類文件來動(dòng)態(tài)測(cè)試效果改完不必重啟也不怕改錯(cuò)——系統(tǒng)重啟后會(huì)自動(dòng)恢復(fù)默認(rèn)值。這種方式試參數(shù)非常高效尤其適合你在不確定該調(diào)哪個(gè)值的時(shí)候快速驗(yàn)證。要記住的是正式環(huán)境里持久化配置還是要寫進(jìn) sysctl 文件并且每次改動(dòng)附上備注方便以后的自己回頭看。