成人免费xxxxx在线视频软件_久久精品久久久_亚洲国产精品久久久_天天色天天色_亚洲人成一区_欧美一级欧美三级在线观看

Linux 進(jìn)程管理之 CFS 調(diào)度策略

系統(tǒng) Linux
CFS 的產(chǎn)生就是為了在真實的硬件上模擬“理想的多任務(wù)處理器”,使每個進(jìn)程都能夠公平的獲得 CPU。

CFS 原理

CFS(Completely Fair Scheduler), 也即是完全公平調(diào)度器。

CFS 的產(chǎn)生就是為了在真實的硬件上模擬“理想的多任務(wù)處理器”,使每個進(jìn)程都能夠公平的獲得 CPU。

CFS 調(diào)度器沒有時間片的概念,CFS 的理念就是讓每個進(jìn)程擁有相同的使用 CPU 的時間。比如有 n 個可運行的進(jìn)程,那么每個進(jìn)程將能獲取的處理時間為 1/n。

在 CFS 調(diào)度器中引用權(quán)重來代表進(jìn)程的優(yōu)先級。各個進(jìn)程按照權(quán)重的比例來分配使用 CPU 的時間。比如2個進(jìn)程 A 和 B, A 的權(quán)重為 100, B 的權(quán)重為200,那么 A 獲得的 CPU 的時間為 100/(100+200) = 33%, B 進(jìn)程獲得的CPU 的時間為 200/(100+200) = 67%。

在引入權(quán)重之后,在一個調(diào)度周期中分配給進(jìn)程的運行時間計算公式如下:

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

可以看到,權(quán)重越大,分到的運行時間越多。

  • 調(diào)度周期:在某個時間長度可以保證運行隊列中的每個進(jìn)程至少運行一次,我們把這個時間長度稱為調(diào)度周期。也稱為調(diào)度延遲,因為一個進(jìn)程等待被調(diào)度的延遲時間是一個調(diào)度周期。
  • 調(diào)度最小粒度:為了防止進(jìn)程切換太頻繁,進(jìn)程被調(diào)度后應(yīng)該至少運行一小段時間,我們把這個時間長度稱為調(diào)度最小粒度。

調(diào)度周期的默認(rèn)值是20毫秒,調(diào)度最小粒度的默認(rèn)值是4毫秒,如下所示,兩者的單位都是納秒。

//默認(rèn)調(diào)度周期 20ms
unsigned int sysctl_sched_latency = 20000000ULL;
//默認(rèn)調(diào)度最小粒度 4ms
unsigned int sysctl_sched_min_granularity = 4000000ULL;
// 默認(rèn)一個調(diào)度周期內(nèi)的進(jìn)程數(shù):sysctl_sched_latency / sysctl_sched_min_granularity
static unsigned int sched_nr_latency = 5;

如果運行隊列中的進(jìn)程數(shù)量太多,導(dǎo)致把調(diào)度周期 sysctl_sched_latency 平分給進(jìn)程時的時間片小于調(diào)度最小粒度,那么調(diào)度周期取 “調(diào)度最小粒度 × 進(jìn)程數(shù)量”。

CFS 調(diào)度器中使用 nice 值(取值范圍為[-20 ~ 19])作為進(jìn)程獲取處理器運行比的權(quán)重:nice 值越高(優(yōu)先級越低)的進(jìn)程獲得的 CPU使用的權(quán)重越低。

在用戶態(tài)進(jìn)程的優(yōu)先級 nice 值與 CFS 調(diào)度器中的權(quán)重又有什么關(guān)系?

在內(nèi)核中通過 prio_to_weight 數(shù)組進(jìn)行 nice 值和權(quán)重的轉(zhuǎn)換。

static const int prio_to_weight[40] = {
/* -20 */ 88761, 71755, 56483, 46273, 36291,
/* -15 */ 29154, 23254, 18705, 14949, 11916,
/* -10 */ 9548, 7620, 6100, 4904, 3906,
/* -5 */ 3121, 2501, 1991, 1586, 1277,
/* 0 */ 1024, 820, 655, 526, 423,
/* 5 */ 335, 272, 215, 172, 137,
/* 10 */ 110, 87, 70, 56, 45,
/* 15 */ 36, 29, 23, 18, 15,
};

從 nice 和權(quán)重的對應(yīng)值可知,nice 值為 0 的權(quán)重為 1024(默認(rèn)權(quán)重), nice 值為1的權(quán)重為 820,nice 值為 15 的權(quán)重值為 36,nice 值為19的權(quán)重值為 15。

