透過(guò)Tracepoint理解內(nèi)核 - 調(diào)度器框架和性能
靜態(tài)tracepoint預(yù)埋在內(nèi)核的關(guān)鍵位置, 通過(guò)這些預(yù)埋的tracepoint, 可以比較容易梳理出相關(guān)模塊的框架及主要流程. 相比于直接鉆到scheduler的實(shí)現(xiàn)細(xì)節(jié)中去, 我們先通過(guò)tracepoint及其相關(guān)工具去理解實(shí)現(xiàn)背后的邏輯, 細(xì)節(jié)總是不停變化, 而分析方法往往相對(duì)固定, 也更容易沉淀下來(lái).
Tracepoint分類
通過(guò)perf命令可以列出系統(tǒng)所有的tracepoint:
核心tracepoint
sched_switch
sched_wakeup
sched_waking
sched_migrate_task
Stat類型
該類型的tracepoint額外帶有delay的時(shí)間
sched_stat_blocked
sched_stat_iowait
sched_stat_runtime
sched_stat_sleep
sched_stat_wait
其他
sched_kthread_stop, sched_kthread_stop_ret. 在kthread_stop時(shí)產(chǎn)生, 一般不是scheduler性能調(diào)試的重點(diǎn)
sched_move_numa, sched_swap_numa, sched_stick_numa. NUMA相關(guān), 從性能分析角度上看, 它們必須在我們的checklist中, 一定程度可以把它們當(dāng)作是異常(USE)
sched_pi_setprio. 用于實(shí)現(xiàn)rt_mutex的優(yōu)先級(jí)繼承, 比如用在futex上.
sched_process_exec, sched_process_exit, sched_process_fork, sched_process_free. 進(jìn)程相關(guān)的主要事件
sched_process_hang. 進(jìn)程hang
sched_process_wait. 等子進(jìn)程的狀態(tài)變化
sched_wait_task. 等待其他任務(wù)unschedule, 比如用于ptrace.
sched_wake_idle_without_ipi. 如果target cpu上的任務(wù)設(shè)置了TIF_POLLING_NRFLAG標(biāo)記 (只有idle進(jìn)程會(huì)設(shè)置), 這樣idle進(jìn)程自己去poll TIF_NEED_RESCHED, 這樣就不用發(fā)ipi中斷去通知了
sched_wakeup_new. 同sched_wakeup, 但針對(duì)的是新創(chuàng)建的任務(wù)
核心Tracepoint
sched_switch
當(dāng)調(diào)度器決定schedule另一個(gè)task運(yùn)行的時(shí)候, 也就是任務(wù)切換的時(shí)候, 會(huì)觸發(fā)該tracepoint. 核心邏輯如下:
我們稍微關(guān)注以下context_switch里面的切棧操作:
注意這里的__switch_to_asm傳入了prev, 又返回了prev, 看似沒(méi)有必要, 但是因?yàn)閏ontext_switch函數(shù)涉及到2個(gè)task, 在切棧之前是A, 切棧之后就變成B了
對(duì)于切棧前的task A來(lái)說(shuō), prev指的就是A本身
對(duì)于切棧后的task B來(lái)說(shuō), prev指的還必須是A, switch到B之后還需要更新A的信息. 這里通過(guò)函數(shù)調(diào)用巧妙解決了2個(gè)task之間變量的傳遞.
ULK引入3個(gè)task來(lái)解釋switch_to, 我認(rèn)為反而復(fù)雜了.
sched_wakeup / sched_waking
內(nèi)核會(huì)通過(guò)try_to_wake_up把任務(wù)喚醒, 這會(huì)涉及到這sched_wakeup和sched_waking兩個(gè)tracepoint.
上面需要關(guān)注的點(diǎn):
可以喚醒current task
喚醒on_rq的task比較直接, 在sched_waking和sched_wakeup之間的時(shí)間非常短
當(dāng)需要遷移到其他cpu時(shí)會(huì)有2種方案
通過(guò)ipi給target cpu發(fā)送中斷, 在中斷處理函數(shù)中完成wakeup的后面部分
直接在當(dāng)前cpu上操作target cpu, 所以需要先執(zhí)行rq_lock操作, 可能會(huì)有鎖沖突
從上面可以看出, sched_waking和sched_wakeup在wakeup task過(guò)程中肯定都會(huì)發(fā)生, sched_waking事件在ttwu開(kāi)始的時(shí)候觸發(fā), 而sched_wakeup在ttwu結(jié)束的時(shí)候觸發(fā). 一般情況下, 這2個(gè)tracepoint觸發(fā)的時(shí)間非??拷? 但是不排除中間會(huì)有較大gap.
sched_migrate_task
從資源的角度看, 只有系統(tǒng)中存在多個(gè)同類資源(這里是cpu), 為了最大化資源利用率, 就會(huì)涉及到migration. 從性能角度看, 這個(gè)的影響是比較大的, 也是性能調(diào)試的時(shí)候必須關(guān)注的, migration有沒(méi)有及時(shí), migration會(huì)不會(huì)太多 (locality).
【文章福利】小編推薦自己的Linux內(nèi)核技術(shù)交流群:【749907784】整理了一些個(gè)人覺(jué)得比較好的學(xué)習(xí)書籍、視頻資料共享在群文件里面,有需要的可以自行添加哦!?。。ê曨l教程、電子書、實(shí)戰(zhàn)項(xiàng)目及代碼)? ? ?


