Linux任務調度延時分析工具getdelays


1 前言

1.1 什么是getdelays工具?

getdelays工具是一個用戶態工具,這個工具可以顯示出指定pid或者tgid對應的調度延時數據,包括用戶態內核態運行的時間,在就緒隊列上等待運行的時間,以及等待IO等資源的延遲時間。這些數據是通過netlink機制從內核獲取,最終呈現給用戶態。

1.2 如何獲取這個工具呢?

這個工具是和具體的Linux內核代碼配套的,其代碼在Linux內核目錄樹的的tools/accounting/getdelays.c 文件中(對的,這個工具只有一個文件),在主機上可以使用如下方式進行編譯:

gcc -I/usr/src/linux/include getdelays.c -o getdelays

如果需要交叉編譯需要參考如下步驟:

#1 進入源碼目錄編譯內核
make ARCH=arm64 CROSS_COMPILE=/$HOME/$CROSS/aarch64-linux-gnu-gcc menuconfig
make ARCH=arm64 CROSS_COMPILE=/$HOME/$CROSS/aarch64-linux-gnu-gcc all
#2 安裝頭文件
make ARCH=arm64 CROSS_COMPILE=/$HOME/$CROSS/aarch64-linux-gnu-gcc headers_install
#3 交叉編譯
/$HOME/$CROSS/aarch64-linux-gnu-gcc -I../../usr/include getdelays.c -o getdelays

1.3 這個工具如何使用呢?

一般的使用方式如下:

getdelays [-t tgid] [-p pid] [-c cmd...]

例如,獲取pid為72的任務自啟動后的延時情況:

./getdelays -d -p 72
print delayacct stats ON
PID     72


CPU             count     real total  virtual total    delay total  delay average
                  260      241000000      246467570       17551751          0.068ms
IO              count    delay total  delay average
                    0              0              0ms
SWAP            count    delay total  delay average
                    0              0              0ms
RECLAIM         count    delay total  delay average
                    0              0              0ms
THRASHING       count    delay total  delay average
                    0              0              0ms

上面的有效輸出有12行,其中前面2行打印的是內核配置與pid信息;后面10行分別是任務各個維度的調度或者延遲信息,其中CPU這一個維度表示任務CPU調度相關的信息;而IO、SWAP、RECLAIM、THRASHING分別表示的是任務由於其他資源而阻塞延時的信息。

2 getdelays的輸出字段

在1.3節我們看到getdelays一個簡單的用例輸出了多條相關信息,有CPU,IO, SWAP, RECLAIM以及THRASHING。這些信息大致可歸納為兩類:CPU維度和delay延時。

2.1 cpu維度

CPU count      real total   virtual total     delay total   delay average
內核中的來源  task->sched_info.pcount  task->utime + task->stime  task->se.sum_exec_runtime  task->sched_info.run_delay  task->cpu_delay_total/1000000/task->cpu_count

count:來自於內核中任務的task->sched_info.pcount字段,表示任務調度運行的次數,這個字段依賴於CONFIG_SCHEDSTATS=y配置。

任務在每次即將調度運行前通過sched_info_arrive()函數來對這個即將運行的任務task->sched_info.pcount++。

real total:等於內核中任務的task->utime + task->stime之和,表示任務在用戶態+內核態運行時間。

內核中定義了TICK時鍾中斷,以HZ頻率觸發;在TICK時鍾中斷中會檢查當前任務current(idle任務除外)處於何種上下文,如果是用戶態則會將task->utime增加一個TICK的時間(轉換為ns);否則如果是內核態(中斷上下文不會統計)則會為task->stime增加一個TICK時間。

因此,從原理上來說task->utime和task->stime實際上是對任務運行時間的一個采樣統計方式。

virtual total:來自於內核中任務調度實體的task->se.sum_exec_runtime字段,這個字段統計的是任務調度實體task->se在CPU上的運行時間。

與task->utime和task->stime的統計方式不同,task->se.sum_exec_runtime是基於調度實體來統計的;它的更新時機也更多,除了在TICK時鍾中斷更新外,還會在dequeue_entity()、put_prev_task()等內核點進行更新,也就是說只要發生調度切換這個字段也會更新;還有一點不同是,它不是按照TICK粒度來統計的,而是通過task->se.sum_exec_runtime += (rq->clock_task - curr->exec_start)這個表達式來計算的,curr表示當前運行隊列上的正在運行的調度實體。

其中task->se.exec_start是在主調度函數中對通過pick_next_task()函數來進行初始化為當前時間戳,表示這個任務開始運行的時間戳,每次更新task->se.sum_exec_runtime時,也會同時更新task->se.exec_start字段。

而rq->clock_task則是通過update_rq_clock()函數進行更新,這個函數在內核的許多調度點都會進行更新,其時間源來自於sched_clock()。

delay total:來自於內核中任務的task->sched_info.run_delay字段,這個字段用以統計一個任務就緒后在運行隊列等待運行的時間,這個字段依賴於CONFIG_SCHEDSTATS=y內核配置。

其計算方法是在任務加入到就緒隊列時記錄一個時間戳到t->sched_info.last_queued字段,在任務被調度運行時內核調用sched_info_arrive()函數統計任務在就緒隊列上等待調度運行的時間。另外有一種特殊情況,如果任務在cpu上運行,但是期間被搶占,這時候被搶占開始到下次再次被調度運行的時間也要累計到t->sched_info.run_delay字段。