例如:假設(shè)調(diào)度周期為12ms,2個相同nice的進(jìn)程其權(quán)重也相同,那么2個進(jìn)程各自的運行時間為 6ms。

假設(shè)進(jìn)程 A 和 B 的 nice 值分別為0、1,那么權(quán)重也就分別為 1024、820。因此,A 實際運行時間為 12 * 1024/(1024+820)= 6.66ms ,B 實際運行時間為 12 * 820/(1024+820)= 5.34ms。

從結(jié)果來看,2個進(jìn)程運行時間時不一樣的。由于A的權(quán)重高,優(yōu)先級大,會出現(xiàn) A 一直被調(diào)度,而 B 最后被調(diào)度,這就失去了公平性,所以 CFS 的存在就是為了解決 這種不公平性。

因此為了讓每個進(jìn)程完全公平調(diào)度,因此就引入了一個 vruntime (虛擬運行時間,virtual runtime)的概念, 每個調(diào)度實體都有一個 vruntime,該vruntime 根據(jù)調(diào)度實體的調(diào)度而不停的累加,CFS 根據(jù) vruntime 的大小來選擇調(diào)度實體。

調(diào)度實體的結(jié)構(gòu)如下:

struct sched_entity {
struct load_weight load; // 調(diào)度實體的負(fù)載權(quán)重值
struct rb_node run_node; //用于添加到CFS運行隊列的紅黑樹中的節(jié)點
unsigned int on_rq;//用于表示是否在運行隊列中
u64 exec_start;//當(dāng)前調(diào)度實體的開始運行時間
u64 sum_exec_runtime;//調(diào)度實體執(zhí)行的總時間
/*

虛擬運行時間,在時間中斷或者任務(wù)狀態(tài)發(fā)生改變時會更新

其會不停的增長,增長速度與load權(quán)重成反比,load越高,增長速度越慢,就越可能處于紅黑樹最左邊被調(diào)度

每次時鐘中斷都會修改其值。注意其值為單調(diào)遞增,在每個調(diào)度器的時鐘中斷時當(dāng)前進(jìn)程的虛擬運行時間都會累加。

單純的說就是進(jìn)程們都在比誰的vruntime最小,最小的將被調(diào)度

*/
u64 vruntime;//虛擬運行時間,這個時間用于在CFS運行隊列中排隊
u64 prev_sum_exec_runtime;//上一個調(diào)度實體運行的總時間
...
#ifdef CONFIG_FAIR_GROUP_SCHED
struct sched_entity *parent; //指向調(diào)度實體的父對象
/* rq on which this entity is (to be) queued: */
struct cfs_rq *cfs_rq; //指向調(diào)度實體歸屬的CFS隊列,也就是需要入列的CFS隊列
/* rq "owned" by this entity/group: */
struct cfs_rq *my_q;//指向歸屬于當(dāng)前調(diào)度實體的CFS隊列
#endif
};

虛擬時間和實際時間的關(guān)系如下:

虛擬運行時間 = 實際運行時間 *(NICE_0_LOAD / 進(jìn)程權(quán)重)

其中,NICE_0_LOAD 是 nice為0時的權(quán)重(默認(rèn)),也即是 1024。也就是說,nice 值為0的進(jìn)程實際運行時間和虛擬運行時間相同。

虛擬運行時間一方面跟進(jìn)程運行時間有關(guān),另一方面跟進(jìn)程優(yōu)先級有關(guān)。

進(jìn)程權(quán)重越大, 運行同樣的實際時間, vruntime 增長的越慢。

一個進(jìn)程在一個調(diào)度周期內(nèi)的虛擬運行時間大小為:

vruntime = 進(jìn)程在一個調(diào)度周期內(nèi)的實際運行時間 * 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)重。

可以看到, 一個進(jìn)程在一個調(diào)度周期內(nèi)的 vruntime 值大小是不和該進(jìn)程自己的權(quán)重相關(guān)的, 所以所有進(jìn)程的 vruntime 值大小都是一樣的。

接著上述的例子,通過虛擬運行時間公式可得:

A 虛擬運行時間為 6.66 * (1024/1024) = 6.66ms,

B 虛擬運行時間為 5.34* (1024/820) = 6.66ms,

