Linux CFS調度器之隊列操作--Linux進程的管理與調度(二十七)

来源:https://www.cnblogs.com/linhaostudy/archive/2018/11/12/9947277.html
-Advertisement-
Play Games

1. CFS進程入隊和出隊 完全公平調度器CFS中有兩個函數可用來增刪隊列的成員: 和`dequeue_task_fair`分別用來向CFS就緒隊列中添加或者刪除進程 2 enqueue_task_fair入隊操作 2.1 enque_task_fair函數 向就緒隊列中放置新進程的工作由函數 函數 ...


1. CFS進程入隊和出隊

完全公平調度器CFS中有兩個函數可用來增刪隊列的成員:enqueue_task_fairdequeue_task_fair分別用來向CFS就緒隊列中添加或者刪除進程

2 enqueue_task_fair入隊操作

2.1 enque_task_fair函數

向就緒隊列中放置新進程的工作由函數enqueue_task_fair函數完成, 該函數定義在kernel/sched/fair.c, line 5442, 其函數原型如下

該函數將task_struct *p所指向的進程插入到rq所在的就緒隊列中, 除了指向所述的就緒隊列rq和task_struct的指針外, 該函數還有另外一個參數wakeup. 這使得可以指定入隊的進程是否最近才被喚醒並轉換為運行狀態(此時需指定wakeup = 1), 還是此前就是可運行的(那麼wakeup = 0).

static void
enqueue_task_fair(struct rq *rq, struct task_struct *p, int flags)

enqueue_task_fair的執行流程如下

  • 如果通過struct sched_entity的on_rq成員判斷進程已經在就緒隊列上, 則無事可做.
  • 否則, 具體的工作委托給enqueue_entity完成, 其中內核會藉機用update_curr更新統計量

在enqueue_entity內部如果需要會調用__enqueue_entity將進程插入到CFS紅黑樹中合適的結點

2.2 enque_task_fair完全函數

/*
 * The enqueue_task method is called before nr_running is
 * increased. Here we update the fair scheduling stats and
 * then put the task into the rbtree:
 */
static void
enqueue_task_fair(struct rq *rq, struct task_struct *p, int flags)
{
    struct cfs_rq *cfs_rq;
    struct sched_entity *se = &p->se;

    for_each_sched_entity(se) {
        if (se->on_rq)
            break;
        cfs_rq = cfs_rq_of(se);
        enqueue_entity(cfs_rq, se, flags);

        /*
         * end evaluation on encountering a throttled cfs_rq
         *
         * note: in the case of encountering a throttled cfs_rq we will
         * post the final h_nr_running increment below.
        */
        if (cfs_rq_throttled(cfs_rq))
            break;
        cfs_rq->h_nr_running++;

        flags = ENQUEUE_WAKEUP;
    }

    for_each_sched_entity(se) {
        cfs_rq = cfs_rq_of(se);
        cfs_rq->h_nr_running++;

        if (cfs_rq_throttled(cfs_rq))
            break;

        update_load_avg(se, 1);
        update_cfs_shares(cfs_rq);
    }

    if (!se)
        add_nr_running(rq, 1);

    hrtick_update(rq);
}

2.3 for_each_sched_entity

首先內核查找到待天機進程p所在的調度實體信息, 然後通過for_each_sched_entity迴圈所有調度實體,

//  enqueue_task_fair函數
{
    struct cfs_rq *cfs_rq;
    struct sched_entity *se = &p->se;

    for_each_sched_entity(se)
    {
    /*  ......  */
    }
}

linux對組調度的支持可以通過CONFIG_FAIR_GROUP_SCHED來啟用, 在啟用和不啟用的條件下, 內核對很多函數的實現也會因條件而異, 這點對for_each_sched_entity函數尤為明顯, 參見啟用CONFIG_FAIR_GROUP_SCHED不啟用CONFIG_FAIR_GROUP_SCHED

  • 如果通過struct sched_entity的on_rq成員判斷進程已經在就緒隊列上, 則無事可做.
  • 否則, 具體的工作委托給enqueue_entity完成, 其中內核會藉機用update_curr更新統計量.
