極簡組調度-CGroup如何限制cpu

来源:https://www.cnblogs.com/organic/archive/2023/04/15/17320490.html
-Advertisement-
Play Games

簡介:本文主要介紹ubuntu20.04容器中搭建xfce遠程桌面、C++、Go環境、容器內docker操作配置、zsh配置 一、創建容器 1、創建容器 docker pull ubuntu:20.04docker run -itd --privileged --name=my-desktop--u ...


1. 說明 1> linux內核關於task調度這塊是比較複雜的,流程也比較長,要從源碼一一講清楚很容易看暈,因此需要簡化,抓住主要的一個點,拋開無關的部分才能講清楚核心思想 2> 本篇文章主要是講清楚在cfs公平調度演算法中,CGroup如何限制cpu使用的主要過程,所以與此無關的代碼一律略過 3> 本篇源碼來自CentOS7.6的3.10.0-957.el7內核 4> 本篇內容以《極簡cfs公平調度演算法》為基礎,裡面講過的內容這裡就不重覆了 5> 為了極簡,這裡略去了CGroup嵌套的情況   2. CGroup控制cpu配置 CGroup控制cpu網上教程很多,這裡就不重點講了,簡單舉個創建名為test的CGroup的基本流程 1> 創建一個/sys/fs/cgroup/cpu/test目錄 2> 創建文件cpu.cfs_period_us並寫入100000,創建cpu.cfs_quota_us並寫入10000 表示每隔100ms(cfs_period_us)給test group分配一次cpu配額10ms(cfs_quota_us),在100ms的周期內,group中的進程最多使用10ms的cpu時長,這樣就能限制這個group最多使用單核10ms/100ms = 10%的cpu 3> 最後創建文件cgroup.procs,寫入要限制cpu的pid即生效   3. CGroup控制cpu基本思想 1> 《極簡cfs公平調度演算法》中我們講過cfs調度是以se為調度實例的,而不是task,因為group se也是一種調度實例,所以將調度實例抽象為se,統一以se進行調度 2> CGroup會設置一個cfs_period_us的時長的定時器,定時給group分配cfs_quota_us指定的cpu配額 3> 每次group下的task執行完一個時間片後,就會從group的cpu quota減去該task使用的cpu時長 4> 當group的cpu quota用完後,就會將整個group se throttle,即將其從公平調度運行隊列中移出,然後等待定時器觸發下個周期重新分配cpu quota後,重啟將group se移入到cpu rq上,從而達到控制cpu的效果。   一句話說明CGroup的控制cpu基本思想: 進程執行完一個時間片後,從cpu quota中減去其執行時間,當quota使用完後,就將其從rq中移除,這樣在一個period內就不會再調度了。   4. 極簡CGroup控制cpu相關數據結構 4.1 名詞解釋
  說明
task group 進程組,為了支持CGroup控制cpu,引入了組調度的概念,task group即包含所有要控制cpu的task集合以及配置信息。
group task 本文的專有名詞,是指一個進程組下的task,這些task受一個CGroup控制
cfs_bandwidth task_group的重要成員,包含了所要控制cpu的period,quota,定時器等信息
throttle 當group se在一個設定的時間周期內,消耗完了指定的cpu配額,則將其從cpu運行隊列中移出,並不再調度。 註意:處於throttled狀態的task仍是Ready狀態的,只是不在rq上。
unthrottle 將throttle狀態的group se,重新加入到cpu運行隊列中調度。
  4.2 cfs調度相關數據結構
struct cfs_rq
{
    struct rb_root tasks_timeline;                      // 以vruntime為key,se為value的紅黑樹根節點,schedule時,cfs調度演算法每次從這裡挑選vruntime最小的se投入運行
    struct rb_node* rb_leftmost;                        // 最左的葉子節點,即vruntime最小的se,直接取這個節點以加快速度
    sched_entity* curr;                                 // cfs_rq中當前正在運行的se
    struct rq* rq;                                       /* cpu runqueue to which this  cfs_rq is attached */
    struct task_group* tg;                              /* group that "owns" this  runqueue */
    int throttled;                                      // 表示該cfs_rq所屬的group se是否被throttled
    s64 runtime_remaining;                              // cfs_rq從全局時間池申請的時間片剩餘時間,當剩餘時間小於等於0的時候,就需要重新申請時間片
};
 