在一個調(diào)度周期過程中,各個調(diào)度實體的 vruntime 都是累加的過程,保證了在一個調(diào)度周期結(jié)束后,每個調(diào)度實體的 vruntime 值大小都是一樣的。

由于權(quán)重越高,應(yīng)該優(yōu)先的得到運行,因此 CFS 采用虛擬運行時間越小,越先調(diào)度。

當(dāng)權(quán)重越高的進(jìn)程隨著調(diào)度的次數(shù)多,其 vruntime 的累加也就越多。當(dāng)其 vruntime 的累加大于其他低優(yōu)先級進(jìn)程的 vruntime 時,低優(yōu)先級的進(jìn)程得以調(diào)度。這就保證了每個進(jìn)程都可以調(diào)度而不會出現(xiàn)高優(yōu)先級的一直得到調(diào)度,而優(yōu)先級低的進(jìn)程得不到調(diào)度而產(chǎn)生饑餓。

一言以蔽之:在CFS中,不管權(quán)重高低,根據(jù) vruntime 大小比較,大家都輪著使用 CPU。

當(dāng)然,根據(jù)一個調(diào)度周期中分配給進(jìn)程的實際運行時間計算公式可知,在一個調(diào)度周期內(nèi),雖然大家都輪著使用 CPU,但是實際運行時間的多少和權(quán)重也是有關(guān)的,權(quán)重越高,總的實際運行的時間也就越多。在一個調(diào)度周期結(jié)束后,各個調(diào)度實體的 vruntime 最終還是相等的。

那么在 CFS 中 vruntime 是怎么使用的呢?

CFS 中的就緒隊列是一棵以 vruntime 為鍵值的紅黑樹,虛擬時間越小的進(jìn)程越靠近整個紅黑樹的最左端。因此,調(diào)度器每次選擇位于紅黑樹最左端的那個進(jìn)程,該進(jìn)程的 vruntime 最小,也就最應(yīng)該優(yōu)先調(diào)度。

實際獲取最左葉子節(jié)點時并不會遍歷樹,而是 vruntime 最小的節(jié)點已經(jīng)緩存在了 rb_leftmost 字段中了,因此 CFS 很快可以獲取 vruntime 最小的節(jié)點。

其中紅黑樹的結(jié)構(gòu)如下:

CFS 調(diào)度時機

與 CFS 相關(guān)的有如下幾個過程:

  • 創(chuàng)建新進(jìn)程: 創(chuàng)建新進(jìn)程時, 需要設(shè)置新進(jìn)程的 vruntime 值以及將新進(jìn)程加入紅黑樹中. 并判斷是否需要搶占當(dāng)前進(jìn)程。
  • 進(jìn)程的調(diào)度: 進(jìn)程調(diào)度時, 需要把當(dāng)前進(jìn)程加入紅黑樹中, 還要從紅黑樹中挑選出下一個要運行的進(jìn)程。
  • 進(jìn)程喚醒: 喚醒進(jìn)程時, 需要調(diào)整睡眠進(jìn)程的 vruntime 值, 并且將睡眠進(jìn)程加入紅黑樹中. 并判斷是否需要搶占當(dāng)前進(jìn)程。
  • 時鐘周期中斷: 在時鐘中斷周期函數(shù)中, 需要更新當(dāng)前運行進(jìn)程的 vruntime 值, 并判斷是否需要搶占當(dāng)前進(jìn)程。

創(chuàng)建新進(jìn)程

創(chuàng)建新進(jìn)程的系統(tǒng)調(diào)用 fork, vfork, clone. 這三個系統(tǒng)調(diào)用最終都是調(diào)用do_fork() 函數(shù).在 do_fork() 函數(shù)中, 主要就是設(shè)置新進(jìn)程的 vruntime 值, 將新進(jìn)程加入到紅黑樹中, 判斷新進(jìn)程是否可以搶占當(dāng)前進(jìn)程.

do_fork
--> wake_up_new_task
--> task_new_fair //設(shè)置新進(jìn)程的vruntime值,并將其加入就緒隊列中
--> check_preempt_curr //檢查新進(jìn)程是否可以搶占當(dāng)前進(jìn)程

task_new_fair 實現(xiàn)如下:

static void task_new_fair(struct rq *rq, struct task_struct *p)
{
struct cfs_rq *cfs_rq = task_cfs_rq(p);
struct sched_entity *se = &p->se, *curr = cfs_rq->curr;
int this_cpu = smp_processor_id();
sched_info_queued(p);
update_curr(cfs_rq); //更新當(dāng)前進(jìn)程的vruntime值
place_entity(cfs_rq, se, 1); //設(shè)置新進(jìn)程的vruntime值, 1表示是新進(jìn)程
/* 'curr' will be NULL if the child belongs to a different group */
//sysctl_sched_child_runs_first值表示是否設(shè)置了讓子進(jìn)程先運行
if (sysctl_sched_child_runs_first && this_cpu == task_cpu(p) &&
curr && curr->vruntime < se->vruntime) {
/*
? Upon rescheduling, sched_class::put_prev_task() will place
? 'current' within the tree based on its new key value.
*/
swap(curr->vruntime, se->vruntime); //當(dāng)子進(jìn)程的vruntime值大于父進(jìn)程的vruntime時, 交換兩個進(jìn)程的vruntime值
}
enqueue_task_fair(rq, p, 0);//將新任務(wù)加入到就緒隊列中
//設(shè)置TIF_NEED_RESCHED標(biāo)志值,該標(biāo)記標(biāo)志進(jìn)程是否需要重新調(diào)度,如果設(shè)置了,就會發(fā)生調(diào)度
resched_task(rq->curr);
}

該函數(shù)給新進(jìn)程設(shè)置一個新的 vruntime,然后加入到就緒隊列中,等待調(diào)度。

check_preempt_curr 在 CFS 中 對應(yīng)的為 check_preempt_wakeup 函數(shù),其實現(xiàn)如下:

static void check_preempt_wakeup(struct rq *rq, struct task_struct *p)
{
struct task_struct *curr = rq->curr;
struct cfs_rq *cfs_rq = task_cfs_rq(curr);
struct sched_entity *se = &curr->se, *pse = &p->se;
unsigned long gran;
...
// gran 為進(jìn)程調(diào)度粒度
gran = sysctl_sched_wakeup_granularity; // 10 ms
if (unlikely(se->load.weight != NICE_0_LOAD))
gran = calc_delta_fair(gran, &se->load); //計算調(diào)度粒度
/*

調(diào)度粒度的設(shè)置, 是為了防止這么一個情況: 新進(jìn)程的vruntime值只比當(dāng)前進(jìn)程的vruntime小一點點, 如果此時發(fā)生重新調(diào)度,則新進(jìn)程只運行一點點時間后,其vruntime值就會大于前面被搶占的進(jìn)程的vruntime值, 這樣又會發(fā)生搶占,所以這樣的情況下,系統(tǒng)會發(fā)生頻繁的切換。故, 只有當(dāng)新進(jìn)程的vruntime值比當(dāng)前進(jìn)程的vruntime值小于調(diào)度粒度之外,才發(fā)生搶占。

所以,當(dāng)前進(jìn)程的虛擬運行時間se->vruntime 比 下一個進(jìn)程 pse->vruntime 大于一個調(diào)度粒度,說明當(dāng)前進(jìn)程應(yīng)該被搶占,應(yīng)該切換出去讓別vruntime小的進(jìn)程進(jìn)行運行,因此給當(dāng)前進(jìn)程設(shè)置是一個重新調(diào)度標(biāo)記TIF_NEED_RESCHED,當(dāng)某個時機根據(jù)該標(biāo)記進(jìn)行調(diào)用schedule(),這時會重新選擇一個進(jìn)程進(jìn)行切換。

*/
if (pse->vruntime + gran < se->vruntime)
resched_task(curr);
}

該功能就是根據(jù)調(diào)度粒度,判斷是否需要設(shè)置被搶占標(biāo)記,若需要,這調(diào)用resched_task 把當(dāng)前進(jìn)程設(shè)置成被搶占標(biāo)記TIF_NEED_RESCHED,該標(biāo)記說明當(dāng)前進(jìn)程運行的時間夠多的了,應(yīng)該切換出去,讓出 CPU 讓讓別的進(jìn)程運行。

static void resched_task(struct task_struct *p)
{
int cpu;
assert_spin_locked(&task_rq(p)->lock);
if (unlikely(test_tsk_thread_flag(p, TIF_NEED_RESCHED)))
return;
//設(shè)置被被搶占標(biāo)記
set_tsk_thread_flag(p, TIF_NEED_RESCHED);
cpu = task_cpu(p);
if (cpu == smp_processor_id())
return;
/* NEED_RESCHED must be visible before we test polling */
smp_mb();
if (!tsk_is_polling(p))
smp_send_reschedule(cpu);
}