//  enqueue_task_fair函數
{
        /*  如果當前進程已經在就緒隊列上  */
        if (se->on_rq)
            break;

        /*  獲取到當前進程所在的cfs_rq就緒隊列  */
        cfs_rq = cfs_rq_of(se);
        /*  內核委托enqueue_entity完成真正的插入工作  */
        enqueue_entity(cfs_rq, se, flags);
}

2.4 enqueue_entity插入進程

enqueue_entity完成了進程真正的入隊操作, 其具體流程如下所示

  • 更新一些統計統計量, update_curr, update_cfs_shares等
  • 如果進程此前是在睡眠狀態, 則調用place_entity中首先會調整進程的虛擬運行時間
  • 最後如果進程最近在運行, 其虛擬運行時間仍然有效, 那麼則直接用__enqueue_entity加入到紅黑樹

首先如果進程最近正在運行, 其虛擬時間時間仍然有效, 那麼(除非它當前在執行中)它可以直接用__enqueue_entity插入到紅黑樹, 該函數徐婭萍處理一些紅黑樹的機制, 這可以依靠內核的標準實現, 參見__enqueue_entity函數, kernel/sched/fair.c, line483

static void
enqueue_entity(struct cfs_rq *cfs_rq, struct sched_entity *se, int flags)
{
    /*
     * Update the normalized vruntime before updating min_vruntime
     * through calling update_curr().
     *
     * 如果當前進程之前已經是可運行狀態不是被喚醒的那麼其虛擬運行時間要增加
     */
    if (!(flags & ENQUEUE_WAKEUP) || (flags & ENQUEUE_WAKING))
        se->vruntime += cfs_rq->min_vruntime;

    /*
     * Update run-time statistics of the 'current'.
     * 更新進程的統計量信息
     */
    update_curr(cfs_rq);
    enqueue_entity_load_avg(cfs_rq, se);
    account_entity_enqueue(cfs_rq, se);
    update_cfs_shares(cfs_rq);

    /*  如果當前進行之前在睡眠剛被喚醒  */
    if (flags & ENQUEUE_WAKEUP)
    {
        /*  調整進程的虛擬運行時間  */
        place_entity(cfs_rq, se, 0);
        if (schedstat_enabled())
            enqueue_sleeper(cfs_rq, se);
    }

    check_schedstat_required();
    if (schedstat_enabled()) {
        update_stats_enqueue(cfs_rq, se);
        check_spread(cfs_rq, se);
    }

    /*  將進程插入到紅黑樹中  */
    if (se != cfs_rq->curr)
        __enqueue_entity(cfs_rq, se);
    se->on_rq = 1;

    if (cfs_rq->nr_running == 1) {
        list_add_leaf_cfs_rq(cfs_rq);
        check_enqueue_throttle(cfs_rq);
    }
}

2.5 place_entity處理睡眠進程

如果進程此前在睡眠, 那麼則調用place_entity處理其虛擬運行時間

設想一下子如果休眠進程的vruntime保持不變, 而其他運行進程的 vruntime一直在推進, 那麼等到休眠進程終於喚醒的時候, 它的vruntime比別人小很多, 會使它獲得長時間搶占CPU的優勢, 其他進程就要餓死了. 這顯然是另一種形式的不公平,因此CFS是這樣做的:在休眠進程被喚醒時重新設置vruntime值,以min_vruntime值為基礎,給予一定的補償,但不能補償太多. 這個重新設置其虛擬運行時間的工作就是就是通過place_entity來完成的, 另外新進程創建完成後, 也是通過place_entity完成其虛擬運行時間vruntime的設置的. place_entity通過其第三個參數initial來標識新進程創建和休眠進程蘇醒兩種不同情形的.

place_entity函數定義在kernel/sched/fair.c, line 3135中首先會調整進程的虛擬運行時間