struct sched_entity
{
    unsigned int            on_rq;                          // se是否在rq上,不在的話即使task是Ready狀態也不會投入運行的
    u64              vruntime;                              // cpu運行時長,cfs調度演算法總是選擇該值最小的se投入運行
    /* rq on which this entity is (to be) queued: */
    struct cfs_rq* cfs_rq;                        // se所在的cfs_rq,如果是普通task  se,等於rq的cfs_rq,如果是group中的task,則等於group的cfs_rq
    /* rq "owned" by this entity/group: */
    struct cfs_rq* my_q;                          // my_q == NULL表示是一個普通task se,否則表示是一個group se,my_q指向group的cfs_rq
};
 
struct task
{
    struct sched_entity se;
};
 
struct rq
{
    struct cfs_rq cfs;                          // 所有要調度的se都掛在cfs rq中
    struct task_struct* curr;                   // 當前cpu上運行的task
};

本文中的sched_entity定義比《極簡cfs公平調度演算法》中的要複雜些,各種cfs_rq容易搞混,這裡講一下cfs公平調度挑選group task調度流程(只用到了my_q這個cfs_rq),以梳理清楚其關係

1> 當se.my_q為NULL時,表示一個task se,否則是group se   2> 選擇當group task3的流程   3> 選擇當group task的代碼
task_struct *pick_next_task_fair(struct rq *rq)
{
    struct cfs_rq *cfs_rq = &rq->cfs;       // 開始的cfs_rq為rq的cfs
    do {
        se = pick_next_entity(cfs_rq);      // 《極簡cfs公平調度演算法》中講過這個函數,其就是取cfs_rq->rb_leftmost,即最小vruntime的se
        cfs_rq = group_cfs_rq(se);          // 取se.my_q,如果是普通的task se,cfs_rq = NULL,這裡就會退出迴圈,如果是group se,cfs_rq = group_se.my_q,然後在group se的cfs_rq中繼續尋找vruntime最小的se
    } while (cfs_rq);
  
    return task_of(se);
}
 
cfs_rq *group_cfs_rq(struct sched_entity *grp)
{
    return grp->my_q;
}

 

4.3 CGroup控制cpu的數據結構
struct cfs_bandwidth
{
    ktime_t period;                             // cpu.cfs_period_us的值
    u64 quota;                                  // cpu.cfs_quota_us的值
    u64 runtime;                                // 當前周期內剩餘的quota時間
    int timer_active;                           // period_timer是否激活
    struct hrtimer period_timer;                // 定時分配cpu quota的定時器,定時器觸發時會更新runtime
};
 
struct task_group
{
    struct sched_entity** se;                   /* schedulable entities of this group  on each cpu */
    struct cfs_rq** cfs_rq;                     /* runqueue "owned" by this group on  each cpu */
    struct cfs_bandwidth cfs_bandwidth;         // 管理記錄CGroup控制cpu的信息
};

 

1> task_group.se是一個數組,每個cpu都有一個其對應的group se

  2>task_group.cfs_rq也是一個數組,每個cpu都有一個其對應的cfs_rq,每個cpu上的group se.my_q指向該cpu上對應的group cfs_rq,group下的task.se.cfs_rq也指向該group cfs_rq   3> cfs_bandwidth是CGroup管理控制cpu的關鍵數據結構,具體用途見定義   5. 極簡流程圖 從throttle到unthrottle:   6. 極簡code 6.1 檢測group se cpu quota的使用 1>《極簡cfs公平調度演算法》中我們講過,task調度的發動機時鐘中斷觸發後,經過層層調用,會到update_curr()這裡,update_curr()不僅++了當前se的vruntime,還調用 account_cfs_rq_runtime()統計並檢測group se是否使用完了cpu quota
void update_curr(struct cfs_rq* cfs_rq)
{
    struct sched_entity* curr = cfs_rq->curr;
    curr->vruntime += delta_exec;   // 增加se的運行時間
    account_cfs_rq_runtime(cfs_rq, delta_exec);
}
  2> account_cfs_rq_runtime()--了cfs_rq->runtime_remaining,如果runtime_remaining不足就調用assign_cfs_rq_runtime()從task group中分配,當分配不到(即表示當前周期的cpu quota用完了)就設置resched標記
void account_cfs_rq_runtime(struct cfs_rq* cfs_rq, u64 delta_exec)
{
    cfs_rq->runtime_remaining -= delta_exec;
    if (cfs_rq->runtime_remaining > 0)
        return;
    // 如果runtime_remaining不夠了,則要向task group分配cpu quota,分配失敗則設置task的thread flag為TIF_NEED_RESCHED,表示需要重新調度
    if (!assign_cfs_rq_runtime(cfs_rq) && likely(cfs_rq->curr))
        resched_curr(cfs_rq->rq);
}

 