設(shè)置完TIF_NEED_RESCHED 不代表當(dāng)前進(jìn)程立馬就切換出去了,而是等待一定時機,然后會根據(jù)TIF_NEED_RESCHED 標(biāo)記調(diào)用schedule()進(jìn)行調(diào)度切換。

進(jìn)程的調(diào)度

進(jìn)程調(diào)度的主要入口點是 schedule 函數(shù),它正是內(nèi)核其他部分用于調(diào)用進(jìn)程調(diào)度器的入口:選擇哪個進(jìn)程可以運行,何時投入運行。

schedule 通常要和一個具體的調(diào)度類相關(guān)聯(lián),也即是,它會找到最高優(yōu)先級的調(diào)度類,然后從就緒隊列中選擇下一個該運行的進(jìn)程。

當(dāng)調(diào)用 schedule() 進(jìn)行任務(wù)切換的時候,調(diào)度器調(diào)用 pick_next_task 函數(shù)選擇下一個將要執(zhí)行的任務(wù),這是相對默認(rèn) nice 值進(jìn)程的進(jìn)程而言的。

asmlinkage void __sched schedule(void)
{
/*prev 表示調(diào)度之前的進(jìn)程, next 表示調(diào)度之后的進(jìn)程 */
struct task_struct *prev, *next;
long *switch_count;
struct rq *rq;
int cpu;
need_resched:
preempt_disable(); //關(guān)閉內(nèi)核搶占
cpu = smp_processor_id(); //獲取所在的cpu
rq = cpu_rq(cpu); //獲取cpu對應(yīng)的運行隊列
rcu_qsctr_inc(cpu);
prev = rq->curr; /*讓prev 成為當(dāng)前進(jìn)程 */
switch_count = &prev->nivcsw;
/釋放全局內(nèi)核鎖,并開this_cpu 的中斷/
release_kernel_lock(prev);
need_resched_nonpreemptible:
__update_rq_clock(rq); //更新運行隊列的時鐘值
...
if (unlikely(!rq->nr_running))
idle_balance(cpu, rq);
// 對應(yīng)到CFS,則為 put_prev_task_fair
prev->sched_class->put_prev_task(rq, prev); //通知調(diào)度器類當(dāng)前運行進(jìn)程要被另一個進(jìn)程取代
/*pick_next_task以優(yōu)先級從高到底依次檢查每個調(diào)度類,從最高優(yōu)先級的調(diào)度類中選擇最高優(yōu)先級的進(jìn)程作為
下一個應(yīng)執(zhí)行進(jìn)程(若其余都睡眠,則只有當(dāng)前進(jìn)程可運行,就跳過下面了)*/
next = pick_next_task(rq, prev); //選擇需要進(jìn)行切換的task
...
}

在選擇下一個進(jìn)程前,先調(diào)用 put_prev_task (對應(yīng)到 CFS 為put_prev_task_fair),計算下當(dāng)前進(jìn)程的運行時間,根據(jù)當(dāng)前運行時間計算出虛擬運行時間,并累加到 vruntime,然后把當(dāng)前進(jìn)程根據(jù) vruntime 重新加入到就緒隊列紅黑樹中,等待下一次被調(diào)度。

