<acronym id="s8ci2"><small id="s8ci2"></small></acronym>
<rt id="s8ci2"></rt><rt id="s8ci2"><optgroup id="s8ci2"></optgroup></rt>
<acronym id="s8ci2"></acronym>
<acronym id="s8ci2"><center id="s8ci2"></center></acronym>
0
  • 聊天消息
  • 系統消息
  • 評論與回復
登錄后你可以
  • 下載海量資料
  • 學習在線課程
  • 觀看技術視頻
  • 寫文章/發帖/加入社區
會員中心
創作中心

完善資料讓更多小伙伴認識你,還能領取20積分哦,立即完善>

3天內不再提示

到底什么是調度延遲

Linux閱碼場 ? 來源:Linux內核之旅 ? 作者:Linux內核之旅 ? 2022-08-01 11:08 ? 次閱讀

本次圈定的性能指標是調度延遲,那首要的目標就是看看到底什么是調度延遲,調度延遲是保證每一個可運行進程都至少運行一次的時間間隔,翻譯一下,是指一個task的狀態變成了TASK_RUNNING,然后從進入 CPU 的runqueue開始,到真正執行(獲得 CPU 的執行權)的這段時間間隔。

需要說明的是調度延遲在 Linux Kernel 中實現的時候是分為兩種方式的:面向task和面向rq,我們現在關注的是task層面。

那么runqueue和調度器的一個sched period的關系就顯得比較重要了。首先來看調度周期,調度周期的含義就是所有可運行的task都在CPU上執行一遍的時間周期,而Linux CFS中這個值是不固定的,當進程數量小于8的時候,sched period就是一個固定值6ms,如果runqueue數量超過了8個,那么就保證每個task都必須運行一定的時間,這個一定的時間還叫最小粒度時間,CFS的默認最小粒度時間是0.75ms,使用sysctl_sched_min_granularity保存,sched period是通過下面這個內核函數來決定的:

/** The idea is to set a period in which each task runs once.** When there are too many tasks (sched_nr_latency) we have to stretch* this period because otherwise the slices get too small.** p = (nr <= nl) ? l : l*nr/nl*/static u64 __sched_period(unsigned long nr_running){    if (unlikely(nr_running > sched_nr_latency))        return nr_running * sysctl_sched_min_granularity;    else        return sysctl_sched_latency;}

nr_running就是可執行task數量

那么一個疑問就產生了,這個不就是調度延遲scheduling latency嗎,并且每一次計算都會給出一個確定的調度周期的值是多少,但是這個調度周期僅僅是用于調度算法里面,因為這里的調度周期是為了確保runqueue上的task的最小調度周期,也就是在這段時間內,所有的task至少被調度一次,但是這僅僅是目標,而實際是達不到的。因為系統的狀態、task的狀態、task的slice等等都是不斷變化的,周期性調度器會在每一次tick來臨的時候檢查當前task的slice是否到期,如果到期了就會發生preempt搶,而周期性調度器本身的精度就很有限,不考慮 hrtick 的情況下,我們查看系統的時鐘頻率:


$ grep CONFIG_HZ /boot/config-$(uname -r)

# CONFIG_HZ_PERIODIC is not set

# CONFIG_HZ_100 is not set

CONFIG_HZ_250=y

# CONFIG_HZ_300 is not set

# CONFIG_HZ_1000 is not set

CONFIG_HZ=250

僅僅是250HZ,也就是4ms一次時鐘中斷,所以都無法保證每一個task在CPU上運行的slice是不是它應該有的slice,更不要說保證調度周期了,外加還有wakeup、preempt等等事件。

1. atop的統計方法

既然不能直接使用計算好的值,那么就得通過其他方法進行統計了,首先Linux kernel 本身是有統計每一個task的調度延遲的,在內核中調度延遲使用的說法是run delay,并且通過proc文件系統暴露了出來,因此大概率現有的傳統工具提取調度延遲的源數據是來自于proc的,例如atop工具。

run delay在proc中的位置:

進程的調度延遲:/proc//schedstat
線程的調度延遲:/proc//task//schedstat

現在的目標變為搞清楚atop工具是怎么統計調度延遲的。

現有的工具atop是可以輸出用戶態每一個進程和線程的調度延遲指標的,在開啟atop后按下s鍵,就會看到RDELAY列,這一列就是調度延遲了。我們來看看 atop 工具是怎么統計這個指標值的,cloneatop工具的代碼:

git@github.com:Atoptool/atop.git

由于目前的目標是搞清楚atop對調度延遲指標的統計方法,因此我只關心和這個部分相關的代碼片段,可視化展示的部分并不關心。

整體來說,atop 工作的大體流程是:

intmain(int argc, char *argv[]){···    // 獲取 interval    interval = atoi(argv[optind]);
    // 開啟收集引擎    engine();···    return 0;    /* never reached */}

這里的interval就是我們使用atop的時候以什么時間間隔來提取數據,這個時間間隔就是interval。

所有的計算等操作都在engine()函數中完成

engine()的工作流程如下:

static voidengine(void){···    /*    ** install the signal-handler for ALARM, USR1 and USR2 (triggers    * for the next sample)    */    memset(&sigact, 0, sizeof sigact);    sigact.sa_handler = getusr1;    sigaction(SIGUSR1, &sigact, (struct sigaction *)0);···    if (interval > 0)        alarm(interval);···    for (sampcnt=0; sampcnt < nsamples; sampcnt++)    {···        if (sampcnt > 0 && awaittrigger)            pause();        awaittrigger = 1;···        do        {            curtlen   = counttasks();    // worst-case value            curtpres  = realloc(curtpres,                    curtlen * sizeof(struct tstat));
            ptrverify(curtpres, "Malloc failed for %lu tstats
",                                curtlen);
            memset(curtpres, 0, curtlen * sizeof(struct tstat));        }        while ( (ntaskpres = photoproc(curtpres, curtlen)) == curtlen);
···    } /* end of main-loop */}

代碼細節上不再詳細介紹,整體運行的大循環是在16行開始的,真正得到調度延遲指標值的是在34行的photoproc()函數中計算的,傳入的是需要計算的task列表和task的數量

來看看最終計算的地方:

unsigned longphotoproc(struct tstat *tasklist, int maxtask){···        procschedstat(curtask);        /* from /proc/pid/schedstat */···        if (curtask->gen.nthr > 1)        {···            curtask->cpu.rundelay = 0;···            /*            ** open underlying task directory            */            if ( chdir("task") == 0 )            {···                while ((tent=readdir(dirtask)) && tvalcpu.rundelay +=                        procschedstat(curthr);                    ···                }                ···            }        }    ···    return tval;}

第5行的函數就是在讀取proc的schedstat文件:

  static count_t  procschedstat(struct tstat *curtask){    FILE    *fp;    char    line[4096];    count_t    runtime, rundelay = 0;    unsigned long pcount;    static char *schedstatfile = "schedstat";      /*     ** open the schedstat file     */    if ( (fp = fopen(schedstatfile, "r")) )    {        curtask->cpu.rundelay = 0;          if (fgets(line, sizeof line, fp))        {            sscanf(line, "%llu %llu %lu
",                    &runtime, &rundelay, &pcount);              curtask->cpu.rundelay = rundelay;        }          /*        ** verify if fgets returned NULL due to error i.s.o. EOF        */        if (ferror(fp))            curtask->cpu.rundelay = 0;          fclose(fp);    }    else    {        curtask->cpu.rundelay = 0;    }      return curtask->cpu.rundelay;  }

15行是在判斷是不是有多個thread,如果有多個thread,那么就把所有的thread的調度延遲相加就得到了這個任務的調度延遲。

所以追蹤完atop對調度延遲的處理后,我們就可以發現獲取數據的思路是開啟atop之后,按照我們指定的interval,在大循環中每一次interval到來以后,就讀取一次proc文件系統,將這個值保存,因此結論就是目前的atop工具對調度延遲的提取方式就是每隔interval秒,讀取一次proc下的schedstat文件。
因此atop獲取的是每interval時間的系統當前進程的調度延遲快照數據,并且是秒級別的提取頻率。

2. proc的底層方法—面向task

那么數據源頭我們已經定位好了,就是來源于proc,而proc的數據全部都是內核運行過程中自己統計的,那現在的目標就轉為內核內部是怎么統計每一個task的調度延遲的,因此需要定位到內核中 proc 計算調度延遲的地點是哪里。

方法很簡單,寫一個讀取schedstat文件的簡單程序,使用ftrace追蹤一下,就可以看到proc里面是哪個函數來生成的schedstat文件中的數據,ftrace的結果如下:

2)   0.125 us    |            single_start();  
2)               |            proc_single_show() {  
2)               |              get_pid_task() {  
2)   0.124 us    |                rcu_read_unlock_strict();  
2)   0.399 us    |              }  
2)               |              proc_pid_schedstat() {  
2)               |                seq_printf() {  
2)   1.145 us    |                  seq_vprintf();  
2)   1.411 us    |                }  
2)   1.722 us    |              }  
2)   2.599 us    |            }