3> assign_cfs_rq_runtime()就是從task_group.cfs_bandwidth.runtime減去要分配的時間片,如果其為0就分配失敗
/* returns 0 on failure to allocate runtime */
int assign_cfs_rq_runtime(struct cfs_rq* cfs_rq)
{
    struct cfs_bandwidth* cfs_b = cfs_rq->tg->cfs_bandwidth;;
 
    // 如果有限制cpu,則減去最小分配時間,如果cfs_b->runtime為0,那就沒有時間可分配了,本函數就會返回0,表示分配失敗
    amount = min(cfs_b->runtime, min_amount);
    cfs_b->runtime -= amount;
    cfs_rq->runtime_remaining += amount;
    return cfs_rq->runtime_remaining > 0;
}

 

6.2 throttle 6.1中我們看到cpu quota被使用完了,標記了resched,要進行重新調度了,但並沒有看到throttle。這是因為上面的代碼還在中斷處理函數中,是不能進行實際調度的,所以只設置resched標記,真正throttle幹活的還是在schedule()中(還記得《極簡cfs公平調度演算法》中講的task運行時間片到了後,進行task切換,也是這樣乾的嗎?)   1> 每次中斷返回返回或系統調用返回時(見ret_from_intr),都會判定TIF_NEED_RESCHED標記,如有則會調用schedule()重新調度,《極簡cfs公平調度演算法》中未暫開講put_prev_task_fair(),而throttle就是在這裡乾的
void schedule()
{
    prev = rq->curr;
    put_prev_task_fair(rq, prev);
    // 選擇下一個task並切換運行
    next = pick_next_task(rq);
    context_switch(rq, prev, next);
}
  2> put_prev_task_fair() → put_prev_entity() → check_cfs_rq_runtime()
void put_prev_task_fair(struct rq* rq, struct task_struct* prev)
{
    struct sched_entity* se = &prev->se;
    put_prev_entity(se->cfs_rq, se);
}
 
void put_prev_entity(struct cfs_rq* cfs_rq, struct sched_entity* prev)
{
    check_cfs_rq_runtime(cfs_rq);
}

 

3> check_cfs_rq_runtime()這裡判定runtime_remaining不足時,就要調用throttle_cfs_rq()進行throttle
void check_cfs_rq_runtime(struct cfs_rq* cfs_rq)
{
    if (cfs_rq->runtime_remaining > 0)
        return;
    throttle_cfs_rq(cfs_rq);
}

 

4> throttle_cfs_rq()將group se從rq.cfs_rq中移除,這樣整個group下的task就不再會被調度了
void throttle_cfs_rq(struct cfs_rq* cfs_rq)
{
    struct sched_entity*  se = cfs_rq->tg->se[cpu_of(rq_of(cfs_rq))];       // 取對應cpu rq上的group se
    dequeue_entity(se->cfs_rq, se, DEQUEUE_SLEEP);                          //從cpu rq中刪除group se
    cfs_rq->throttled = 1;                                                  // 標記group cfs_rq被throttled
}

 

6.3 cpu quota重新分配 6.2中group se被從rq移除後,不再會被調度,這時經過一個period周期,定時器激活後,就會再次加入到rq中重新調度   1> cfs_bandwidth的定期器初始化回調函數為sched_cfs_period_timer()
viod init_cfs_bandwidth(struct cfs_bandwidth* cfs_b)
{
    hrtimer_init(&cfs_b->period_timer, CLOCK_MONOTONIC, HRTIMER_MODE_REL);
    cfs_b->period_timer.function = sched_cfs_period_timer;
}

 

2> 定時器到期後回調sched_cfs_period_timer(),其只是簡單調用實際幹活的do_sched_cfs_period_timer()
enum hrtimer_restart sched_cfs_period_timer(struct hrtimer* timer)
{
    idle = do_sched_cfs_period_timer(cfs_b, overrun);
    return idle ? HRTIMER_NORESTART : HRTIMER_RESTART;
}

 

3> do_sched_cfs_period_timer()調用__refill_cfs_bandwidth_runtime()重新分配task_group的runtime,然後調用distribute_cfs_runtime()進行unthrottle
int do_sched_cfs_period_timer(struct cfs_bandwidth* cfs_b, int overrun)
{
    __refill_cfs_bandwidth_runtime(cfs_b);
    distribute_cfs_runtime(cfs_b, runtime, runtime_expires);
}

 