put_prev_task_fair
--> put_prev_entity
--> update_curr
--> __update_curr //更新當(dāng)前調(diào)度實體的實際運行時間和虛擬運行時間
--> __enqueue_entity //把當(dāng)前調(diào)度實體重新加入到就緒隊列紅黑樹中等待下一次調(diào)度
/*
cfs_rq:可運行隊列對象。
curr:當(dāng)前進(jìn)程調(diào)度實體。
delta_exec:實際運行的時間。
__update_curr() 函數(shù)主要完成以下幾個工作:
更新進(jìn)程調(diào)度實體的總實際運行時間。
根據(jù)進(jìn)程調(diào)度實體的權(quán)重值,計算其使用的虛擬運行時間。
把計算虛擬運行時間的結(jié)果添加到進(jìn)程調(diào)度實體的 vruntime 字段。
*/
static inline void
__update_curr(struct cfs_rq *cfs_rq, struct sched_entity *curr,
unsigned long delta_exec)
{
unsigned long delta_exec_weighted;
u64 vruntime;
schedstat_set(curr->exec_max, max((u64)delta_exec, curr->exec_max));
//// 增加當(dāng)前進(jìn)程總實際運行的時間
curr->sum_exec_runtime += delta_exec;
// 更新cfs_rq的實際執(zhí)行時間cfs_rq->exec_clock
schedstat_add(cfs_rq, exec_clock, delta_exec);
//根據(jù)實際運行時間計算虛擬運行時間并累加到當(dāng)前進(jìn)程的虛擬運行時間
delta_exec_weighted = delta_exec;
//// 根據(jù)實際運行時間計算其使用的虛擬運行時間
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)程的虛擬運行時間
/*
? maintain cfs_rq->min_vruntime to be a monotonic increasing
? value tracking the leftmost vruntime in the tree.
*/
if (first_fair(cfs_rq)) {
vruntime = min_vruntime(curr->vruntime,
__pick_next_entity(cfs_rq)->vruntime);
} else
vruntime = curr->vruntime;
/*min_vruntime記錄CFS運行隊列上vruntime最小值,但是實際上min_vruntime只能單調(diào)遞增,
所以,如果當(dāng)前進(jìn)程vruntime比min_vruntime小,是不會更新min_vruntime的。
那么min_vruntime的作用的是什么呢?試想一下如果一個進(jìn)程睡眠了很長時間,則它的vruntime非常小,
一旦它被喚醒,將持續(xù)占用CPU,很容易引發(fā)進(jìn)程饑餓。
CFS調(diào)度器會根據(jù)min_vruntime設(shè)置一個合適的vruntime值給被喚醒的進(jìn)程,
既要保證它能優(yōu)先被調(diào)度,又要保證其他進(jìn)程也能得到合理調(diào)度。
*/
cfs_rq->min_vruntime =
max_vruntime(cfs_rq->min_vruntime, vruntime);
}

該函數(shù)的功能如下:

  • 更新進(jìn)程調(diào)度實體的總實際運行時間。
  • 根據(jù)進(jìn)程調(diào)度實體的權(quán)重值,計算其虛擬運行時間。
  • 把計算虛擬運行時間的結(jié)果添加到進(jìn)程調(diào)度實體的 vruntime 字段。

將調(diào)度實體加入紅黑樹中

static void __enqueue_entity(struct cfs_rq *cfs_rq, struct sched_entity *se)
{
struct rb_node **link = &cfs_rq->tasks_timeline.rb_node; // 紅黑樹根節(jié)點
struct rb_node *parent = NULL;
struct sched_entity *entry;
s64 key = entity_key(cfs_rq, se);// 當(dāng)前進(jìn)程調(diào)度實體的虛擬運行時間
int leftmost = 1;
/*
? Find the right place in the rbtree:
*/
while (*link) { // 把當(dāng)前調(diào)度實體插入到運行隊列的紅黑樹中
parent = *link;
entry = rb_entry(parent, struct sched_entity, run_node);
/*
? We dont care about collisions. Nodes with
? the same key stay together.
*/
if (key < entity_key(cfs_rq, entry)) { // 比較虛擬運行時間
link = &parent->rb_left;
} else {
link = &parent->rb_right;
leftmost = 0;
}
}
/*
? Maintain a cache of leftmost tree entries (it is frequently
? used):
*/
if (leftmost) //緩存最左葉子節(jié)點
cfs_rq->rb_leftmost = &se->run_node;
rb_link_node(&se->run_node, parent, link);
//把節(jié)點插入到紅黑樹中
rb_insert_color(&se->run_node, &cfs_rq->tasks_timeline);
}

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

  1. 獲取運行隊列紅黑樹的根節(jié)點。
  2. 獲取當(dāng)前進(jìn)程調(diào)度實體的虛擬運行時間。
  3. 把當(dāng)前進(jìn)程調(diào)度實體添加到紅黑樹中。
  4. 緩存紅黑樹最左端節(jié)點。
  5. 對紅黑樹進(jìn)行平衡操作。

獲取下一個合適的調(diào)度實體