零聲白金VIP體驗(yàn)卡(含基礎(chǔ)架構(gòu)/高性能存儲(chǔ)/golang/QT/音視頻/Linux內(nèi)核)課程:

Stat類型
為了使用stat類型的tracepoint, 我們需要先enable.
stat_iowait / stat_sleep / stat_blocked
stat_sleep用于記錄TASK_INTERRUPTIBLE的時(shí)間
stat_blocked用于記錄TASK_UNINTERRUPTIBLE的時(shí)間
stat_iowait用于iowait的場(chǎng)景, 這種情況下stat_iowait和stat_blocked值是一樣的
stat_wait
stat_wait和上面的stat不一樣的地方在于, stat_wait更反映調(diào)度器本身的執(zhí)行情況.
wait的起始時(shí)間wait_start. 任務(wù)狀態(tài)切到runnable, 但是不能馬上在cpu上執(zhí)行
task被搶占了, 那么wait_start就是搶占點(diǎn). put_prev_entity并且prev->on_rq成立
task喚醒的時(shí)候, 從enqueue_entity進(jìn)入
wait的結(jié)束時(shí)間
任務(wù)馬上要在cpu上執(zhí)行了, set_next_entity
任務(wù)enqueue后壓根沒(méi)能在該cpu上執(zhí)行就被dequeue了, update_stats_dequeue
stat_runtime
記錄任務(wù)的執(zhí)行時(shí)間, 包括runtime, vruntime
Scheduler框架
__schedule()的主要邏輯
性能調(diào)試
即使不是調(diào)度器的開(kāi)發(fā)者, 有的時(shí)候也需要能夠?qū)φ{(diào)度器進(jìn)行調(diào)試, 比如應(yīng)用開(kāi)發(fā)者或者系統(tǒng)管理員, 升級(jí)內(nèi)核后性能退化, 修改線程模型后性能不滿足預(yù)期等, 最終可能只需要?jiǎng)幽硞€(gè)調(diào)度器的參數(shù)而已, 但是前提是能夠定位到這個(gè)參數(shù).
sched map
只要抓取sched:sched_switch一個(gè)tracepoint, 就可以抓到系統(tǒng)所有的切換事件, 以下perf sched map的輸出:
前面每列代表一個(gè)cpu, 后面2列是事件發(fā)生的時(shí)間戳和任務(wù)縮寫的映射
點(diǎn)(.)表示cpu在idle
星號(hào)(*)表示有事件發(fā)生
sched timehist
該命令可以獲得task的wait time, 特別地, 還能拿到sch delay. timehist統(tǒng)計(jì)的sch delay是通過(guò)sched_switch和sched_wakeup計(jì)算出來(lái)的, 而不是上面的stat_wait.
sched latency
這里的delay同timehist的sch delay.
perf inject
通過(guò)關(guān)聯(lián)以下2個(gè)tracepoint, 我們可以得到任務(wù)sleep的時(shí)長(zhǎng)及其對(duì)應(yīng)的callchain
sched_iowait/sleep/blocked. 獲得sleep的時(shí)長(zhǎng)
sched_switch. 獲得調(diào)用棧
其他
這里列出一些調(diào)試的想法, 暫時(shí)沒(méi)有整理和一一展開(kāi)
性能調(diào)試要考慮工具的開(kāi)銷, 比如perf的開(kāi)銷是否會(huì)影響到應(yīng)用的性能. 我們可以使用eBPF重寫上面的perf的功能, eBPF因?yàn)槟軌蛟趦?nèi)核中直接聚合, 開(kāi)銷相比perf會(huì)小
雖然tracepoint能提供更多更完整的調(diào)試信息, 但是其他的統(tǒng)計(jì)工具比如schedstat等對(duì)調(diào)試也會(huì)有幫助, 很多時(shí)候只能用這些一直在搜集的信息, 而不是所有場(chǎng)景都能復(fù)現(xiàn)然后上去通過(guò)tracepoint搜集信息的
以上涉及的工具都還是文本界面的, 圖形界面的工具會(huì)更有優(yōu)勢(shì). 文本的好處是可以再加工, 圖像的好處是更直觀, 更容易發(fā)現(xiàn)問(wèn)題
和scheduler相關(guān)的性能問(wèn)題主要是兩個(gè)方面, 一是怎么定位應(yīng)用程序的off-cpu, 二是scheduler自身的影響, 都有一些相對(duì)固定的方法
有了這些tracepoint以及動(dòng)態(tài)添加的kprobe, 我們很容易拿到應(yīng)用程序schedule相關(guān)的信息, 比如在context switch in/out時(shí)收集信息, 就可以生成帶callchain的off-cpu flamegraph
如果某個(gè)cpu忙應(yīng)該看到什么現(xiàn)象, 我們可以去獲取cpu runqueue的長(zhǎng)度
如果task的某個(gè)函數(shù)執(zhí)行時(shí)間過(guò)長(zhǎng), 我們可以檢查它是在cpu上執(zhí)行慢, 還是在等資源. 如果是調(diào)度不及時(shí), 我們可以看到當(dāng)時(shí)它runnable的時(shí)長(zhǎng), 以及其他cpu的狀態(tài)
如果我們已經(jīng)有了cpu視角和task視角, 我們看到大量cpu idle而只有某個(gè)task在跑, 那么一種合理的推測(cè)是該task是否阻塞其他task了
調(diào)試其實(shí)就是把很多現(xiàn)象關(guān)聯(lián)起來(lái), 也就是說(shuō)孤立地去看一種現(xiàn)象往往收獲不大. 一般來(lái)說(shuō)我們可以通過(guò)時(shí)間軸把這些事件關(guān)聯(lián)起來(lái), 從資源的角度(比如每個(gè)cpu的在任意時(shí)間的使用情況), 從消費(fèi)者的角度(比如每個(gè)進(jìn)程的運(yùn)行狀態(tài)/路徑)
如果某個(gè)cpu忙其他cpu閑會(huì)有什么現(xiàn)象, 以每個(gè)cpu為視角, 通過(guò)時(shí)間軸把所有的cpu關(guān)聯(lián)起來(lái), 使用不同的顏色表示runqueue的長(zhǎng)度, 這樣生成的圖可以很容易看出migration是否及時(shí), 這樣的資源利用圖是非常有必要的, 有點(diǎn)類似htop, 但是更加精細(xì)
原文作者:J.FW