//  http://lxr.free-electrons.com/source/kernel/sched/fair.c?v=4.6#L3134
static void
place_entity(struct cfs_rq *cfs_rq, struct sched_entity *se, int initial)
{
    u64 vruntime = cfs_rq->min_vruntime;

    /*
     * The 'current' period is already promised to the current tasks,
     * however the extra weight of the new task will slow them down a
     * little, place the new task so that it fits in the slot that
     * stays open at the end.
     *
     * 如果是新進程第一次要入隊, 那麼就要初始化它的vruntime
     * 一般就把cfsq的vruntime給它就可以
     * 但是如果當前運行的所有進程被承諾了一個運行周期
     * 那麼則將新進程的vruntime後推一個他自己的slice
     * 實際上新進程入隊時要重新計算運行隊列的總權值
     * 總權值顯然是增加了,但是所有進程總的運行時期並不一定隨之增加
     * 則每個進程的承諾時間相當於減小了,就是減慢了進程們的虛擬時鐘步伐。 
     */
    /*  initial標識了該進程是新進程  */
    if (initial && sched_feat(START_DEBIT))
        vruntime += sched_vslice(cfs_rq, se);

    /* sleeps up to a single latency don't count. 
     * 休眠進程  */
    if (!initial)
    {
        /*  一個調度周期  */
        unsigned long thresh = sysctl_sched_latency;

        /*
         * Halve their sleep time's effect, to allow
         * for a gentler effect of sleepers:
         */
        /*  若設了GENTLE_FAIR_SLEEPERS  */
        if (sched_feat(GENTLE_FAIR_SLEEPERS))
            thresh >>= 1;   /*  補償減為調度周期的一半  */

        vruntime -= thresh;
    }

    /* ensure we never gain time by being placed backwards.
     * 如果是喚醒已經存在的進程,則單調附值
     */
    se->vruntime = max_vruntime(se->vruntime, vruntime);
}

我們可以看到enqueue_task_fair調用place_entity傳遞的initial參數為0

place_entity(cfs_rq, se, 0);

所以會執行if (!initial)後的語句。因為進程睡眠後,vruntime就不會增加了,當它醒來後不知道過了多長時間,可能vruntime已經比 min_vruntime小了很多,如果只是簡單的將其插入到就緒隊列中,它將拼命追趕min_vruntime,因為它總是在紅黑樹的最左面。如果這 樣,它將會占用大量的CPU時間,導致紅黑樹右邊的進程被餓死。但是我們又必須及時響應醒來的進程,因為它們可能有一些工作需要立刻處理,所以系統採取了 一種折衷的辦法,將當前cfs_rq->min_vruntime時間減去sysctl_sched_latency賦給vruntime,這時它 會被插入到就緒隊列的最左邊。這樣剛喚醒的進程在當前執行進程時間耗盡時就會被調度上處理器執行。當然如果進程沒有睡眠那麼多時間,我們只需保留原來的時 間vruntime = max_vruntime(se->vruntime, vruntime)。這有什麼好處的,我覺得它可以將所有喚醒的進程排個隊,睡眠越久的越快得到響應。

對於新進程創建時initial為1,所以它會執行vruntime += sched_vslice(cfs_rq, se);這句,而這裡的vruntime就是當前CFS就緒隊列的min_vruntime,新加進程應該在最近很快被調度,這樣減少系統的響應時間,我們已經知道當前進程的vruntime越小,它在紅黑樹中就會越靠左,就會被很快調度到處理器上執行。但是,Linux內核需要根據新加入的進程的權重決策一下應該何時調度該進程,而不能任意進程都來搶占當前隊列中靠左的進程,因為必須保證就緒隊列中的所有進程儘量得到他們應得的時間響應, sched_vslice函數就將其負荷權重轉換為等價的虛擬時間, 其定義在kernel/sched/fair.c, line 626

函數就是根據initial的值來區分兩種情況, 一般來說只有在新進程被加到系統中時,才會首次設置該參數, 但是這裡的情況並非如此:

由於內核已經承諾在當前的延遲周期內使所有活動進程都至少運行一次, 隊列的min_vruntime用作基準虛擬時間, 通過減去sysctl_sched_latency, 則可以確保新喚醒新喚醒的進程只有在當前延遲周期結束後才能運行.