static inline struct task_struct *
pick_next_task(struct rq *rq, struct task_struct *prev)
{
const struct sched_class *class;
struct task_struct *p;
/*
? Optimization: we know that if all tasks are in
? the fair class we can call that function directly:
*/
/選擇時并不是想象中的直接按照調(diào)度器的優(yōu)先級對所有調(diào)度器類進(jìn)行遍歷,而是假設(shè)下一個運行的進(jìn)程屬于 cfs 調(diào)度器類,畢竟,系統(tǒng)中絕大多數(shù)的進(jìn)程都是由 cfs 調(diào)度器進(jìn)行管理,這樣做可以從整體上提高執(zhí)行效率./
if (likely(rq->nr_running == rq->cfs.nr_running)) {
p = fair_sched_class.pick_next_task(rq);
if (likely(p))
return p;
}
class = sched_class_highest; // #define sched_class_highest (&rt_sched_class)
for ( ; ; ) {
p = class->pick_next_task(rq);
if (p)
return p;
/*
? Will never be NULL as the idle class always
? returns a non-NULL p:
*/
class = class->next;
}
}

在 pick_next_task 中會遍歷所有的調(diào)度類,然后從就緒隊列中選取一個最合適的調(diào)度實體進(jìn)行調(diào)度。

對于完全公平調(diào)度算法(CFS),會調(diào)用fair_sched_class.pick_next_task() 函數(shù),從 fair_sched_class 中可知,也即是調(diào)用 pick_next_task_fair。

static const struct sched_class fair_sched_class = {
.next = &idle_sched_class,
.enqueue_task = enqueue_task_fair,
.dequeue_task = dequeue_task_fair,
.yield_task = yield_task_fair,
.check_preempt_curr = check_preempt_wakeup,
.pick_next_task = pick_next_task_fair,
.put_prev_task = put_prev_task_fair,
#ifdef CONFIG_SMP
.load_balance = load_balance_fair,
.move_one_task = move_one_task_fair,
#endif
.set_curr_task = set_curr_task_fair,
.task_tick = task_tick_fair,
.task_new = task_new_fair,
};

pick_next_task_fair 調(diào)用過程如下:

pick_next_task_fair
---> pick_next_entity
---> __pick_next_entity 從就緒隊列中選取一個最合適的調(diào)度實體(虛擬時間最小的調(diào)度實體)
---> set_next_entity 把選中的進(jìn)程從紅黑樹中移除,并更新紅黑樹

從 schedule 調(diào)用可知,其在選擇下一個運行任務(wù)前,先計算當(dāng)前進(jìn)程(待切換出去的進(jìn)程)的時間運行時間,然后根據(jù)實際運行時間計算虛擬運行時間,在根據(jù)虛擬運行時間把當(dāng)前進(jìn)程加入到就緒隊列中的紅黑樹中等待下一次調(diào)度。

其次,從就緒隊列的紅黑樹中選擇虛擬運行時間最小的任務(wù)作為即將運行的任務(wù)。

進(jìn)程喚醒

進(jìn)程的默認(rèn)喚醒函數(shù)是 try_to_wake_up(), 該函數(shù)主要是調(diào)整睡眠進(jìn)程的 vruntime值, 以及把睡眠進(jìn)程加入紅黑樹中, 并判斷是否可以發(fā)生搶占。

調(diào)用關(guān)系如下:

try_to_wake_up
--> activate_task
--> enqueue_task
--> check_preempt_curr

時鐘周期中斷

周期性調(diào)度器是基于 scheduler_tick 函數(shù)實現(xiàn)。系統(tǒng)都是以tick(節(jié)拍)來執(zhí)行各種調(diào)度與統(tǒng)計,節(jié)拍可以通過 CONFIG_HZ 宏來控制。內(nèi)核會以 1/HZ ms 為周期來執(zhí)行周期性調(diào)度,這也是 CFS 實現(xiàn)的關(guān)鍵。CFS調(diào)度類會根據(jù)這個節(jié)拍來對所有進(jìn)程進(jìn)行記賬。每個 CPU 都會擁有自己的周期性調(diào)度器。周期性調(diào)度器可以把當(dāng)前進(jìn)程設(shè)置為 need resched 狀態(tài),等待合適的時機當(dāng)前的進(jìn)程就會被重新調(diào)度。

時鐘周期中斷函數(shù)的調(diào)用過程:

tick_periodic
--> update_process_times
--> scheduler_tick
--> task_tick_fair
-->entity_tick
--> update_curr
--> check_preempt_tick

在 CFS 中,scheduler_tick 會調(diào)用 具體實現(xiàn) task_tick_fair,在task_tick_fair 中會從當(dāng)前進(jìn)程開始獲取每個調(diào)度實體,對每個調(diào)度實體進(jìn)行調(diào)用 entity_tick,在 entity_tick 中更新調(diào)度實體的實際運行時間和虛擬運行時間,同時檢查是否需要重新調(diào)度。