4> __refill_cfs_bandwidth_runtime()就是將task_group.cfs_bandwidth.runtime重置為設置的cpu quota
void __refill_cfs_bandwidth_runtime(struct cfs_bandwidth* cfs_b)
{
    cfs_b->runtime = cfs_b->quota;
}

 

5> distribute_cfs_runtime()調用unthrottle_cfs_rq()將所有se加回到rq上去,這樣group下的task就能重新調度了
u64 distribute_cfs_runtime(struct cfs_bandwidth* cfs_b, u64 remaining, u64  expires)
{
    struct cfs_rq* cfs_rq;
    list_for_each_entry_rcu(cfs_rq, &cfs_b->throttled_cfs_rq, throttled_list)
    {
        unthrottle_cfs_rq(cfs_rq);
    }
}
 
void unthrottle_cfs_rq(struct cfs_rq* cfs_rq)
{
    se = cfs_rq->tg->se[cpu_of(rq_of(cfs_rq))];
    enqueue_entity(cfs_rq, se, ENQUEUE_WAKEUP);     // 將se加回rq.cfs_rq的紅黑樹上
}

 

  本文為博主原創文章,如需轉載請說明轉至http://www.cnblogs.com/organic/
您的分享是我們最大的動力!

-Advertisement-
Play Games
更多相關文章
  • 本系列文章導航 https://www.cnblogs.com/aierong/p/17300066.html https://github.com/aierong/WpfDemo (自我Demo地址) 希望提到的知識對您有所提示,同時歡迎交流和指正 作者:aierong出處:https://www ...
  • 安裝 NuGet 包:在 Visual Studio 中打開項目,右鍵單擊項目名稱,選擇“管理 NuGet 包”,搜索“Quartz”並安裝。 創建作業:創建一個實現了 IJob 介面的類,該介面包含一個 Execute 方法,該方法將在作業運行時調用。例如: public class MyJob ...
  • 朋友做網站需要根據城市展示天氣預報,找了一圈沒有找到靠譜的介面,今天在中央氣象臺的官網查詢某個城市找到了介面,先用postman試了一下居然可以使用,可以查詢某個城市7天的天氣預報等信息。但是查詢編碼是氣象臺自己的編碼,在網上搜索了一下居然有這個編碼。本文使用HttpClient方法查詢這個介面。 ...
  • #總覽需求 1. 簡述靜態網頁和動態網頁的區別。 2. 簡述 Webl.0 和 Web2.0 的區別。 3. 安裝tomcat8,配置服務啟動腳本,部署jpress應用。 1、簡述靜態網頁和動態網頁的區別 靜態網頁: 請求響應信息,發給客戶端進行處理,由瀏覽器進行解析,顯示的頁面,靜態網頁包含文本、 ...
  • title: msp430點燈實驗 date: 2023-04-15 15:31:25 description: 基於msp430f5529點燈實驗 一、實驗內容 使用開發板:msp430f5529 使用的LED燈:為開發板上自帶的User LEDs(LED1、LED2) 環境:CCS (Versi ...
  • 一、 yum mysql5.7以下 mysql5.7以上 Centos8 可以,但是需要重新配置文件 可以,但是需要重新配置文件 可以,但是需要重新配置文件 Centos7 可以直接yum,但是是安裝mariadb-server。如果是mysql-server需要配置文件 直接yum後啟動就好 yu ...
  • 本文章來自我的微信個人技術公眾號 網路技術修煉,公眾號中總結普及網路基礎知識,包括基礎原理、網路方案、開發經驗和問題定位案例等,歡迎關註。 Linux網路開發者面臨的問題往往比較複雜,因此需要使用一些工具和命令來進行定位和解決。在本篇博客中,我將總結一些常用的Linux網路開發者工具和命令,包括網路 ...
  • 哈嘍大家好,我是鹹魚。今天跟大家分享一個關於正則表達式的案例,希望能夠對你有所幫助 案例現象 前幾天有一個小伙伴在群里求助,說他這個 shell 腳本有問題,讓大家幫忙看看 可以看到,這個腳本首先將目標文本文件的名字當作該腳本的第一個參數($1)傳遞進去,然後查看這個文本文件的內容(cat $1), ...
一周排行
    -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.數據驗證 在伺服器端進行嚴格的數據驗證,確保接收到的數據符合預期格 ...