但是如果進程在睡眠的過程中累積了比較大的不公平值(即se->vruntime值比較大), 則內核必須考慮這一點. 如果se->vruntime比先前的差值更大, 則將其作為進程的vruntime, 這會導致高進程在紅黑樹中處於靠左的位置, 而具有較小vruntime值得進程可以更早調度執行.

2.6 __enqueue_entity完成紅黑樹的插入

如果進程最近在運行, 其虛擬時間是有效的, 那麼它可以直接通過__enqueue_entity加入到紅黑樹

//  enqueue_entity函數解析
    /*  將進程插入到紅黑樹中  */
    if (se != cfs_rq->curr)
        __enqueue_entity(cfs_rq, se);
    se->on_rq = 1;

__enqueue_entity函數定義在kernel/sched/fair.c, line 486中, 其實就是一個機械性地紅黑樹插入操作

/*
 * Enqueue an entity into the rb-tree:
 */
static void __enqueue_entity(struct cfs_rq *cfs_rq, struct sched_entity *se)
{
    struct rb_node **link = &cfs_rq->tasks_timeline.rb_node;
    struct rb_node *parent = NULL;
    struct sched_entity *entry;
    int leftmost = 1;

    /*
     * Find the right place in the rbtree:
     * 從紅黑樹中找到se所應該在的位置
     * 同時leftmost標識其位置是不是最左結點
     * 如果在查找結點的過程中向右走了, 則置leftmost為0
     * 否則說明一直再相左走, 最終將走到最左節點, 此時leftmost恆為1
     */
    while (*link) {
        parent = *link;
        entry = rb_entry(parent, struct sched_entity, run_node);
        /*
         * We dont care about collisions. Nodes with
         * the same key stay together.
         * 以se->vruntime值為鍵值進行紅黑樹結點的比較
         */
        if (entity_before(se, entry)) {
            link = &parent->rb_left;
        } else {
            link = &parent->rb_right;
            leftmost = 0;
        }
    }
    /*
     * Maintain a cache of leftmost tree entries (it is frequently
     * used):
     * 如果leftmost為1, 說明se是紅黑樹當前的最左結點, 即vruntime最小
     * 那麼把這個節點保存在cfs就緒隊列的rb_leftmost域中
     */
    if (leftmost)
        cfs_rq->rb_leftmost = &se->run_node;

    /*  將新進程的節點加入到紅黑樹中  */
    rb_link_node(&se->run_node, parent, link);
    /*  為新插入的結點進行著色  */
    rb_insert_color(&se->run_node, &cfs_rq->tasks_timeline);
}

3 dequeue_task_fair出隊操作

dequeue_task_fair函數在完成睡眠等情況下調度, 將任務從就緒隊列中移除

其執行的過程正好跟enqueue_task_fair的思路相同, 只是操作剛好相反

dequeue_task_fair的執行流程如下

  • 如果通過struct sched_entity的on_rq成員判斷進程已經在就緒隊列上, 則無事可做.

  • 否則, 具體的工作委托給dequeue_entity完成, 其中內核會藉機用update_curr更新統計量
    在enqueue_entity內部如果需要會調用__dequeue_entity將進程插入到CFS紅黑樹中合適的結點

dequeue_task_fair定義在/kernel/sched/fair.c, line 4155, 其大致框架流程如下

/*
 * The dequeue_task method is called before nr_running is
 * decreased. We remove the task from the rbtree and
 * update the fair scheduling stats:
 */
static void dequeue_task_fair(struct rq *rq, struct task_struct *p, int flags)
;

    struct cfs_rq *cfs_rq;
    struct sched_entity *se = &p->se;
    int task_sleep = flags & DEQUEUE_SLEEP;

    //   設置
    flags |= DEQUEUE_SLEEP;


    for_each_sched_entity(se) {
        cfs_rq = cfs_rq_of(se);
        cfs_rq->h_nr_running--;

        if (cfs_rq_throttled(cfs_rq))
            break;

        update_load_avg(se, 1);
        update_cfs_shares(cfs_rq);
    }

    if (!se)
        sub_nr_running(rq, 1);

    hrtick_update(rq);
}

3.2 dequeue_entity將調度實體出隊