很容易可以發現是第六行的函數:

#ifdef CONFIG_SCHED_INFO/** Provides /proc/PID/schedstat*/static int proc_pid_schedstat(struct seq_file *m, struct pid_namespace *ns,                              struct pid *pid, struct task_struct *task){    if (unlikely(!sched_info_on()))        seq_puts(m, "0 0 0
");    else        seq_printf(m, "%llu %llu %lu
",                   (unsigned long long)task->se.sum_exec_runtime,                   (unsigned long long)task->sched_info.run_delay,                   task->sched_info.pcount);
    return 0;}#endif

第8行是在判斷一個內核配置選項,一般默認都是開啟的,或者能看到schedstat文件有輸出,那么就是開啟的,或者可以用make menuconfig查找一下這個選項的狀態。

可以發現proc在拿取這個調度延遲指標的時候是直接從傳進來的task_struct中的sched_info中記錄的run_delay,而且是一次性讀取,沒有做平均值之類的數據處理,因此也是一個快照形式的數據。

首先說明下sched_info結構:

struct sched_info {#ifdef CONFIG_SCHED_INFO    /* Cumulative counters: */
    /* # of times we have run on this CPU: */    unsigned long            pcount;
    /* Time spent waiting on a runqueue: */    unsigned long long        run_delay;
    /* Timestamps: */
    /* When did we last run on a CPU? */    unsigned long long        last_arrival;
    /* When were we last queued to run? */    unsigned long long        last_queued;#endif /* CONFIG_SCHED_INFO */};

和上面proc函數的宏是一樣的,所以可以推測出來這個宏很有可能是用來開啟內核統計task的調度信息的。每個字段的含義代碼注釋已經介紹的比較清晰了,kernel 對調度延遲給出的解釋是在 runqueue 中等待的時間。

現在的目標轉變為內核是怎么對這個run_delay字段進行計算的。需要回過頭來看一下sched_info的結構,后兩個是用于計算run_delay參數的,另外這里就需要Linux調度器框架和CFS調度器相關了,首先需要梳理一下和進程調度信息統計相關的函數,其實就是看CONFIG_SCHED_INFO這個宏包起來了哪些函數,找到這些函數的聲明點,相關的函數位于kernel/sched/stats.h中。

涉及到的函數如下:

sched_info_queued(rq, t)sched_info_reset_dequeued(t)sched_info_dequeued(rq, t)sched_info_depart(rq, t)sched_info_arrive(rq, next)sched_info_switch(rq, t, next)

BTW,調度延遲在rq中統計的函數是:

rq_sched_info_arrive()rq_sched_info_dequeued()rq_sched_info_depart()

注意的是這些函數的作用只是統計調度信息,查看這些函數的代碼,其中和調度延遲相關的函數有以下三個:

sched_info_depart(rq, t)sched_info_queued(rq, t)sched_info_arrive(rq, next)

并且一定是在關鍵的調度時間節點上被調用的:


1. 進入runqueue
task 從其他狀態(休眠,不可中斷等)切換到可運行狀態后,進入 runqueue 的起始時刻;

2. 調度下CPU,然后進入runqueue
task 從一個 cpu 的 runqueue 移動到另外一個 cpu 的 runqueue 時,更新進入新的 runqueue
的起始時刻;
task 正在運行被調度下CPU,放入 runqueue 的起始時刻,被動下CPU;

3. 產生新task然后進入runqueue;

4. 調度上CPU
進程從 runqueue 中被調度到cpu上運行時更新last_arrival;

可以這么理解要么上CPU,要么下CPU,下CPU并且狀態還是TASK_RUNNING狀態的其實就是進入runqueue的時機。

進入到runqueue都會最終調用到sched_info_queued,而第二種情況會先走sched_info_depart函數:

static inline void sched_info_depart(struct rq *rq, struct task_struct *t){    unsigned long long delta = rq_clock(rq) - t->sched_info.last_arrival;
    rq_sched_info_depart(rq, delta);
    if (t->state == TASK_RUNNING)        sched_info_queued(rq, t);}

第3行的代碼在計算上次在CPU上執行的時間戳是多少,用現在的時間減去last_arrival(上次被調度上CPU的時間)就可以得到,然后傳遞給了rq_sched_info_depart()函數

第2種情況下,在第8行,如果進程這個時候的狀態還是TASK_RUNNING,那么說明這個時候task是被動下CPU的,表示該task又開始在runqueue中等待了,為什么不統計其它狀態的task,因為其它狀態的task是不能進入runqueue的,例如等待IO的task,這些task只有在完成等待后才可以進入runqueue,這個時候就有變成了第1種情況;第1種情況下會直接進入sched_info_queued()函數;因此這兩種情況下都是task進入了runqueue然后最終調用sched_info_queued()函數記錄上次(就是現在)進入runqueue 的時間戳last_queued。

sched_info_queued()的代碼如下:

  static inline void sched_info_queued(struct rq *rq, struct task_struct *t)  {    if (unlikely(sched_info_on())) {        if (!t->sched_info.last_queued)            t->sched_info.last_queued = rq_clock(rq);    }  }

然后就到了最后一個關鍵節點,task被調度CPU了,就會觸發sched_info_arrive()函數:

static void sched_info_arrive(struct rq *rq, struct task_struct *t)  {    unsigned long long now = rq_clock(rq), delta = 0;      if (t->sched_info.last_queued)        delta = now - t->sched_info.last_queued;    sched_info_reset_dequeued(t);    t->sched_info.run_delay += delta;    t->sched_info.last_arrival = now;    t->sched_info.pcount++;      rq_sched_info_arrive(rq, delta);  }

這個時候就可以來計算調度延遲了,代碼邏輯是如果有記錄上次的last_queued時間戳,那么就用現在的時間戳減去上次的時間戳,就是該 task 的調度延遲,然后保存到run_delay字段里面,并且標記這次到達CPU的時間戳到last_arrival里面,pcount記錄的是上cpu上了多少次。

公式就是:

該task的調度延遲=該task剛被調度上CPU的時間戳-last_queued(該task上次進入runqueue的時間戳)

審核編輯:彭靜
聲明:本文內容及配圖由入駐作者撰寫或者入駐合作網站授權轉載。文章觀點僅代表作者本人,不代表電子發燒友網立場。文章及其配圖僅供工程師學習之用,如有內容侵權或者其他違規問題,請聯系本站處理。 舉報投訴
  • cpu
    cpu
    +關注

    關注

    68

    文章

    10512

    瀏覽量

    207272
  • Linux
    +關注

    關注

    87

    文章

    11022

    瀏覽量

    207063
  • 調度器
    +關注

    關注

    0

    文章

    96

    瀏覽量

    5175

原文標題:通過性能指標學習Linux Kernel - (上)

文章出處:【微信號:LinuxDev,微信公眾號:Linux閱碼場】歡迎添加關注!文章轉載請注明出處。

收藏 人收藏

    評論

    相關推薦

    鴻蒙開發接口資源調度:【@ohos.workScheduler (延遲任務調度)】

    開發者在開發應用時,通過調用延遲任務注冊接口,注冊對實時性要求不高的延遲任務,該任務默認由系統安排,在系統空閑時根據性能、功耗、熱等情況進行調度執行。
    的頭像 發表于 06-04 10:01 ?469次閱讀
    鴻蒙開發接口資源<b class='flag-5'>調度</b>:【@ohos.workScheduler (<b class='flag-5'>延遲</b>任務<b class='flag-5'>調度</b>)】

    鴻蒙原生應用/元服務開發-延遲任務說明(一)

    添加到執行隊列,系統會根據內存、功耗、設備溫度、用戶使用習慣等統一調度拉起應用。 二、運行原理 圖1 延遲任務實現原理 應用調用延遲任務接口添加、刪除、查詢延遲任務,
    發表于 01-16 14:57

    鴻蒙原生應用/元服務開發-延遲任務開發實現(二)

