核從啟動(dòng)到實(shí)戰(zhàn):實(shí)驗(yàn)驅(qū)動(dòng)學(xué)習(xí)與問題排查)
1. 為什么我要開這個(gè)Linux內(nèi)核專欄搞了快十年底層開發(fā)從單片機(jī)裸機(jī)一路做到內(nèi)核態(tài)驅(qū)動(dòng)踩過的坑能填滿一整個(gè)機(jī)房。這些年陸陸續(xù)續(xù)帶過不少新人發(fā)現(xiàn)一個(gè)特別普遍的現(xiàn)象大部分人學(xué)Linux內(nèi)核的方式是“哪里不會(huì)點(diǎn)哪里”——今天看兩頁內(nèi)存管理明天翻三章文件系統(tǒng)后天又去啃調(diào)度器源碼。結(jié)果就是每個(gè)子系統(tǒng)都摸過一遍但真遇到線上問題比如一個(gè)內(nèi)存泄漏、一次IO卡頓、一個(gè)莫名其妙的panic腦子里完全沒有排查路徑。這個(gè)專欄就是來解決這個(gè)問題的。它不是一本“內(nèi)核源碼導(dǎo)讀”也不是那種把教科書目錄抄一遍的“知識(shí)大綱”。我想做的事情很具體用一條完整的、有先后依賴關(guān)系的路徑把Linux內(nèi)核從啟動(dòng)到運(yùn)行的核心機(jī)制串起來講清楚每一篇都對應(yīng)一個(gè)可以動(dòng)手驗(yàn)證的實(shí)驗(yàn)或者一個(gè)真實(shí)場景下的問題排查案例。適合誰看如果你寫過C語言、用過Linux命令行、對操作系統(tǒng)的基本概念進(jìn)程、內(nèi)存、文件有模糊印象那這個(gè)專欄就是為你準(zhǔn)備的。不需要你讀過任何內(nèi)核源碼也不需要你手上有服務(wù)器集群——一臺(tái)能跑虛擬機(jī)的筆記本就夠了。如果你已經(jīng)是內(nèi)核老手這里面的實(shí)操記錄和排查思路或許也能給你一些參考畢竟很多細(xì)節(jié)是文檔里不會(huì)寫的。整個(gè)專欄的規(guī)劃邏輯是“先跑起來再拆開看”。我不會(huì)一上來就講頁表結(jié)構(gòu)或者CFS調(diào)度算法而是先讓你編譯一個(gè)能跑的內(nèi)核、寫一個(gè)最簡單的模塊、用工具看到內(nèi)核在干什么。有了感性認(rèn)識(shí)之后再往深處挖每個(gè)子系統(tǒng)的設(shè)計(jì)原理和實(shí)現(xiàn)細(xì)節(jié)。這樣你學(xué)到的不是孤立的知識(shí)點(diǎn)而是一張互相勾連的網(wǎng)——知道內(nèi)存分配失敗會(huì)怎么影響文件系統(tǒng)知道調(diào)度延遲會(huì)怎么導(dǎo)致網(wǎng)絡(luò)超時(shí)。2. 專欄整體架構(gòu)與學(xué)習(xí)路徑設(shè)計(jì)2.1 從“會(huì)用”到“懂原理”的階梯式安排很多人學(xué)內(nèi)核失敗根本原因在于跳過了“觀察”這一步。你連內(nèi)核長什么樣、跑起來什么狀態(tài)都不知道直接去讀mm/memory.c那跟看天書沒區(qū)別。所以這個(gè)專欄的章節(jié)順序是經(jīng)過刻意設(shè)計(jì)的分成四個(gè)階段。第一階段是環(huán)境與工具。你得先有一個(gè)能編譯、能調(diào)試、能跑實(shí)驗(yàn)的內(nèi)核環(huán)境。我會(huì)講怎么選內(nèi)核版本、怎么配置編譯選項(xiàng)、怎么用QEMU起一個(gè)最小系統(tǒng)、怎么用GDB單步跟蹤內(nèi)核代碼。這些內(nèi)容看起來基礎(chǔ)但實(shí)際動(dòng)手時(shí)你會(huì)發(fā)現(xiàn)坑非常多——比如編譯出來的內(nèi)核起不來、GDB斷點(diǎn)打不上、模塊加載報(bào)版本不匹配。這些我都會(huì)給出具體的排查方法。第二階段是核心子系統(tǒng)拆解。從內(nèi)核啟動(dòng)流程開始到內(nèi)存管理、進(jìn)程調(diào)度、中斷處理、文件系統(tǒng)、設(shè)備驅(qū)動(dòng)每個(gè)子系統(tǒng)單獨(dú)成篇。但注意我不是按教科書順序講的而是按“依賴關(guān)系”講的。比如你必須先理解內(nèi)存管理的基本框架才能看懂進(jìn)程地址空間是怎么映射的你必須先理解中斷和異常機(jī)制才能看懂驅(qū)動(dòng)里的等待隊(duì)列是怎么工作的。第三階段是調(diào)試與排查實(shí)戰(zhàn)。這部分會(huì)大量使用ftrace、perf、eBPF、crash dump這些工具針對真實(shí)場景下的問題——內(nèi)存泄漏、死鎖、IO性能瓶頸、內(nèi)核panic——給出完整的排查路徑。每個(gè)案例都會(huì)從“現(xiàn)象”開始一步步推導(dǎo)到“根因”再給出“修復(fù)方案”。第四階段是進(jìn)階與擴(kuò)展。包括內(nèi)核模塊開發(fā)、系統(tǒng)調(diào)用hook、內(nèi)核熱補(bǔ)丁、實(shí)時(shí)性優(yōu)化這些偏工程化的內(nèi)容。這部分不是必須的但如果你想把內(nèi)核知識(shí)用到實(shí)際產(chǎn)品里這些技能遲早要掌握。2.2 為什么選擇“實(shí)驗(yàn)驅(qū)動(dòng)”而不是“源碼驅(qū)動(dòng)”市面上講內(nèi)核的書和課程大部分是“源碼驅(qū)動(dòng)”的——打開一個(gè)函數(shù)逐行解釋它在干什么。這種方式不是不好但它有一個(gè)致命缺陷你學(xué)完之后不知道這些代碼在什么場景下會(huì)被觸發(fā)。內(nèi)核代碼里有大量的條件分支和邊界處理如果你沒有實(shí)際觸發(fā)過那些場景你根本記不住那些分支是干什么的。我選擇“實(shí)驗(yàn)驅(qū)動(dòng)”的思路每一篇都會(huì)先拋出一個(gè)具體問題或者一個(gè)可觀察的現(xiàn)象然后帶著這個(gè)問題去讀代碼、做實(shí)驗(yàn)、看結(jié)果。比如講內(nèi)存管理我不會(huì)一上來就講伙伴系統(tǒng)和slab分配器而是先寫一個(gè)會(huì)觸發(fā)OOM的小程序讓你親眼看到內(nèi)核在內(nèi)存耗盡時(shí)做了什么然后再去分析背后的分配邏輯和回收機(jī)制。這樣你學(xué)到的每一個(gè)知識(shí)點(diǎn)都綁定了一個(gè)具體的場景記憶會(huì)牢固得多。提示這個(gè)專欄的所有實(shí)驗(yàn)都可以在一臺(tái)普通開發(fā)機(jī)上完成不需要特殊硬件。我會(huì)盡量用QEMU模擬器來降低環(huán)境門檻但涉及性能分析的部分建議在物理機(jī)上做虛擬機(jī)的時(shí)間精度和性能計(jì)數(shù)器會(huì)有偏差。2.3 各章節(jié)之間的依賴關(guān)系與學(xué)習(xí)節(jié)奏建議整個(gè)專欄的章節(jié)不是完全線性的但有一些硬性依賴。比如你必須先學(xué)完“內(nèi)核編譯與調(diào)試環(huán)境搭建”才能做后面的實(shí)驗(yàn)?zāi)惚仨毾壤斫狻斑M(jìn)程地址空間”才能看懂“缺頁異常處理”你必須先理解“中斷上下文”才能看懂“驅(qū)動(dòng)中的并發(fā)控制”。我建議的學(xué)習(xí)節(jié)奏是每周吃透一個(gè)子系統(tǒng)不要貪多。每個(gè)子系統(tǒng)包含三到五篇文章第一篇通常是概念和框架第二篇是核心數(shù)據(jù)結(jié)構(gòu)和算法第三篇是實(shí)操實(shí)驗(yàn)第四篇是問題排查案例。如果你時(shí)間充??梢愿鏊袑?shí)驗(yàn)如果時(shí)間緊張至少要把每篇的“實(shí)操要點(diǎn)”和“常見問題”部分看完那些是精華。另外我強(qiáng)烈建議你準(zhǔn)備一個(gè)筆記本專門記錄實(shí)驗(yàn)過程中遇到的報(bào)錯(cuò)和解決方法。內(nèi)核開發(fā)最耗時(shí)的不是寫代碼而是排查環(huán)境問題和理解報(bào)錯(cuò)信息。你記錄下來的每一個(gè)坑以后都會(huì)變成你的經(jīng)驗(yàn)值。3. 核心子系統(tǒng)拆解與實(shí)操要點(diǎn)3.1 內(nèi)核啟動(dòng)流程從加電到第一個(gè)進(jìn)程內(nèi)核啟動(dòng)是整個(gè)系統(tǒng)最神秘的部分之一。BIOS/UEFI做完硬件初始化之后把控制權(quán)交給內(nèi)核入口然后內(nèi)核要在短短幾百毫秒內(nèi)完成解壓、頁表建立、內(nèi)存初始化、中斷控制器配置、調(diào)度器啟動(dòng)、掛載根文件系統(tǒng)、啟動(dòng)init進(jìn)程這一系列操作。任何一個(gè)環(huán)節(jié)出問題你看到的就是一塊黑屏或者一行莫名其妙的報(bào)錯(cuò)。我會(huì)用QEMU配合GDB從內(nèi)核入口點(diǎn)開始單步跟蹤讓你看到每一個(gè)關(guān)鍵階段的寄存器狀態(tài)和內(nèi)存布局變化。重點(diǎn)講清楚幾個(gè)核心問題內(nèi)核解壓后的重定位是怎么做的早期頁表是怎么建立的為什么需要臨時(shí)頁表start_kernel函數(shù)里那幾十個(gè)初始化調(diào)用的順序?yàn)槭裁床荒軄yinit進(jìn)程的地址空間是怎么從內(nèi)核空間切換到用戶空間的實(shí)操部分會(huì)帶你修改內(nèi)核啟動(dòng)參數(shù)觀察不同參數(shù)對啟動(dòng)過程的影響。比如調(diào)整mem參數(shù)限制可用內(nèi)存看看內(nèi)核在內(nèi)存不足時(shí)怎么處理調(diào)整console參數(shù)切換控制臺(tái)輸出理解內(nèi)核日志系統(tǒng)的初始化時(shí)機(jī)。這些實(shí)驗(yàn)?zāi)茏屇銓?dòng)流程的理解從“知道”變成“感受到”。注意修改啟動(dòng)參數(shù)時(shí)一定要保留一個(gè)能正常啟動(dòng)的配置作為備份。我有一次把root參數(shù)寫錯(cuò)了結(jié)果內(nèi)核找不到根文件系統(tǒng)直接panic花了半小時(shí)才想起來是參數(shù)問題。3.2 內(nèi)存管理從物理頁到虛擬地址空間內(nèi)存管理是內(nèi)核里最復(fù)雜也最重要的子系統(tǒng)沒有之一。它向上支撐進(jìn)程地址空間向下管理物理硬件中間還要處理緩存、回收、遷移、壓縮等各種策略。很多內(nèi)核問題的根源都在內(nèi)存管理上比如內(nèi)存泄漏、碎片化、OOM、頁表錯(cuò)誤。我會(huì)從最基礎(chǔ)的物理頁分配講起把伙伴系統(tǒng)的合并與分裂過程用圖示和實(shí)驗(yàn)展示出來。然后講slab/slub分配器怎么在物理頁之上構(gòu)建對象緩存為什么kmalloc和vmalloc的行為差異那么大。接著講虛擬內(nèi)存管理包括頁表結(jié)構(gòu)、缺頁異常處理、反向映射、頁面回收。最后講進(jìn)程地址空間包括mmap、brk、棧擴(kuò)展、寫時(shí)復(fù)制這些機(jī)制。實(shí)驗(yàn)部分會(huì)寫一個(gè)內(nèi)核模塊直接調(diào)用alloc_pages、kmalloc、vmalloc來分配內(nèi)存然后通過/proc接口觀察內(nèi)存使用情況的變化。還會(huì)用/proc/pagetypeinfo和/proc/buddyinfo來觀察內(nèi)存碎片化的過程。這些實(shí)驗(yàn)?zāi)茏屇阒庇^地看到內(nèi)核內(nèi)存管理的行為而不是停留在概念層面。分配方式適用場景最大尺寸是否連續(xù)典型API伙伴系統(tǒng)物理頁分配4MBorder 10物理連續(xù)alloc_pagesslub小對象分配8KB物理連續(xù)kmallocvmalloc大塊虛擬內(nèi)存幾乎無限虛擬連續(xù)vmalloc頁緩存文件數(shù)據(jù)緩存按頁物理連續(xù)read/write3.3 進(jìn)程調(diào)度與并發(fā)控制誰在什么時(shí)候跑進(jìn)程調(diào)度決定了哪個(gè)任務(wù)在哪個(gè)CPU上跑多久直接影響系統(tǒng)的響應(yīng)速度和吞吐量。很多人覺得調(diào)度器就是“按優(yōu)先級排隊(duì)”實(shí)際遠(yuǎn)比這復(fù)雜——CFS用紅黑樹維護(hù)虛擬運(yùn)行時(shí)間實(shí)時(shí)調(diào)度用優(yōu)先級位圖還有負(fù)載均衡、CPU親和性、調(diào)度域這些機(jī)制在背后工作。我會(huì)先講清楚調(diào)度的基本概念什么是調(diào)度類、什么是調(diào)度實(shí)體、什么是調(diào)度延遲。然后深入CFS的實(shí)現(xiàn)解釋vruntime是怎么計(jì)算的、紅黑樹是怎么維護(hù)的、min_vruntime是怎么更新的。接著講實(shí)時(shí)調(diào)度和截止時(shí)間調(diào)度以及它們和CFS的優(yōu)先級關(guān)系。最后講多核負(fù)載均衡包括調(diào)度域?qū)哟?、?fù)載計(jì)算、遷移決策。并發(fā)控制是另一個(gè)重災(zāi)區(qū)。內(nèi)核里到處是自旋鎖、互斥鎖、RCU、原子操作用錯(cuò)了就是死鎖或者數(shù)據(jù)競爭。我會(huì)用具體的代碼示例展示每種鎖的適用場景和誤用后果。比如在中斷上下文里用互斥鎖會(huì)導(dǎo)致睡眠在持有自旋鎖時(shí)調(diào)用可能睡眠的函數(shù)會(huì)導(dǎo)致死鎖。這些規(guī)則聽起來簡單但實(shí)際寫代碼時(shí)很容易踩坑。實(shí)驗(yàn)部分會(huì)寫一個(gè)多線程的內(nèi)核模塊用不同的鎖保護(hù)共享數(shù)據(jù)然后用lockdep檢測潛在的鎖依賴問題。還會(huì)用ftrace的wakeup和sched_switch事件來觀察調(diào)度行為看看一個(gè)高優(yōu)先級任務(wù)是怎么搶占低優(yōu)先級任務(wù)的。3.4 文件系統(tǒng)與IO棧數(shù)據(jù)怎么從磁盤到內(nèi)存文件系統(tǒng)是用戶態(tài)和內(nèi)核態(tài)交互最頻繁的子系統(tǒng)之一。你每次讀寫文件、創(chuàng)建目錄、查看屬性背后都涉及VFS層、具體文件系統(tǒng)層、塊設(shè)備層、IO調(diào)度層、驅(qū)動(dòng)層的協(xié)同工作。理解這個(gè)棧的每一層對于排查IO性能問題和數(shù)據(jù)一致性問題至關(guān)重要。我會(huì)從VFS的四個(gè)核心對象講起超級塊、索引節(jié)點(diǎn)、目錄項(xiàng)、文件。解釋它們之間的關(guān)系和生命周期。然后講頁緩存和回寫機(jī)制為什么寫文件不是立即落盤、fsync到底做了什么、臟頁什么時(shí)候會(huì)被回收。接著講塊設(shè)備層的請求隊(duì)列和IO調(diào)度算法包括noop、deadline、cfq、bfq這些調(diào)度器的適用場景。最后講具體的文件系統(tǒng)實(shí)現(xiàn)以ext4為例講日志、延遲分配、多塊分配這些特性。實(shí)驗(yàn)部分會(huì)用fio工具做IO性能測試對比不同IO調(diào)度器和掛載參數(shù)下的性能差異。還會(huì)用blktrace抓取塊設(shè)備層的請求軌跡分析IO合并和排序的效果。這些實(shí)驗(yàn)?zāi)茏屇銓O棧的理解從“知道有這些層”變成“知道每層在干什么”。提示做IO性能測試時(shí)一定要用裸設(shè)備或者回環(huán)設(shè)備不要在根文件系統(tǒng)上直接測否則測試結(jié)果會(huì)被其他IO干擾而且有數(shù)據(jù)損壞的風(fēng)險(xiǎn)。4. 調(diào)試工具鏈與問題排查方法論4.1 內(nèi)核調(diào)試的三大支柱printk、ftrace、crash內(nèi)核調(diào)試不像用戶態(tài)程序那么方便你不能隨便加斷點(diǎn)、不能隨便打印變量、不能隨便暫停系統(tǒng)。但內(nèi)核提供了三套非常強(qiáng)大的調(diào)試機(jī)制掌握了它們大部分問題都能定位。printk是最基礎(chǔ)的但很多人用不好。我會(huì)講清楚日志級別、pr_*宏的用法、動(dòng)態(tài)調(diào)試、printk的性能影響。更重要的是我會(huì)講怎么在系統(tǒng)崩潰時(shí)還能看到最后的日志——比如配置pstore或者ramoops把日志保存在內(nèi)存里重啟后還能讀出來。ftrace是內(nèi)核自帶的跟蹤框架可以跟蹤函數(shù)調(diào)用、中斷、調(diào)度、內(nèi)存分配等幾乎所有事件。我會(huì)講怎么配置function_graph跟蹤器看函數(shù)調(diào)用圖怎么用kprobe動(dòng)態(tài)插入跟蹤點(diǎn)怎么用trace-cmd和kernelshark做可視化分析。這些工具用好了比加一百個(gè)printk都管用。crash是分析內(nèi)核轉(zhuǎn)儲(chǔ)文件的工具。當(dāng)內(nèi)核panic時(shí)如果配置了kdump會(huì)把內(nèi)存鏡像保存下來然后用crash工具分析。我會(huì)講怎么配置kdump、怎么用crash查看調(diào)用棧、怎么分析內(nèi)存中的數(shù)據(jù)結(jié)構(gòu)、怎么找到崩潰的根因。4.2 性能分析的利器perf和eBPF性能問題往往比功能問題更難排查因?yàn)槟憧床坏矫黠@的報(bào)錯(cuò)只能感覺到“慢”。perf和eBPF是解決這類問題的兩把利器。perf可以采樣CPU性能計(jì)數(shù)器告訴你時(shí)間花在哪個(gè)函數(shù)、哪個(gè)指令、哪個(gè)緩存行上。我會(huì)講perf top、perf record、perf report的基本用法以及怎么用perf stat看CPI、緩存命中率、分支預(yù)測失敗率這些指標(biāo)。更重要的是我會(huì)講怎么用perf的調(diào)用圖功能找到性能瓶頸的調(diào)用鏈。eBPF是近年來最火的內(nèi)核技術(shù)它允許你在內(nèi)核里安全地運(yùn)行自定義程序不需要修改內(nèi)核源碼或者加載模塊。我會(huì)講怎么用bpftrace寫一行命令統(tǒng)計(jì)某個(gè)函數(shù)的調(diào)用次數(shù)和延遲怎么用bcc工具集分析IO、網(wǎng)絡(luò)、內(nèi)存的性能問題。這些工具能讓你在不重啟系統(tǒng)、不修改代碼的情況下動(dòng)態(tài)地觀察內(nèi)核行為。4.3 常見問題速查表與排查思路下面這張表是我這些年排查內(nèi)核問題時(shí)總結(jié)的速查表覆蓋了最常見的幾類問題。每個(gè)問題都給出了可能的原因和排查方向但具體定位還需要結(jié)合日志和工具分析?,F(xiàn)象可能原因排查工具關(guān)鍵命令系統(tǒng)卡死無響應(yīng)死鎖、中斷風(fēng)暴、內(nèi)存耗盡sysrq、crashecho t /proc/sysrq-trigger內(nèi)核panic空指針、越界訪問、斷言失敗crash、dmesgcrash vmlinux vmcore內(nèi)存泄漏未釋放的kmalloc、頁緩存增長kmemleak、slabinfocat /proc/slabinfoIO性能差調(diào)度器不合適、碎片化、緩存命中低blktrace、iostatblktrace -d /dev/sda調(diào)度延遲高優(yōu)先級反轉(zhuǎn)、CPU飽和、鎖競爭perf、ftraceperf sched latency網(wǎng)絡(luò)丟包緩沖區(qū)滿、中斷親和性、軟中斷延遲dropwatch、perfperf trace -e skb:kfree_skb排查內(nèi)核問題的核心思路是“先定位子系統(tǒng)再定位具體函數(shù)最后定位代碼行”。不要一上來就盯著源碼看先用工具確定問題出在哪個(gè)子系統(tǒng)然后逐步縮小范圍。比如系統(tǒng)卡死先用sysrq看所有CPU的調(diào)用棧確定是死鎖還是死循環(huán)如果是死鎖再用lockdep看鎖依賴關(guān)系最后才去讀相關(guān)代碼。注意使用sysrq觸發(fā)轉(zhuǎn)儲(chǔ)之前確保已經(jīng)配置了kernel.sysrq參數(shù)并且知道怎么解讀輸出。我有一次在生產(chǎn)環(huán)境誤觸了sysrq導(dǎo)致系統(tǒng)直接重啟幸好是測試環(huán)境。5. 從專欄到實(shí)戰(zhàn)如何把內(nèi)核知識(shí)用起來5.1 內(nèi)核模塊開發(fā)的最小閉環(huán)學(xué)內(nèi)核最終要落到“能寫”上。寫一個(gè)能加載、能運(yùn)行、能卸載的內(nèi)核模塊是檢驗(yàn)?zāi)憷斫獬潭鹊淖詈梅绞?。我?huì)帶你從零寫一個(gè)字符設(shè)備驅(qū)動(dòng)包含模塊初始化、設(shè)備注冊、文件操作、中斷處理、內(nèi)存分配、并發(fā)控制這些核心要素。這個(gè)驅(qū)動(dòng)會(huì)實(shí)現(xiàn)一個(gè)簡單的“計(jì)數(shù)器”設(shè)備用戶態(tài)可以通過read讀取當(dāng)前計(jì)數(shù)通過write設(shè)置計(jì)數(shù)通過ioctl控制計(jì)數(shù)器的啟停??雌饋砗唵蔚婕暗闹R(shí)點(diǎn)非常多怎么注冊字符設(shè)備、怎么實(shí)現(xiàn)file_operations、怎么處理用戶態(tài)和內(nèi)核態(tài)的數(shù)據(jù)拷貝、怎么用自旋鎖保護(hù)共享數(shù)據(jù)、怎么在中斷里更新計(jì)數(shù)。寫完這個(gè)驅(qū)動(dòng)之后你會(huì)對內(nèi)核模塊的開發(fā)流程有一個(gè)完整的認(rèn)識(shí)。然后我會(huì)講怎么給模塊加參數(shù)、怎么用sysfs暴露接口、怎么處理模塊依賴、怎么用modprobe自動(dòng)加載。這些技能在實(shí)際工作中非常實(shí)用比如你要給某個(gè)硬件寫驅(qū)動(dòng)或者要給內(nèi)核加一個(gè)自定義的系統(tǒng)調(diào)用。5.2 內(nèi)核問題排查的實(shí)戰(zhàn)案例光有工具不夠還得知道怎么用。我會(huì)用三個(gè)完整的實(shí)戰(zhàn)案例展示從問題發(fā)現(xiàn)到根因定位的全過程。第一個(gè)案例是“內(nèi)存泄漏”。一個(gè)長時(shí)間運(yùn)行的服務(wù)內(nèi)存占用持續(xù)增長最終觸發(fā)OOM。我會(huì)用kmemleak和slabinfo定位到泄漏的對象類型然后用ftrace跟蹤分配和釋放的調(diào)用棧找到?jīng)]有釋放的那條路徑。第二個(gè)案例是“IO卡頓”。一個(gè)數(shù)據(jù)庫服務(wù)偶爾出現(xiàn)幾秒鐘的IO延遲飆升。我會(huì)用blktrace抓取塊設(shè)備層的請求軌跡分析IO合并和排序的效果然后用perf看IO提交路徑上的熱點(diǎn)函數(shù)最終發(fā)現(xiàn)是文件系統(tǒng)日志提交導(dǎo)致的周期性阻塞。第三個(gè)案例是“調(diào)度延遲”。一個(gè)實(shí)時(shí)性要求很高的應(yīng)用偶爾出現(xiàn)幾十毫秒的延遲。我會(huì)用perf sched看調(diào)度延遲的分布用ftrace的sched_switch事件看上下文切換的細(xì)節(jié)最終發(fā)現(xiàn)是某個(gè)中斷處理程序執(zhí)行時(shí)間過長導(dǎo)致實(shí)時(shí)任務(wù)被延遲調(diào)度。每個(gè)案例都會(huì)給出完整的命令和輸出解讀你可以跟著做一遍感受一下真實(shí)的排查過程。這些經(jīng)驗(yàn)是看書學(xué)不到的只有親手做過才能記住。5.3 持續(xù)學(xué)習(xí)與社區(qū)資源內(nèi)核在持續(xù)演進(jìn)每個(gè)版本都有新的特性和改動(dòng)。保持學(xué)習(xí)的最好方式是訂閱內(nèi)核郵件列表、關(guān)注核心開發(fā)者的博客、定期閱讀LKML的摘要。但不要試圖讀完所有內(nèi)容那是不可能的。我的建議是跟著你關(guān)心的子系統(tǒng)走。比如你做存儲(chǔ)就關(guān)注塊設(shè)備層和文件系統(tǒng)的改動(dòng)你做網(wǎng)絡(luò)就關(guān)注網(wǎng)絡(luò)棧和驅(qū)動(dòng)模型的改動(dòng)。另外動(dòng)手寫代碼永遠(yuǎn)是最有效的學(xué)習(xí)方式。你可以從修改內(nèi)核的一個(gè)小bug開始比如修復(fù)一個(gè)拼寫錯(cuò)誤、改進(jìn)一段注釋、優(yōu)化一個(gè)邊界檢查。這些改動(dòng)雖然小但能讓你熟悉內(nèi)核的代碼風(fēng)格、提交流程、review過程。慢慢地你就可以嘗試修復(fù)更復(fù)雜的問題甚至提交新功能。這個(gè)專欄的每一篇都會(huì)盡量保持更新如果某個(gè)內(nèi)核版本有重大改動(dòng)我會(huì)在對應(yīng)章節(jié)里補(bǔ)充說明。但內(nèi)核知識(shí)的核心部分——內(nèi)存管理、調(diào)度、并發(fā)、文件系統(tǒng)——這些基礎(chǔ)機(jī)制在很長一段時(shí)間內(nèi)不會(huì)有大變化所以你先掌握這些再去跟進(jìn)新特性會(huì)輕松很多。最后分享一個(gè)我自己的習(xí)慣每次遇到一個(gè)新的內(nèi)核問題我都會(huì)在筆記本上畫一張圖把涉及的子系統(tǒng)、數(shù)據(jù)結(jié)構(gòu)、調(diào)用路徑都畫出來。畫圖的過程就是梳理思路的過程畫完之后往往就能找到排查方向。這個(gè)習(xí)慣幫我解決了很多看似無解的問題你也可以試試。