static void
dequeue_entity(struct cfs_rq *cfs_rq, struct sched_entity *se, int flags)
{
    /*
     * Update run-time statistics of the 'current'.
     */
    update_curr(cfs_rq);
    dequeue_entity_load_avg(cfs_rq, se);

    if (schedstat_enabled())
        update_stats_dequeue(cfs_rq, se, flags);

    clear_buddies(cfs_rq, se);

    if (se != cfs_rq->curr)
        __dequeue_entity(cfs_rq, se);
    se->on_rq = 0;
    account_entity_dequeue(cfs_rq, se);

    /*
     * Normalize the entity after updating the min_vruntime because the
     * update can refer to the ->curr item and we need to reflect this
     * movement in our normalized position.
     */
    if (!(flags & DEQUEUE_SLEEP))
        se->vruntime -= cfs_rq->min_vruntime;

    /* return excess runtime on last dequeue */
    return_cfs_rq_runtime(cfs_rq);

    update_min_vruntime(cfs_rq);
    update_cfs_shares(cfs_rq);
}

3.3 __dequeue_entity完成真正的出隊操作

static void __dequeue_entity(struct cfs_rq *cfs_rq, struct sched_entity *se)
{
    if (cfs_rq->rb_leftmost == &se->run_node) {
        struct rb_node *next_node;

        next_node = rb_next(&se->run_node);
        cfs_rq->rb_leftmost = next_node;
    }

    rb_erase(&se->run_node, &cfs_rq->tasks_timeline);
}

您的分享是我們最大的動力!