    : workScheduler.WorkInfo): void 延遲調度任務開始的回調 onWorkStop(work: workScheduler.WorkInfo): void 延遲調度
    發表于 01-17 17:53

    使用SAFECheckpoints驗證任務調度性能

    在基于任務優先級的搶占式調度機制中,會選擇就緒的最高優先級任務執行,因此,需要仔細考慮分配給每個任務的優先級,它將直接影響任務何時被執行。任務調度還受中斷影響,因為中斷的優先級高于所有任務。過長或
    發表于 12-11 10:01

    JESD204B中的確定延遲到底是什么? 它是否就是轉換器的總延遲?

    什么是8b/10b編碼,為什么JESD204B接口需使用這種編碼?怎么消除影響JESD204B鏈路傳輸的因素?JESD204B中的確定延遲到底是什么? 它是否就是轉換器的總延遲?JESD204B如何使用結束位?結束位存在的意義是什么?如何計算轉換器的通道速率?什么是應用層
    發表于 04-13 06:39

    在單片機C語言中怎么通過循環次數計算延遲函數的延遲時間?

    你的循環編譯成機器語言后到底是幾個指令周期 這樣就知道了延遲時間 當然你取近似值也可以的 比如 for(i=0,i
    發表于 07-14 07:09

    調度器的原理及其任務調度代碼實現

    一、介紹調度器是常用的一種編程框架,也是操作系統的拆分多任務的核心,比如單片機的裸機程序框架,網絡協議棧的框架如can網關、485網關等等,使用場合比較多,是做穩定產品比較常用的編程技術二、原理1
    發表于 02-17 07:07

    延遲線,延遲線是什么意思

    延遲線,延遲線是什么意思  延遲線  delay line  用于將電信號延遲一段時間的元件或器件稱為延遲線。
    發表于 03-09 11:33 ?8157次閱讀

    基于可延遲調度提升實時數據對象時序一致性服務質量算法

    針對保證實時數據對象時序一致性調度算法在軟實時數據庫系統環境下的應用問題,提出了一種基于概率統計的可延遲優化( SDS-OPT)算法。首先,分析和比較了現有算法在可調度性、服務質量(QoS)以及工作
    發表于 12-17 11:07 ?0次下載
    基于可<b class='flag-5'>延遲</b><b class='flag-5'>調度</b>提升實時數據對象時序一致性服務質量算法

    煉鋼連鑄重調度算法

    針對轉爐出鋼延遲的煉鋼連鑄重調度問題,以開工時間、加工時間以及加工機器的差異度和同一爐次相鄰設備間的等待時間的差異化最小為目標建立了動態約束滿足模型,提出了基于約束滿足和斷澆修復的重調度算法。算法
    發表于 02-27 16:28 ?0次下載

    什么是調度?為什么要調度?

    什么是調度?按照某種調度算法,從進程的ready隊列中選擇進程給CPU。
    的頭像 發表于 06-15 15:18 ?8002次閱讀
    什么是<b class='flag-5'>調度</b>?為什么要<b class='flag-5'>調度</b>?

    什么是調度?如何進行調度?

    進程調度是操作系統最重要的內容之一,也是學習操作系統的重點和難點。關于進程調度,我們首先就會問出一些問題,什么是進程調度,為什么要進程調度,如何進行
    發表于 08-05 09:04 ?1.1w次閱讀

    CXL內存延遲到底有多糟糕?

    以前的內存擴展嘗試都陷入了妥協,特別是在延遲方面。例如,GigaIO 表示其FabreX 架構已經可以使用 DMA 在 PCI-Express 上進行內存池化,但這樣做需要應用程序能夠容忍 500 納秒到 1.5 微秒的延遲。
    的頭像 發表于 12-07 15:44 ?1208次閱讀

    智能調度模式是什么 智能調度的優缺點

    智能調度模式是一種通過先進的信息技術和智能算法實現電力系統智能化調度的方式。智能調度模式可以是中央調度模式,區域調度模式,分布式
    發表于 04-11 15:35 ?3207次閱讀

    國產調度器之光——Fsched到底有多能打?

    這是一篇推薦我們速石自研調度器——Fsched的文章??雌饋碓趯iT寫調度器,但又不完全在寫。往下看,你就懂了。 本篇一共五個章節: 一、介紹一下主角——速石自研調度器Fsched 二、只要有個
    的頭像 發表于 08-30 22:01 ?380次閱讀
    國產<b class='flag-5'>調度</b>器之光——Fsched<b class='flag-5'>到底</b>有多能打?
    亚洲欧美日韩精品久久_久久精品AⅤ无码中文_日本中文字幕有码在线播放_亚洲视频高清不卡在线观看
    <acronym id="s8ci2"><small id="s8ci2"></small></acronym>
    <rt id="s8ci2"></rt><rt id="s8ci2"><optgroup id="s8ci2"></optgroup></rt>
    <acronym id="s8ci2"></acronym>
    <acronym id="s8ci2"><center id="s8ci2"></center></acronym>