<kbd id="afajh"><form id="afajh"></form></kbd>
<strong id="afajh"><dl id="afajh"></dl></strong>
    <del id="afajh"><form id="afajh"></form></del>
        1. <th id="afajh"><progress id="afajh"></progress></th>
          <b id="afajh"><abbr id="afajh"></abbr></b>
          <th id="afajh"><progress id="afajh"></progress></th>

          Linux 完全公平調(diào)度算法

          共 6829字,需瀏覽 14分鐘

           ·

          2021-02-05 12:19

          Linux 進(jìn)程調(diào)度算法經(jīng)歷了以下幾個(gè)版本的發(fā)展:

          • 基于時(shí)間片輪詢調(diào)度算法。(2.6之前的版本)

          • O(1) 調(diào)度算法。(2.6.23之前的版本)

          • 完全公平調(diào)度算法。(2.6.23以及之后的版本)

          之前我寫過一篇分析?O(1)調(diào)度算法?的文章:O(1)調(diào)度算法,而這篇主要分析 Linux 現(xiàn)在所使用的?完全公平調(diào)度算法

          分析?完全公平調(diào)度算法?前,我們先了解下?完全公平調(diào)度算法?的基本原理。

          完全公平調(diào)度算法基本原理

          完全公平調(diào)度算法?體現(xiàn)在對(duì)待每個(gè)進(jìn)程都是公平的,那么怎么才能做到完全公平呢?有一個(gè)比較簡單的方法就是:讓每個(gè)進(jìn)程都運(yùn)行一段相同的時(shí)間片,這就是?基于時(shí)間片輪詢調(diào)度算法。如下圖所示:

          bc03ece235d5a48371c2920dc8cb010f.webp

          如上圖所示,開始時(shí)進(jìn)程1獲得?time0 ~ time1?的CPU運(yùn)行時(shí)間。當(dāng)進(jìn)程1的時(shí)間片使用完后,進(jìn)程2獲得?time1 ~ time2?的CPU運(yùn)行時(shí)間。而當(dāng)進(jìn)程2的時(shí)間片使用完后,進(jìn)程3獲得?time2 ~ time3?的CPU運(yùn)行時(shí)間。

          如此類推,由于每個(gè)時(shí)間片都是相等的,所以理論上每個(gè)進(jìn)程都能獲得相同的CPU運(yùn)行時(shí)間。這個(gè)算法看起來很不錯(cuò),但存在兩個(gè)問題:

          • 不能按權(quán)重分配不同的運(yùn)行時(shí)間,例如有些進(jìn)程權(quán)重大的應(yīng)該獲得更多的運(yùn)行時(shí)間。

          • 每次調(diào)度時(shí)都需要遍歷運(yùn)行隊(duì)列中的所有進(jìn)程,找到優(yōu)先級(jí)最大的進(jìn)程運(yùn)行,時(shí)間復(fù)雜度為?O(n)。對(duì)于一個(gè)高性能的操作系統(tǒng)來說,這是不能接受的。

          為了解決上面兩個(gè)問題,Linux內(nèi)核的開發(fā)者創(chuàng)造了?完全公平調(diào)度算法。

          完全公平調(diào)度的兩個(gè)時(shí)間

          為了實(shí)現(xiàn)?完全公平調(diào)度算法,為進(jìn)程定義兩個(gè)時(shí)間:

          1. 實(shí)際運(yùn)行時(shí)間

          實(shí)際運(yùn)行時(shí)間 = 調(diào)度周期 * 進(jìn)程權(quán)重 / 所有進(jìn)程權(quán)重之和

          • 調(diào)度周期:是指所有可進(jìn)程運(yùn)行一遍所需要的時(shí)間。

          • 進(jìn)程權(quán)重:依據(jù)進(jìn)程的重要性,分配給每個(gè)進(jìn)程不同的權(quán)重。

          例如,調(diào)度周期為 30ms,進(jìn)程A的權(quán)重為 1,而進(jìn)程B的權(quán)重為 2。那么:

          進(jìn)程A的實(shí)際運(yùn)行時(shí)間為30ms * 1 / (1 + 2) = 10ms

          進(jìn)程B的實(shí)際運(yùn)行時(shí)間為30ms * 2 / (1 + 2) = 20ms

          1. 虛擬運(yùn)行時(shí)間

          虛擬運(yùn)行時(shí)間 = 實(shí)際運(yùn)行時(shí)間 * 1024 / 進(jìn)程權(quán)重 = (調(diào)度周期 * 進(jìn)程權(quán)重 / 所有進(jìn)程權(quán)重之和) * 1024 / 進(jìn)程權(quán)重 = 調(diào)度周期 * 1024 / 所有進(jìn)程總權(quán)重

          從上面的公式可以看出,在一個(gè)調(diào)度周期里,所有進(jìn)程的?虛擬運(yùn)行時(shí)間?是相同的。所以在進(jìn)程調(diào)度時(shí),只需要找到?虛擬運(yùn)行時(shí)間?最小的進(jìn)程調(diào)度運(yùn)行即可。

          為了能夠快速找到?虛擬運(yùn)行時(shí)間?最小的進(jìn)程,Linux 內(nèi)核使用?紅黑樹?來保存可運(yùn)行的進(jìn)程,而比較的鍵值就是進(jìn)程的?虛擬運(yùn)行時(shí)間

          如果不了解?紅黑樹?的話,可以把它看成一個(gè)自動(dòng)排序的容器即可。如下圖所示:

          df19c79f53e0bd58f096b2b5ea5a0c77.webp

          如上圖所示,紅黑樹?的左節(jié)點(diǎn)比父節(jié)點(diǎn)小,而右節(jié)點(diǎn)比父節(jié)點(diǎn)大。所以查找最小節(jié)點(diǎn)時(shí),只需要獲取?紅黑樹?的最左節(jié)點(diǎn)即可,時(shí)間復(fù)雜度為?O(logN)。

          完全公平調(diào)度的兩個(gè)對(duì)象

          Linux 內(nèi)核為了實(shí)現(xiàn)?完全公平調(diào)度算法,定義兩個(gè)對(duì)象:cfs_rq (可運(yùn)行進(jìn)程隊(duì)列)?和?sched_entity (調(diào)度實(shí)體)。

          • cfs_rq (可運(yùn)行進(jìn)程隊(duì)列):使用?紅黑樹?結(jié)構(gòu)來保存內(nèi)核中可以運(yùn)行的進(jìn)程。

          • sched_entity (調(diào)度實(shí)體):可被內(nèi)核調(diào)度的實(shí)體,如果忽略組調(diào)度(本文也不涉及組調(diào)度),可以把它當(dāng)成是進(jìn)程。

          cfs_rq?對(duì)象定義

          struct cfs_rq {    struct load_weight load;    unsigned long nr_running;       // 運(yùn)行隊(duì)列中的進(jìn)程數(shù)    u64 exec_clock;                 // 當(dāng)前時(shí)鐘    u64 min_vruntime;               // 用于修證虛擬運(yùn)行時(shí)間
          struct rb_root tasks_timeline; // 紅黑樹的根節(jié)點(diǎn) struct rb_node *rb_leftmost; // 緩存紅黑樹最左端節(jié)點(diǎn), 用于快速獲取最小節(jié)點(diǎn) ...};

          對(duì)于?cfs_rq?對(duì)象,現(xiàn)在主要關(guān)注的是?tasks_timeline?成員,其保存了可運(yùn)行進(jìn)程隊(duì)列的根節(jié)點(diǎn)(紅黑樹的根節(jié)點(diǎn))。

          sched_entity?對(duì)象定義

          struct sched_entity {  struct load_weight  load;??struct?rb_node????run_node;?????????//?用于連接到運(yùn)行隊(duì)列的紅黑樹中  struct list_head  group_node;??unsigned?int????on_rq;??????????????//?是否已經(jīng)在運(yùn)行隊(duì)列中
          u64 exec_start; // 開始統(tǒng)計(jì)運(yùn)行時(shí)間的時(shí)間點(diǎn) u64 sum_exec_runtime; // 總共運(yùn)行的實(shí)際時(shí)間 u64 vruntime; // 虛擬運(yùn)行時(shí)間(用于紅黑樹的鍵值對(duì)比) u64 prev_sum_exec_runtime; // 總共運(yùn)行的虛擬運(yùn)行時(shí)間
          u64 last_wakeup; u64 avg_overlap; ...};

          對(duì)于?sched_entity?對(duì)象,現(xiàn)在主要關(guān)注的是?run_node?成員,其主要用于把調(diào)度實(shí)體連接到可運(yùn)行進(jìn)程的隊(duì)列中。

          另外,進(jìn)程描述符?task_struct?對(duì)象中,定義了一個(gè)類型為?sched_entity?的成員變量?se,如下:

          struct task_struct {    ...    struct sched_entity se;    ...}

          所以,他們之間的關(guān)系如下圖:

          36db3e2a01ba23f81dd36e4c281be321.webp

          從上圖可以看出,所有?sched_entity?對(duì)象通過其?run_node?成員組成了一顆紅黑樹,這顆紅黑樹的根結(jié)點(diǎn)保存在?cfs_rq?對(duì)象的?task_timeline?成員中。

          另外,進(jìn)程描述符?task_sturct?定義了一個(gè)類型為?sched_entity?的成員變量?se,所以通過進(jìn)程描述符的?se?字段就可以把進(jìn)程保存到可運(yùn)行隊(duì)列中。

          完全公平調(diào)度算法實(shí)現(xiàn)

          有了上面的基礎(chǔ),現(xiàn)在可以開始分析 Linux 內(nèi)核中怎么實(shí)現(xiàn)?完全公平調(diào)度算法?了。

          我們先來看看怎么更新一個(gè)進(jìn)程的虛擬運(yùn)行時(shí)間。

          1. 更新進(jìn)程虛擬運(yùn)行時(shí)間

          更新一個(gè)進(jìn)程的虛擬運(yùn)行時(shí)間是通過?__update_curr()?函數(shù)完成的,其代碼如下:

          /src/kernel/sched_fair.c

          static inline void__update_curr(struct cfs_rq *cfs_rq, struct sched_entity *curr,              unsigned long delta_exec){    unsigned long delta_exec_weighted;
          curr->sum_exec_runtime += delta_exec; // 增加進(jìn)程總實(shí)際運(yùn)行的時(shí)間 delta_exec_weighted = delta_exec; // 初始化進(jìn)程使用的虛擬運(yùn)行時(shí)間
          // 根據(jù)進(jìn)程的權(quán)重計(jì)算其使用的虛擬運(yùn)行時(shí)間 if (unlikely(curr->load.weight != NICE_0_LOAD)) { delta_exec_weighted = calc_delta_fair(delta_exec_weighted, &curr->load); }
          curr->vruntime += delta_exec_weighted; // 更新進(jìn)程的虛擬運(yùn)行時(shí)間}

          __update_curr()?函數(shù)各個(gè)參數(shù)意義如下:

          • cfs_rq:可運(yùn)行隊(duì)列對(duì)象。

          • curr:當(dāng)前進(jìn)程調(diào)度實(shí)體。

          • delta_exec:實(shí)際運(yùn)行的時(shí)間。

          __update_curr()?函數(shù)主要完成以下幾個(gè)工作:

          1. 更新進(jìn)程調(diào)度實(shí)體的總實(shí)際運(yùn)行時(shí)間。

          2. 根據(jù)進(jìn)程調(diào)度實(shí)體的權(quán)重值,計(jì)算其使用的虛擬運(yùn)行時(shí)間。

          3. 把計(jì)算虛擬運(yùn)行時(shí)間的結(jié)果添加到進(jìn)程調(diào)度實(shí)體的?vruntime?字段。

          我們接著分析怎么把進(jìn)程添加到運(yùn)行隊(duì)列中。

          2. 把進(jìn)程調(diào)度實(shí)體添加到運(yùn)行隊(duì)列中

          要將進(jìn)程調(diào)度實(shí)體添加到運(yùn)行隊(duì)列中,可以調(diào)用?__enqueue_entity()?函數(shù),其實(shí)現(xiàn)如下:

          /src/kernel/sched_fair.c

          static void __enqueue_entity(struct cfs_rq *cfs_rq, struct sched_entity *se){    struct rb_node **link = &cfs_rq->tasks_timeline.rb_node; // 紅黑樹根節(jié)點(diǎn)    struct rb_node *parent = NULL;    struct sched_entity *entry;    s64 key = entity_key(cfs_rq, se); // 當(dāng)前進(jìn)程調(diào)度實(shí)體的虛擬運(yùn)行時(shí)間    int leftmost = 1;
          while (*link) { // 把當(dāng)前調(diào)度實(shí)體插入到運(yùn)行隊(duì)列的紅黑樹中 parent = *link; entry = rb_entry(parent, struct sched_entity, run_node);
          if (key < entity_key(cfs_rq, entry)) { // 比較虛擬運(yùn)行時(shí)間 link = &parent->rb_left; } else { link = &parent->rb_right; leftmost = 0; } }
          if (leftmost) { cfs_rq->rb_leftmost = &se->run_node; // 緩存紅黑樹最左節(jié)點(diǎn) cfs_rq->min_vruntime = max_vruntime(cfs_rq->min_vruntime, se->vruntime); }
          // 這里是紅黑樹的平衡過程(參考紅黑樹數(shù)據(jù)結(jié)構(gòu)的實(shí)現(xiàn)) rb_link_node(&se->run_node, parent, link); rb_insert_color(&se->run_node, &cfs_rq->tasks_timeline);}

          __enqueue_entity()?函數(shù)的主要工作如下:

          1. 獲取運(yùn)行隊(duì)列紅黑樹的根節(jié)點(diǎn)。

          2. 獲取當(dāng)前進(jìn)程調(diào)度實(shí)體的虛擬運(yùn)行時(shí)間。

          3. 把當(dāng)前進(jìn)程調(diào)度實(shí)體添加到紅黑樹中(可參考紅黑樹的插入算法)。

          4. 緩存紅黑樹最左端節(jié)點(diǎn)。

          5. 對(duì)紅黑樹進(jìn)行平衡操作(可參考紅黑樹的平衡算法)。

          調(diào)用?__enqueue_entity()?函數(shù)后,就可以把進(jìn)程調(diào)度實(shí)體插入到運(yùn)行隊(duì)列的紅黑樹中。同時(shí)會(huì)把紅黑樹最左端的節(jié)點(diǎn)緩存到運(yùn)行隊(duì)列的?rb_leftmost?字段中,用于快速獲取下一個(gè)可運(yùn)行的進(jìn)程。

          3. 從可運(yùn)行隊(duì)列中獲取下一個(gè)可運(yùn)行的進(jìn)程

          要獲取運(yùn)行隊(duì)列中下一個(gè)可運(yùn)行的進(jìn)程可以通過調(diào)用?__pick_next_entity()?函數(shù),其實(shí)現(xiàn)如下:

          /src/kernel/sched_fair.c

          static struct sched_entity *__pick_next_entity(struct cfs_rq *cfs_rq){    // 1. 先調(diào)用 first_fair() 獲取紅黑樹最左端節(jié)點(diǎn)    // 2. 調(diào)用 rb_entry() 返回節(jié)點(diǎn)對(duì)應(yīng)的調(diào)度實(shí)體    return rb_entry(first_fair(cfs_rq), struct sched_entity, run_node);}
          static inline struct rb_node *first_fair(struct cfs_rq *cfs_rq){ return cfs_rq->rb_leftmost; // 獲取紅黑樹最左端節(jié)點(diǎn)}

          前面介紹過,紅黑樹的最左端節(jié)點(diǎn)就是虛擬運(yùn)行時(shí)間最少的進(jìn)程,所以?__pick_next_entity()?函數(shù)的流程就非常簡單了,如下:

          • 首先調(diào)用?first_fair()?獲取紅黑樹最左端節(jié)點(diǎn)。

          • 然后再調(diào)用?rb_entry()?返回節(jié)點(diǎn)對(duì)應(yīng)的調(diào)度實(shí)體。

          調(diào)度時(shí)機(jī)

          前面介紹了?完全公平調(diào)度算法?怎么向可運(yùn)行隊(duì)列添加調(diào)度的進(jìn)程和怎么從可運(yùn)行隊(duì)列中獲取下一個(gè)可運(yùn)行的進(jìn)程,那么 Linux 內(nèi)核在什么時(shí)候會(huì)進(jìn)行進(jìn)程調(diào)度呢?

          答案是由 Linux 內(nèi)核的時(shí)鐘中斷觸發(fā)的。

          時(shí)鐘中斷?是什么?如果沒了解過操作系統(tǒng)原理的可能不了解這個(gè)東西,但是沒關(guān)系,不了解可以把他當(dāng)成是定時(shí)器就好了,就是每隔固定一段時(shí)間會(huì)調(diào)用一個(gè)回調(diào)函數(shù)來處理這個(gè)事件。

          時(shí)鐘中斷?猶如 Linux 的心臟一樣,每隔一定的時(shí)間就會(huì)觸發(fā)調(diào)用?scheduler_tick()?函數(shù),其實(shí)現(xiàn)如下:

          void scheduler_tick(void){    ...    curr->sched_class->task_tick(rq, curr, 0); // 這里會(huì)調(diào)用 task_tick_fair() 函數(shù)    ...}

          scheduler_tick()?函數(shù)會(huì)調(diào)用?task_tick_fair()?函數(shù)處理調(diào)度相關(guān)的工作,而?task_tick_fair()?主要通過調(diào)用?entity_tick()?來完成調(diào)度工作的,調(diào)用鏈如下:

          scheduler_tick() -> task_tick_fair() -> entity_tick()

          entity_tick()?函數(shù)實(shí)現(xiàn)如下:

          static voidentity_tick(struct cfs_rq *cfs_rq, struct sched_entity *curr, int queued){    // 更新當(dāng)前進(jìn)程的虛擬運(yùn)行時(shí)間    update_curr(cfs_rq);    ...    if (cfs_rq->nr_running > 1 || !sched_feat(WAKEUP_PREEMPT))        check_preempt_tick(cfs_rq, curr); // 判斷是否需要進(jìn)行進(jìn)程調(diào)度}

          entity_tick()?函數(shù)主要完成以下工作:

          • 調(diào)用?update_curr()?函數(shù)更新進(jìn)程的虛擬運(yùn)行時(shí)間,這個(gè)前面已經(jīng)介紹過。

          • 調(diào)用?check_preempt_tick()?函數(shù)判斷是否需要進(jìn)行進(jìn)程調(diào)度。

          我們接著分析?check_preempt_tick()?函數(shù)的實(shí)現(xiàn):

          static voidcheck_preempt_tick(struct cfs_rq *cfs_rq, struct sched_entity *curr){    unsigned long ideal_runtime, delta_exec;
          // 計(jì)算當(dāng)前進(jìn)程可用的時(shí)間片 ideal_runtime = sched_slice(cfs_rq, curr);
          // 進(jìn)程運(yùn)行的實(shí)際時(shí)間 delta_exec = curr->sum_exec_runtime - curr->prev_sum_exec_runtime;
          // 如果進(jìn)程運(yùn)行的實(shí)際時(shí)間大于其可用時(shí)間片, 那么進(jìn)行調(diào)度 if (delta_exec > ideal_runtime) resched_task(rq_of(cfs_rq)->curr);}

          check_preempt_tick()?函數(shù)主要完成以下工作:

          • 通過調(diào)用?sched_slice()?計(jì)算當(dāng)前進(jìn)程可用的時(shí)間片。

          • 獲取當(dāng)前進(jìn)程在當(dāng)前調(diào)度周期實(shí)際已運(yùn)行的時(shí)間。

          • 如果進(jìn)程實(shí)際運(yùn)行的時(shí)間大于其可用時(shí)間片, 那么調(diào)用?resched_task()?函數(shù)進(jìn)行進(jìn)程調(diào)度。

          可以看出,在?時(shí)鐘中斷?的處理中,有可能會(huì)進(jìn)行進(jìn)程調(diào)度。除了?時(shí)鐘中斷?外,一些主動(dòng)讓出 CPU 的操作也會(huì)進(jìn)行進(jìn)程調(diào)度(如一些 I/O 操作),這里就不詳細(xì)分析了,有興趣可以自己研究。


          瀏覽 52
          點(diǎn)贊
          評(píng)論
          收藏
          分享

          手機(jī)掃一掃分享

          分享
          舉報(bào)
          評(píng)論
          圖片
          表情
          推薦
          點(diǎn)贊
          評(píng)論
          收藏
          分享

          手機(jī)掃一掃分享

          分享
          舉報(bào)
          <kbd id="afajh"><form id="afajh"></form></kbd>
          <strong id="afajh"><dl id="afajh"></dl></strong>
            <del id="afajh"><form id="afajh"></form></del>
                1. <th id="afajh"><progress id="afajh"></progress></th>
                  <b id="afajh"><abbr id="afajh"></abbr></b>
                  <th id="afajh"><progress id="afajh"></progress></th>
                  骚资源| 亚洲成人资源网 | 一级特黄60分钟免费看 | 亚洲免费一级片 | 九色PORNY原创自拍 |