-Advertisement-
Play Games
更多相關文章
  • 1、^word #匹配word開頭的內容,vi/vim編輯器里^代表一行的開頭。 2、word$ #匹配以word結尾的內容,vi/vim編輯器里$代表一行的結尾。 3、^$ #表示空行 4、. #代表任意一個字元 5、\ #轉移符(在visudo里\表示換行) 6、* #匹配重覆0個或1個前面的字 ...
  • 硬鏈接:在linux文件系統中多個文件名指向同一個索引節點(Inode)是正常允許的。硬鏈接文件就相當於文件的另一個入口。 硬鏈接的作用:允許一個文件擁有多個有效路徑(多個入口),這樣用戶就可以建立硬鏈接到重要文件,以防止“誤刪”源數據 (很多硬體存儲,如netapp存儲中的快照功能就應用了這個原理 ...
  • DEVICE=eth0 #物理設備名,網卡名稱 WADDR=00:0c:29:7a:6b:24 #網卡的MAC地址,48位(配置克隆機時需把這一段刪除)TYPE=Ethernet #以外網UUID=c35b4084-7211-4da2-a64f-d078470ebac2 #網卡的唯一標識(配置克隆機 ...
  • Exchange主要是針對內部網或者企業網用戶進行搭建的郵件伺服器軟體,利用它能夠很快地搭建安全性較高的內部網郵件系統。 本次搭建在個人環境中實踐,純屬愛好折騰,分四步驟,1.搭建windows 2008 server域環境 2.安裝Exchange 2010郵件伺服器 3.配置Exchange 2 ...
  • [toc] Linux Namespace Linux Namespace 是kernel的一個功能,用於隔離系統資源。比如說PID,User ID 等。(可以想象一下變成語言變數的命名空間)這樣我們就可以將 進程,網路介面,掛載點以及用戶做一個隔離。 比如說我們在同一臺主機上,需要將資源分給多個類 ...
  • 個人總結,請尊重版權,轉載請在顯眼地方註明出處:https://www.cnblogs.com/sunshine5683/p/9948259.html day(01)基本命令學習 今天開始自學linux,在學習的過程中將踩過的坑和學習經歷總結出來,在提升自己的過程中,幫助有需要的人,一起學習,希望在 ...
  • Ubuntu的圖形界面使用起來非常方便,但是要想對Linux熟練掌握,就必須學會它的操作命令。雖然可能會花費一些時間,不過從長遠的角度來說,這的確是一件事半功倍的事情,它會讓我們更瞭解Linux,更靈活地去使用Linux。Ubuntu提供了很多命令,不過在本章中不做全部介紹,只是先介紹一些常用命令, ...
  • 這個寫的比較簡單,先做以下記錄 centos虛擬機安裝到別的電腦上,因為linux中的程式需要向外有網路互通,所以需要重新設置ip 通過 ifconfig eth4 192.168.0.20 broadcast 192.168.0.1 :broadcast 參數可以不填,這樣就設置好了ip 但是一個 ...
一周排行
    -Advertisement-
    Play Games
  • 移動開發(一):使用.NET MAUI開發第一個安卓APP 對於工作多年的C#程式員來說,近來想嘗試開發一款安卓APP,考慮了很久最終選擇使用.NET MAUI這個微軟官方的框架來嘗試體驗開發安卓APP,畢竟是使用Visual Studio開發工具,使用起來也比較的順手,結合微軟官方的教程進行了安卓 ...
  • 前言 QuestPDF 是一個開源 .NET 庫,用於生成 PDF 文檔。使用了C# Fluent API方式可簡化開發、減少錯誤並提高工作效率。利用它可以輕鬆生成 PDF 報告、發票、導出文件等。 項目介紹 QuestPDF 是一個革命性的開源 .NET 庫,它徹底改變了我們生成 PDF 文檔的方 ...
  • 項目地址 項目後端地址: https://github.com/ZyPLJ/ZYTteeHole 項目前端頁面地址: ZyPLJ/TreeHoleVue (github.com) https://github.com/ZyPLJ/TreeHoleVue 目前項目測試訪問地址: http://tree ...
  • 話不多說,直接開乾 一.下載 1.官方鏈接下載: https://www.microsoft.com/zh-cn/sql-server/sql-server-downloads 2.在下載目錄中找到下麵這個小的安裝包 SQL2022-SSEI-Dev.exe,運行開始下載SQL server; 二. ...
  • 前言 隨著物聯網(IoT)技術的迅猛發展,MQTT(消息隊列遙測傳輸)協議憑藉其輕量級和高效性,已成為眾多物聯網應用的首選通信標準。 MQTTnet 作為一個高性能的 .NET 開源庫,為 .NET 平臺上的 MQTT 客戶端與伺服器開發提供了強大的支持。 本文將全面介紹 MQTTnet 的核心功能 ...
  • Serilog支持多種接收器用於日誌存儲,增強器用於添加屬性,LogContext管理動態屬性,支持多種輸出格式包括純文本、JSON及ExpressionTemplate。還提供了自定義格式化選項,適用於不同需求。 ...
  • 目錄簡介獲取 HTML 文檔解析 HTML 文檔測試參考文章 簡介 動態內容網站使用 JavaScript 腳本動態檢索和渲染數據,爬取信息時需要模擬瀏覽器行為,否則獲取到的源碼基本是空的。 本文使用的爬取步驟如下: 使用 Selenium 獲取渲染後的 HTML 文檔 使用 HtmlAgility ...
  • 1.前言 什麼是熱更新 游戲或者軟體更新時,無需重新下載客戶端進行安裝,而是在應用程式啟動的情況下,在內部進行資源或者代碼更新 Unity目前常用熱更新解決方案 HybridCLR,Xlua,ILRuntime等 Unity目前常用資源管理解決方案 AssetBundles,Addressable, ...
  • 本文章主要是在C# ASP.NET Core Web API框架實現向手機發送驗證碼簡訊功能。這裡我選擇是一個互億無線簡訊驗證碼平臺,其實像阿裡雲,騰訊雲上面也可以。 首先我們先去 互億無線 https://www.ihuyi.com/api/sms.html 去註冊一個賬號 註冊完成賬號後,它會送 ...
  • 通過以下方式可以高效,並保證數據同步的可靠性 1.API設計 使用RESTful設計,確保API端點明確,並使用適當的HTTP方法(如POST用於創建,PUT用於更新)。 設計清晰的請求和響應模型,以確保客戶端能夠理解預期格式。 2.數據驗證 在伺服器端進行嚴格的數據驗證,確保接收到的數據符合預期格 ...