2.2 task->delays資源維度

內核如果使能了CONFIG_TASK_DELAY_ACCT=y配置,則會在struct task_struct結構中增加一個struct task_delay_info *delays字段。這個字段是struct task_delay_info 結構,顧名思義,這個結構主要用於記錄任務的延遲信息。

如下所示,這個結構成員的lock用於保護結構體,flags用於標識任務阻塞的原因;

其它字段則是記錄不同延遲事件的信息,總共有blkio、swapin、freepages和thrashing 這4種不同的延遲類型。

#ifdef CONFIG_TASK_DELAY_ACCT
struct task_delay_info {
        raw_spinlock_t  lock;
        unsigned int    flags;  /* Private per-task flags */

        u64 blkio_start;        /* Shared by blkio, swapin */
        u64 blkio_delay;        /* wait for sync block io completion */
        u64 swapin_delay;       /* wait for swapin block io completion */
        u32 blkio_count;        /* total count of the number of sync block */
                                /* io operations performed */
        u32 swapin_count;       /* total count of the number of swapin block */
                                /* io operations performed */

        u64 freepages_start;
        u64 freepages_delay;    /* wait for memory reclaim */

        u64 thrashing_start;
        u64 thrashing_delay;    /* wait for thrashing page */

        u32 freepages_count;    /* total count of memory reclaim */
        u32 thrashing_count;    /* total count of thrash waits */
};
#endif

IO 

count  delay total  delay average

task->delays->blkio_count

task->delays->blkio_delay

SWAP  count  delay total  delay average

task->delays->swapin_count

task->delays->swapin_delay

RECLAIM  count  delay total  delay average

task->delays->freepages_count

task->delays->freepages_delay

THRASHING  count  delay total  delay average

task->delays->thrashing_count

task->delays->thrashing_delay

IO delay:IO delay中count與delay total來自於內核中的task->delays->blkio_count和task->delays->blkio_delay,表示一個任務task等待IO資源而阻塞的次數和任務等待IO阻塞的時間。

在IO資源可用時調用delayacct_blkio_end()計算blkio_start到blkio_end之間的時間。時間戳都是通過ktime_get_ns()函數獲取的

什么時候blkio_start?什么時候又blkio_end呢?

在主調度函數__schedule()中,如果任務prev因為IO阻塞,即prev->in_iowait不為0而調度出去,這時候就會調用delayacct_blkio_start()將當前時間戳記錄下來:

current->delays->blkio_start = ktime_get_ns()

而在這個阻塞任務由於IO資源可用而被喚醒時,內核調用delayacct_blkio_end()函數將這段阻塞睡眠的時間累加到task->delays->blkio_delay字段,並對task->delays->blkio_count++ 。

SWAP delay:SWAP delay中的count和delay total來自於內核中的task->delays->swapin_count與task->delays->swapin_delay字段,表示一個任務task訪問的內存在交換設備上時產生的swapin次數和任務等待內存swapin的延遲時間。

這兩個字段和上面的blkio_count、blkio_delay一樣也是統計的delayacct_blkio_start與delayacct_blkio_end()之間的時間,區別在於swapin_count與swapin_delay在進入blockio前會先在do_swap_page()函數中為發生swapin的任務current->delays->flags設置DELAYACCT_PF_SWAPIN標志;這樣在delayacct_blkio_end()函數中就會通過任務的current->delays->flags標志是否設置了DELAYACCT_PF_SWAPIN而決定統計swapin_count與swapin_delay。

RECLAIM delay:RECLAIM delay中的count和delay total來自於內核中的task->delays->freepages_count與task->delays->freepages_delay字段,表示一個任務task分配內存時產生的內存回收次數和任務等待內存回收的延遲時間。

在內核如果內存不足時會調用do_try_to_free_pages()來進行內存回收(如回收頁緩存)。在進行頁面回收前調用delayacct_freepages_start()將當前時間戳記錄到current->delays->freepages_start;在頁面回收完成后調用delayacct_freepages_end()來將該任務上下文進行的頁面回收次數和頁面回收消耗的時間累積到task->delays->freepages_count和task->delays->freepages_delay。 

THRASHING delay:THRASHING delay中的count和delay total來自於內核中的task->delays->thrashing_count與task->delays->thrashing_delay字段,表示一個任務task在訪問剛剛被加入非活躍狀態緩存頁(頁顛簸)的次數和等待時間。

在函數wait_on_page_bit_common()中,如果等待的page滿足如下條件,說明這個頁發生了頁顛簸,則調用delayacct_thrashing_start()函數記錄當前時間戳到task->delays->thrashing_start字段;等到這個page可用時再調用delayacct_thrashing_end()函數統計當前任務task發生頁顛簸的次數和由於頁顛簸而等待的時間。

if (bit_nr == PG_locked && !PageSwapBacked(page) &&
        !PageUptodate(page) && PageWorkingset(page))


免責聲明!

本站轉載的文章為個人學習借鑒使用,本站對版權不負任何法律責任。如果侵犯了您的隱私權益,請聯系本站郵箱yoyou2525@163.com刪除。



 
粵ICP備18138465號   © 2018-2025 CODEPRJ.COM