簡介:本文主要介紹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進行調度
![](https://img2023.cnblogs.com/blog/818872/202304/818872-20230415084851148-243944255.png)
說明 | |
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運行隊列中調度。 |
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的流程![](https://img2023.cnblogs.com/blog/818872/202304/818872-20230415084851184-756979868.png)
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
![](https://img2023.cnblogs.com/blog/818872/202304/818872-20230415084851157-1952351171.png)
![](https://img2023.cnblogs.com/blog/818872/202304/818872-20230415084851179-802681373.png)
![](https://img2023.cnblogs.com/blog/818872/202304/818872-20230415084851183-844823687.png)
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切換,也是這樣乾的嗎?)
![點擊下載](https://img2023.cnblogs.com/blog/818872/202304/818872-20230415084851149-600469685.png)
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中重新調度
![](https://img2023.cnblogs.com/blog/818872/202304/818872-20230415084851157-523974700.png)
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/