static void
check_preempt_tick(struct cfs_rq *cfs_rq, struct sched_entity *curr)
{
unsigned long ideal_runtime, delta_exec;
// ideal_runtime 為一個調(diào)度周期內(nèi)理想的運行時間,也即是為 調(diào)度周期 * 進(jìn)程權(quán)重 / 所有進(jìn)程權(quán)重之和
ideal_runtime = sched_slice(cfs_rq, curr);
// sum_exec_runtime 指進(jìn)程總共執(zhí)行的實際時間;
// prev_sum_exec_runtime 指上次該進(jìn)程被調(diào)度時已經(jīng)占用的實際時間。
delta_exec = curr->sum_exec_runtime - curr->prev_sum_exec_runtime;
// delta_exec 這次調(diào)度占用實際時間,如果大于 ideal_runtime,則應(yīng)該被搶占了
if (delta_exec > ideal_runtime)
resched_task(rq_of(cfs_rq)->curr);
}
若當(dāng)前進(jìn)程運行的時間超過時間限制,

則把當(dāng)前進(jìn)程設(shè)置為 被搶占重新調(diào)度標(biāo)記 TIF_NEED_RESCHED,待一定時機后會根據(jù) TIF_NEED_RESCHED 標(biāo)記調(diào)用 schedule() 進(jìn)行調(diào)度切換。關(guān)于“一定的時機”,后續(xù)文章進(jìn)行分析。

責(zé)任編輯:華軒 來源: 今日頭條
相關(guān)推薦

2021-05-12 07:50:02

CFS調(diào)度器Linux

2021-05-17 18:28:36

Linux CFS負(fù)載均衡

2023-03-03 00:03:07

Linux進(jìn)程管理

2023-11-22 13:18:02

Linux調(diào)度

2023-05-08 12:03:14

Linux內(nèi)核進(jìn)程

2011-01-11 13:47:27

Linux管理進(jìn)程

2023-03-05 16:12:41

Linux進(jìn)程線程

2023-03-02 23:50:36

Linux進(jìn)程管理

2009-09-16 08:40:53

linux進(jìn)程調(diào)度linuxlinux操作系統(tǒng)

2025-06-03 07:15:00

Linux操作系統(tǒng)CFS 調(diào)度器

2021-04-15 05:51:25

Linux

2021-04-22 07:47:46

Linux進(jìn)程管理

2021-06-15 08:02:55

Linux 進(jìn)程管理

2021-12-15 15:03:51

Linux內(nèi)核調(diào)度

2020-06-04 08:36:55

Linux內(nèi)核線程

2010-03-08 14:40:27

Linux進(jìn)程調(diào)度

2023-11-03 08:22:09

Android系統(tǒng)算法

2022-04-27 10:14:43

進(jìn)程調(diào)度LinuxCPU

2023-03-21 15:26:02

Kubernetes容器開發(fā)

2012-05-14 14:09:53

Linux內(nèi)核調(diào)度系統(tǒng)
點贊
收藏

51CTO技術(shù)棧公眾號

主站蜘蛛池模板: 97avcc| 免费视频中文字幕 | 亚洲一区二区av | 婷婷久久综合 | 人成在线视频 | 日韩一区二区在线播放 | 亚洲欧美男人天堂 | 国产欧美精品一区二区 | 精品区 | 免费毛片网 | 国产精品视频播放 | 天天操天天干天天透 | 不卡av电影在线播放 | 天天天操 | 国产精品免费看 | 国产91精品久久久久久久网曝门 | 97av视频| 97精品国产97久久久久久免费 | 日韩美女一区二区三区在线观看 | 91久久国产综合久久91精品网站 | 黄篇网址| av在线免费看网址 | 黄网站免费入口 | 日日噜噜噜夜夜爽爽狠狠视频, | 亚洲精品91 | 亚洲福利 | 婷婷激情综合 | 成人一区二区三区 | 成人午夜电影在线观看 | 99视频免费 | 久久成人国产精品 | 91精品国产综合久久久久久 | 在线视频一区二区三区 | 国产日韩欧美一区 | 国产欧美一区二区三区久久 | 日韩高清黄色 | 在线区| 亚洲码欧美码一区二区三区 | 在线不卡| 日韩a | 国产成人久久精品一区二区三区 |