基於 Linux-4.19.153
一、相關結構成員描述
1. struct root_domain
實時調度器需要幾個全局的或者說系統范圍的資源來作出調度決定,以及 CPU 數量的增加而出現的可伸縮性瓶頸(由於鎖保護的這些資源的競爭),Root Domain 引入的目的就是為了減少這樣的競爭以改善可伸縮性。
cpuset 提供了一個把 CPUs 分成子集被一個進程或者或一組進程使用的機制。幾個 cpuset 可以重疊。如果沒有其他的 cpuset 包含重疊的 CPU,這個 cpuset 被稱為“互斥的(exclusive)”。每個互斥的 cpuset 定義了一個與其他 cpuset 或 CPU 分離的孤島域(isolated domain,也叫作 root domain)。但是嵌入式設備平台根組cpus必須是0-7,因此不會形成根組。與每個 root domian 有關的信息存在 struct root_domain 結構(對象)中:
//kernel/sched/sched.h /* * 我們添加了 root-domain 的概念,用於定義 per-domain 的變量。 * 每個互斥的 cpuset 本質上通過將成員 CPU 與其他任何 cpuset 完 * 全划分開來定義一個島域。每當創建一個新的獨占 cpuset 時,我們也 * 會創建並附加一個新的 root-domain 對象。 */ struct root_domain { //root domain 的引用計數,當 rd 被運行隊列引用時加1,反之減1 atomic_t refcount; //實時任務過載的(rt overload)的CPU的數目 atomic_t rto_count; struct rcu_head rcu; //屬於該 rd 的CPU掩碼 cpumask_var_t span; cpumask_var_t online; /* * Indicate pullable load on at least one CPU, e.g: * - More than one runnable task * - Running task is misfit */ //表明該 rd 有任一CPU有多於一個的可運行任務 int overload; /* Indicate one or more cpus over-utilized (tipping point) */ int overutilized; /* * The bit corresponding to a CPU gets set here if such CPU has more * than one runnable -deadline task (as it is below for RT tasks). */ cpumask_var_t dlo_mask; atomic_t dlo_count; struct dl_bw dl_bw; struct cpudl cpudl; /* * Indicate whether a root_domain's dl_bw has been checked or * updated. It's monotonously increasing value. * * Also, some corner cases, like 'wrap around' is dangerous, but given * that u64 is 'big enough'. So that shouldn't be a concern. */ u64 visit_gen; #ifdef HAVE_RT_PUSH_IPI /* * For IPI pull requests, loop across the rto_mask. */ struct irq_work rto_push_work; raw_spinlock_t rto_lock; /* These are only updated and read within rto_lock */ int rto_loop; int rto_cpu; /* These atomics are updated outside of a lock */ atomic_t rto_loop_next; atomic_t rto_loop_start; #endif /* * The "RT overload" flag: it gets set if a CPU has more than * one runnable RT task. */ //某CPU有多於一個的可運行實時任務,對應的位被設置 cpumask_var_t rto_mask; //包含在 rd 中的CPU優先級管理結構成員 struct cpupri cpupri; unsigned long max_cpu_capacity; /* * NULL-terminated list of performance domains intersecting with the * CPUs of the rd. Protected by RCU. */ struct perf_domain __rcu *pd; };
這些 rd 被用於減小 per-domain 變量的全局變量的范圍。無論何時一個互斥 cpuset 被創建,一個新 root domain 對象也會被創建,信息來自 CPU 成員。缺省情況下,一個單獨的高層次的 rd 被創建,並把所有 CPU 作為成員。所有的實時調度決定只在一個 rd 的范圍內作出決定。
2. struct task_struct
struct task_struct { ... struct sched_rt_entity rt; #ifdef CONFIG_SMP /*符合條件的RT認為通過此成員掛入 rq->rt.pushable_tasks 鏈表,表示是可push的任務*/ struct plist_node pushable_tasks; struct rb_node pushable_dl_tasks; #endif ... };
二、CPU優先級管理
1. CPU優先級管理(CPU Priority Management)跟蹤系統中每個 CPU 的優先級,為了讓進程遷移的決定更有效率。CPU優先級有 102 個,下面是cpupri與prio的對應關系:
//kernel/sched/cpupri.h cpupri prio ---------------------------- CPUPRI_INVALID (-1) -1 CPUPRI_IDLE(0) MAX_PRIO(140) CPUPRI_NORMAL(1) MAX_RT_PRIO ~ MAX_PRIO-1 (100~139) 2~101 99~0
注意,運行idle任務的CPU的cpupri=0,運行CFS任務的CPU的cpupri=1。
static int convert_prio(int prio) { int cpupri; if (prio == CPUPRI_INVALID) /* -1 */ cpupri = CPUPRI_INVALID; /* -1 */ else if (prio == MAX_PRIO) /* 140 */ cpupri = CPUPRI_IDLE; /* 0 */ else if (prio >= MAX_RT_PRIO) /* 100 */ cpupri = CPUPRI_NORMAL; /* 1 */ else cpupri = MAX_RT_PRIO - prio + 1; /* 100 - prio + 1 */ return cpupri; }
傳參prio=99返回0,傳參prio=100返回100.
cpupri 數值越大表示優先級越高(用了減法)。處於 CPUPRI_INVALID 狀態的 CPU 沒有資格參與 task routing。cpupri 屬於 root domain的,每個互斥的 cpuset 由一個含有 cpupri 數據的 root momain 組成。系統從兩個維度的位映射來維護這些 CPU 狀態:
(1) CPU 的優先級,由任務優先級映射而來。
(2) 在某個優先級上的 CPU。
2. 相關數據結構
//kernel/sched/cpupri.h struct cpupri_vec { //在這個優先級上的 CPU 的數量 atomic_t count; //在這個優先級上的 CPU 位碼 cpumask_var_t mask; }; //體現兩個維度 struct cpupri { //持有關於一個 cpuset 在 某個特定的優先級上的 所有 CPU 的信息 struct cpupri_vec pri_to_cpu[CPUPRI_NR_PRIORITIES]; //指示一個 CPU 的優先級,指向一個數組,每個CPU一個成員,主要用於記錄當前cpu的cpupri值,便於更新修改。 int *cpu_to_pri; };
通過 cpupri_find()/cpupri_find_fitness() 和 cpupri_set() 來查找和設置 CPU 優先級是實時負載均衡快速找到要遷移的任務的關鍵。
3. cpupri_set() 函數
(1) 函數分析
/** * cpupri_set - update the CPU priority setting * @cp: The cpupri context * @cpu: The target CPU * @newpri: The priority (INVALID,NORMAL,RT1-RT99,HIGHER) to assign to this CPU * * Note: Assumes cpu_rq(cpu)->lock is locked * * Returns: (void) */ void cpupri_set(struct cpupri *cp, int cpu, int newpri) { //獲取當前cpu的cpupri int *currpri = &cp->cpu_to_pri[cpu]; int oldpri = *currpri; int do_mb = 0; //將p->prio轉換為cpuprio newpri = convert_prio(newpri); BUG_ON(newpri >= CPUPRI_NR_PRIORITIES); if (newpri == oldpri) return; //若是cpupri變化了,就更新此cpupri對應的信息 if (likely(newpri != CPUPRI_INVALID)) { struct cpupri_vec *vec = &cp->pri_to_cpu[newpri]; cpumask_set_cpu(cpu, vec->mask); smp_mb__before_atomic(); atomic_inc(&(vec)->count); do_mb = 1; } //然后刪除舊信息 if (likely(oldpri != CPUPRI_INVALID)) { struct cpupri_vec *vec = &cp->pri_to_cpu[oldpri]; if (do_mb) smp_mb__after_atomic(); atomic_dec(&(vec)->count); smp_mb__after_atomic(); cpumask_clear_cpu(cpu, vec->mask); } *currpri = newpri; }
舉個例子,比如CPU2上的任務從prio=120的CFS任務切換為了prio=97的RT任務,此時先讀取cp->cpu_to_pri[2]當前的cpupri的值,由於CPU2上先前運行的是CFS任務,因為讀取的值是1。然后計算新任務運行下new-cpupri為101-97=4,於是將CPU2的掩碼設置進cp->pri_to_cpu[4] 的 vec->mask 中,並將 vec->count 計數加1,表示處於cpupri=4優先級的CPU又增加了一個CPU2。然后將CPU2從cp->pri_to_cpu[1] 的 vec->mask 中刪除,並將 vec->count 計數減1,表示cpupri=1優先級的CPU又減少一個CPU2。
(2) cpupri_set()的調用路徑:
rt_sched_class.rq_offline 回調 rq_offline_rt //傳參(cpupri, rq->cpu, CPUPRI_INVALID) rt_sched_class.rq_online 回調 rq_online_rt enqueue_rt_entity dequeue_rt_entity dequeue_rt_stack __dequeue_rt_entity dec_rt_tasks dec_rt_prio dec_rt_prio_smp enqueue_rt_entity dequeue_rt_entity __enqueue_rt_entity inc_rt_tasks inc_rt_prio inc_rt_prio_smp cpupri_set
可見主要是在enqueue/dequeue RT任務的路徑中調用,應該是當一個CPU其上任務切換的時候調用,由於CFS任務的cpupri都是1,所以只有涉及RT的任務切換才會調用,調用函數都在rt.c中。
三、PUSH任務遷移
1. PUSH任務的基本思想
根據cpupri搜索出一組cpu優先級最低的cpu作為候選cpu,然后從候選cpu中選出一個cpu作為目標cpu,然后push本rq上queue者的優先級最高的並且可push的RT任務過去。持續循環執行,直到沒有可push的任務為止。
源cpu就是 push_rt_task(struct rq *rq) 參數中的rq所屬的cpu,從這個cpu的rq上往外push RT任務。
2. PUSH任務的時機
push_rt_task()函數會在以下時間點被調用:
(1) rt_mutex鎖優先級改變、__sched_setscheduler()導致調度類改變、__schedule()任務切換
rt_mutex_setprio //core.c __sched_setscheduler //core.c check_class_changed //core.c 在調度類改變的時候調用,會先調用上一個調度類的switched_from,再調用下一個調度類的switched_to rt_sched_class.switched_to //rt.c 回調 switched_to_rt //rt.c 若p在rq上且不是rq上正在運行的任務,且p運行在多個cpu上運行且rq->rt.overload了,才調用 __schedule //core.c pick_next_task //core.c 選擇下一個任務 rt_sched_class.pick_next_task //rt.c 回調 pick_next_task_rt //rt.c 無條件調用 rt_queue_push_tasks //rt.c 判斷參數rq上有可push的任務,即 rq->rt.pushable_tasks 鏈表不為空調用 queue_balance_callback(rq, &per_cpu(rt_push_head, rq->cpu), push_rt_tasks); //rt.c 頭插法掛入 rq->balance_callback 鏈表
回調時機:
__sched_setscheduler rt_mutex_setprio schedule_tail //core.c 沒有找到調用的地方 __schedule //core.c 任務切換函數最后調用 balance_callback //core.c __balance_callback //core.c 依次回調 rq->balance_callback 鏈表上的所有函數,持rq->lock關中斷調用的
(2) 有cpu執行拉RT任務的時候,告訴其它CPU推出去一些任務
pull_rt_task(rq) //rt.c 使能 RT_PUSH_IPI 時才會執行,在拉任務時觸發push. rq為當前隊列,告訴其它cpu往當前cpu上push一些任務 tell_cpu_to_push //rt.c 有rto的cpu才queue irq_work_queue_on(&rq->rd->rto_push_work, cpu); rto_push_irq_work_func //發現有可push的任務,持有rq->lock spin鎖調用 push_rt_tasks(rq) irq_work_queue_on(&rd->rto_push_work, cpu); //自己queue自己,只要有rto的cpu就不斷queue自己,構成一個"內核線程"一直運行,直到沒有rto的cpu. init_rootdomain init_irq_work(&rd->rto_push_work, rto_push_irq_work_func);
(3) 若喚醒的是RT任務又認為不能及時得到調度執行,就將其從喚醒的rq上push走
ttwu_do_wakeup //core.c wake_up_new_task //core.c rt_sched_class.task_woken //調度類回調 task_woken_rt //rt.c push_rt_tasks(rq)
task_woken_rt() 中調用 push_rt_tasks() 的條件比較苛刻,如下。表示為喚醒的任務p不是rq上正在running的任務,且當前rq也沒有設置resched標志位(不會馬上重新調度),且p也允許在其它CPU上運行,且rq當前正在運行的任務是DL或RT任務,且rq的當前任務只能在當前CPU運行或優先級比p更高。才會調用push_rt_tasks()將喚醒的RT任務push走。
static void task_woken_rt(struct rq *rq, struct task_struct *p) { if (!task_running(rq, p) && !test_tsk_need_resched(rq->curr) && p->nr_cpus_allowed > 1 && (dl_task(rq->curr) || rt_task(rq->curr)) && (rq->curr->nr_cpus_allowed < 2 || rq->curr->prio <= p->prio)) push_rt_tasks(rq); }
3. PUSH任務的結束條件
見 push_rt_tasks(rq),從rq上一直往外push任務,直到沒有任務可push了才停止。
4. PUSH任務邏輯實現——push_rt_tasks()
(1) push_rt_tasks()
//rt.c 作用:從參數rq上推一些任務到其它rq上 static void push_rt_tasks(struct rq *rq) { /* push_rt_task will return true if it moved an RT */ while (push_rt_task(rq)) //如果有任務可PUSH將一直執行下去 ; }
(2) push_rt_task()
/* * If the current CPU has more than one RT task, see if the non * running task can migrate over to a CPU that is running a task * of lesser priority. */ //push出去任務了返回1,否則返回0 static int push_rt_task(struct rq *rq) { struct task_struct *next_task; struct rq *lowest_rq; int ret = 0; /* update_rt_migration()中設置,多余1個RT任務且有可遷移的RT任務設置為1 */ if (!rq->rt.overloaded) return 0; //從rq->rt.pushable_tasks鏈表頭取出可push的task,最先取出的是優先級最高的RT task. next_task = pick_next_pushable_task(rq); if (!next_task) return 0; retry: //取出來的應該是Runnable的,而不能是正在running的任務 if (unlikely(next_task == rq->curr)) { WARN_ON(1); return 0; } /* * next_task 是可能比當前的優先級更高的,如果是這種情況, * 只需觸發一次重新調度。 */ if (unlikely(next_task->prio < rq->curr->prio)) { resched_curr(rq); return 0; } /* We might release rq lock */ get_task_struct(next_task); /* find_lock_lowest_rq locks the rq if found */ //根據cpupri找到cpu優先級最低cpu作為任務要push到的目的cpu lowest_rq = find_lock_lowest_rq(next_task, rq); //1.如果 lowest_rq 沒有找到 if (!lowest_rq) { struct task_struct *task; /* * find_lock_lowest_rq 釋放 rq->lock,因此 next_task 可能已被遷移走了。 * 需要確保任務仍然還在這個rq中,並且仍然是下一個有資格被推送的任務。因此 * 需要再重新執行一次這個函數。 */ task = pick_next_pushable_task(rq); //(1)重新選出的待push task還是原來的task if (task == next_task) { /* * 該任務尚未遷移,仍然是下一個符合條件的任務,但我們未能找到將其推送到的目 * 標運行隊列。 在這種情況下不要重試,因為其他 CPU 會在准備好時從我們這里拉取。 */ goto out; } //(2)重新選出的待push task不是原來的task if (!task) /* No more tasks, just exit */ goto out; /* Something has shifted, try again. */ //再次選出的是不同的task了,重新試一次 put_task_struct(next_task); next_task = task; goto retry; } //2.如果 lowest_rq 沒有找到了,就將任務從rq上摘下放到lowest_rq上 deactivate_task(rq, next_task, 0); set_task_cpu(next_task, lowest_rq->cpu); activate_task(lowest_rq, next_task, 0); ret = 1; //對目標lowest_rq觸發一次重新調度 resched_curr(lowest_rq); //CONFIG_LOCKDEP相關,若是沒有使能只是釋放lowest_rq->lock double_unlock_balance(rq, lowest_rq); out: put_task_struct(next_task); return ret; }
(3) pick_next_pushable_task()
選擇出rq上queue着狀態的優先級最高的RT任務,優先push優先級最高的RT任務。
static struct task_struct *pick_next_pushable_task(struct rq *rq) //rt.c { struct task_struct *p; //rq->rt.pushable_tasks 鏈表不為空表示有可push的任務 if (!has_pushable_tasks(rq)) return NULL; //first也就使鏈表上優先級最高的那個RT任務,也就是push rq上queue着的最高優先級的RT任務 p = plist_first_entry(&rq->rt.pushable_tasks, struct task_struct, pushable_tasks); BUG_ON(rq->cpu != task_cpu(p)); /*校驗p是掛載在此cpu rq上的*/ BUG_ON(task_current(rq, p)); /*return rq->curr == p;校驗p不是正在運行的任務*/ BUG_ON(p->nr_cpus_allowed <= 1);/*校驗p是允許在多個cpu上運行的,否則不能push*/ BUG_ON(!task_on_rq_queued(p)); /*return p->on_rq == TASK_ON_RQ_QUEUED; 校驗p是queue在rq上的*/ BUG_ON(!rt_task(p)); /*return prio < 100 校驗p必須是RT任務*/ return p; }
(4) find_lock_lowest_rq()
根據cpupri找出cpu優先級最低的cpu作為push任務的目標cpu.
/* Will lock the rq it finds */ static struct rq *find_lock_lowest_rq(struct task_struct *task, struct rq *rq) { struct rq *lowest_rq = NULL; int tries; int cpu; //最大try 3次 for (tries = 0; tries < RT_MAX_TRIES; tries++) { //選擇一個cpu優先級最低的cpu(比task運行的cpu的優先級低,否則返回-1) cpu = find_lowest_rq(task); //沒找到的話cpu==-1可能成立,或后面的恆不會成立 if ((cpu == -1) || (cpu == rq->cpu)) break; //找到了task要被push到的目標cpu的rq lowest_rq = cpu_rq(cpu); //這個if判斷有可能成立,因為沒有持lowest_rq的鎖,它上面可能又queue了高優先級的任務 if (lowest_rq->rt.highest_prio.curr <= task->prio) { /* * Target rq has tasks of equal or higher priority, * retrying does not release any lock and is unlikely * to yield a different result. * 翻譯: * 目標 rq 具有相同或更高優先級的任務,重試不會釋放任何鎖 * 並且不太可能產生不同的結果。因此放棄retry,返回沒找到lowest_rq。 */ lowest_rq = NULL; break; } /* if the prio of this runqueue changed, try again */ //? ############### //下面是做一些校驗,主要是判斷環境有沒有變化來判斷是否應該將lowest_rq置為NULL if (double_lock_balance(rq, lowest_rq)) { /* * We had to unlock the run queue. In the mean time, task could have * migrated already or had its affinity changed. * Also make sure that it wasn't scheduled on its rq. */ if (unlikely(task_rq(task) != rq || !cpumask_test_cpu(lowest_rq->cpu, &task->cpus_allowed) || task_running(rq, task) || !rt_task(task) || !task_on_rq_queued(task))) { double_unlock_balance(rq, lowest_rq); lowest_rq = NULL; break; } } /* If this rq is still suitable use it. */ //大概率是成功的,滿足就不retry了,直接返回找到的lowest_rq if (lowest_rq->rt.highest_prio.curr > task->prio) break; /* try again */ double_unlock_balance(rq, lowest_rq); lowest_rq = NULL; } return lowest_rq; }
5. 何時往 rq->rt.pushable_tasks 鏈表上添加可push的任務
在 enqueue_task_rt 中,只有當p不是正在執行的任務且可以在多個CPU上運行時才會掛入 p->pushable_tasks 鏈表,p->prio越小優先級高的越掛在靠前的位置。
static void enqueue_pushable_task(struct rq *rq, struct task_struct *p) { plist_del(&p->pushable_tasks, &rq->rt.pushable_tasks); plist_node_init(&p->pushable_tasks, p->prio); //p->prio值越小,插入的位置越靠前 plist_add(&p->pushable_tasks, &rq->rt.pushable_tasks); /* Update the highest prio pushable task */ if (p->prio < rq->rt.highest_prio.next) rq->rt.highest_prio.next = p->prio; } static void dequeue_pushable_task(struct rq *rq, struct task_struct *p) { plist_del(&p->pushable_tasks, &rq->rt.pushable_tasks); /* Update the new highest prio pushable task */ if (has_pushable_tasks(rq)) { p = plist_first_entry(&rq->rt.pushable_tasks, struct task_struct, pushable_tasks); rq->rt.highest_prio.next = p->prio; } else rq->rt.highest_prio.next = MAX_RT_PRIO; }
調用路徑:
rt_sched_class.enqueue_task enqueue_task_rt //rt.c 在函數最后執行,只有當p滿足不是正在執行的任務且滿足可以在多於1個CPU上運行才調用 enqueue_pushable_task(rq, p); rt_sched_class.dequeue_task dequeue_task_rt //無條件執行 dequeue_pushable_task(rq, p);
6. rt_rq->overloaded 標志的設置
在enqueue/dequeue RT任務時,判斷rt_rq上有可遷移的實時任務時更新。
static void update_rt_migration(struct rt_rq *rt_rq) { if (rt_rq->rt_nr_migratory && rt_rq->rt_nr_total > 1) { if (!rt_rq->overloaded) { //rd->rto_count++ 和設置更新rd->rto_mask cpu掩碼 rt_set_overload(rq_of_rt_rq(rt_rq)); rt_rq->overloaded = 1; } } else if (rt_rq->overloaded) { //rd->rto_count-- 和清除更新rd->rto_mask cpu掩碼 rt_clear_overload(rq_of_rt_rq(rt_rq)); rt_rq->overloaded = 0; } }
調用路徑:
__enqueue_rt_entity inc_rt_tasks inc_rt_migration //無條件調用,無條件 rt_rq->rt_nr_total++,若p允許在多於一個cpu上運行才執行 rt_rq->rt_nr_migratory++; __dequeue_rt_entity dec_rt_tasks dec_rt_migration //無條件調用,無條件執行 rt_rq->rt_nr_total--,若p允許在多於一個cpu上運行才執行 rt_rq->rt_nr_migratory--; update_rt_migration
四、PULL任務遷移
1. PULL任務的基本思想
當選下一個RT任務時,若發現rq上的最高優先級的RT任務的優先級比prev還低,就認為需要pull rt任務過來。此時又分兩種情況:
(1) 不使能 RT_PUSH_IPI
從runnable RT最高優先級比自己高的cpu上拉rt任務過來,對每個cpu都執行這樣的操作,然后觸發本cpu搶占調度。
(2) 使能 RT_PUSH_IPI
采用逐個向每個rto cpu上queue irq_work 的方式來觸發rto cpu進行push task,然后走push task的處理邏輯,以push task的方式代替pull task.
2. PULL任務的時機
rt_mutex_setprio //core.c __sched_setscheduler //core.c check_class_changed //core.c rt_sched_class.switched_from switched_from_rt //若p是runnable的rt任務且rq上已經沒有rt任務在運行了調用 check_class_changed rt_sched_class.prio_changed prio_changed_rt //若p是當前正在執行的任務且其優先級降低了調用 rt_queue_pull_task queue_balance_callback(rq, &per_cpu(rt_pull_head, rq->cpu), pull_rt_task); rt_sched_class.pick_next_task pick_next_task_rt //判斷需要pull時才pull pull_rt_task
執行時機一,在要選擇下一個RT任務時。need_pull_rt_task用來判斷是否需要pull任務,只要當前rq上queue的RT線程的最高優先級還比prev任務的優先級低,就認為需要pull任務到rq中來。
static inline bool need_pull_rt_task(struct rq *rq, struct task_struct *prev) { /* Try to pull RT tasks here if we lower this rq's prio */ return rq->rt.highest_prio.curr > prev->prio; }
調用時機二,queue_balance_callback,同 push_rt_task
3. PULL任務邏輯實現——pull_rt_task()
3.1 先看沒有使能 RT_PUSH_IPI sched feat 的情況
(1) pull_rt_task 函數
static void pull_rt_task(struct rq *this_rq) { int this_cpu = this_rq->cpu, cpu; bool resched = false; struct task_struct *p; struct rq *src_rq; //return rq->rd->rto_count, 只要一個cpu上有可遷移的任務就加1 int rt_overload_count = rt_overloaded(this_rq); if (likely(!rt_overload_count)) return; /* * Match the barrier from rt_set_overloaded; this guarantees that if we * see overloaded we must also see the rto_mask bit. */ smp_rmb(); /* If we are the only overloaded CPU do nothing */ //目前只有本cpu一個是rt_overload,那就沒有必要去拉rt任務過來了 if (rt_overload_count == 1 && cpumask_test_cpu(this_rq->cpu, this_rq->rd->rto_mask)) return; #ifdef HAVE_RT_PUSH_IPI //若是使能了這個feature,就會通知其它CPU推任務到本rq,而不會執行拉任務的動作了 if (sched_feat(RT_PUSH_IPI)) { tell_cpu_to_push(this_rq); return; } #endif //對於每一個rt超載的cpu都執行 for_each_cpu(cpu, this_rq->rd->rto_mask) { //跳過本cpu,肯定不能從本cpu上往本cpu上拉任務 if (this_cpu == cpu) continue; src_rq = cpu_rq(cpu); /* * Don't bother taking the src_rq->lock if the next highest * task is known to be lower-priority than our current task. * This may look racy, but if this value is about to go * logically higher, the src_rq will push this task away. * And if its going logically lower, we do not care * 翻譯: * 如果已知下一個最高優先級的任務的優先級低於當前任務的優先級,不需要 * 持有 src_rq->lock。 這可能看起來存在競爭,但如果這個值在邏輯上即將 * 變得更高,src_rq 將把這個任務推開。 如果它在邏輯上降低,我們不在乎 */ //只選最高優先級比自己的高的作為備選src_rq (enqueue時會更新) if (src_rq->rt.highest_prio.next >= this_rq->rt.highest_prio.curr) continue; /* * We can potentially drop this_rq's lock in * double_lock_balance, and another CPU could alter this_rq * 翻譯: * 在 double_lock_balance 中可能會釋放 this_rq 的鎖,而另一個 * CPU 可能會更改 this_rq */ double_lock_balance(this_rq, src_rq); /* * We can pull only a task, which is pushable on its rq, and no others. */ //從src_rq上選出一個優先級最高的runnable的RT任務 p = pick_highest_pushable_task(src_rq, this_cpu); /* * Do we have an RT task that preempts the to-be-scheduled task? */ if (p && (p->prio < this_rq->rt.highest_prio.curr)) { WARN_ON(p == src_rq->curr); //選出的RT任務不能是src_rq上正在執行的任務 WARN_ON(!task_on_rq_queued(p)); //選出的RT任務不能是非runnable的任務 /* * There's a chance that p is higher in priority than what's currently * running on its CPU. This is just that p is wakeing up and hasn't had * a chance to schedule. We only pull p if it is lower in priority than * the current task on the run queue. */ /* 若選從src_rq上選出的p比src_rq上正在執行的任務優先級還高,就不跳過它, * 因為它可以搶占低優先級的任務從而很快被調度執行。 */ if (p->prio < src_rq->curr->prio) goto skip; resched = true; //從源src_rq上摘下來放到this_rq deactivate_task(src_rq, p, 0); set_task_cpu(p, this_cpu); activate_task(this_rq, p, 0); /* * We continue with the search, just in * case there's an even higher prio task * in another runqueue. (low likelihood * but possible) */ } skip: double_unlock_balance(this_rq, src_rq); } //若pull過來了任務,就觸發一次搶占調度 if (resched) resched_curr(this_rq); }
(2) pick_highest_pushable_task 函數
/* * Return the highest pushable rq's task, which is suitable to be executed * on the CPU, NULL otherwise */ //傳參: rq: 源rq, cpu: 目的地cpu static struct task_struct *pick_highest_pushable_task(struct rq *rq, int cpu) { struct plist_head *head = &rq->rt.pushable_tasks; struct task_struct *p; //判斷 rq->rt.pushable_tasks 為空表示rq上沒有可push的任務 if (!has_pushable_tasks(rq)) return NULL; /* * 按優先級由高到低遍歷src_rq上的每一個可push的任務,若其非 * running且親和性允許運行在目標cpu上就返回第一個滿足條件的任務p */ plist_for_each_entry(p, head, pushable_tasks) { if (pick_rt_task(rq, p, cpu)) return p; } return NULL; } static int pick_rt_task(struct rq *rq, struct task_struct *p, int cpu) { if (!task_running(rq, p) && cpumask_test_cpu(cpu, &p->cpus_allowed)) return 1; return 0; }
3.2 使能 RT_PUSH_IPI sched feat 的情況
pull_rt_task 邏輯委托給 tell_cpu_to_push(this_rq),讓其它cpu往this_rq上push任務來代替拉任務,以減少拉任務帶來的鎖競爭。
(1) tell_cpu_to_push()函數:
static void tell_cpu_to_push(struct rq *rq) { int cpu = -1; /* Keep the loop going if the IPI is currently active */ //唯一增加其值的地方,沒有降低其值的地方 atomic_inc(&rq->rd->rto_loop_next); /* Only one CPU can initiate a loop at a time */ if (!rto_start_trylock(&rq->rd->rto_loop_start)) return; raw_spin_lock(&rq->rd->rto_lock); /* * The rto_cpu is updated under the lock, if it has a valid CPU * then the IPI is still running and will continue due to the * update to loop_next, and nothing needs to be done here. * Otherwise it is finishing up and an ipi needs to be sent. */ //初始化為-1,只在 rto_next_cpu 中賦值為cpu id或-1 if (rq->rd->rto_cpu < 0) //返回一個rt overload 的cpu cpu = rto_next_cpu(rq->rd); raw_spin_unlock(&rq->rd->rto_lock); //將 rd->rto_loop_start 設置為0 rto_start_unlock(&rq->rd->rto_loop_start); if (cpu >= 0) { /* Make sure the rd does not get freed while pushing。rd->refcount++;*/ sched_get_rd(rq->rd); //向參數cpu指定的CPU上queue一個irq_work irq_work_queue_on(&rq->rd->rto_push_work, cpu); rto_push_irq_work_func } }
(2) rto_next_cpu 函數:
static int rto_next_cpu(struct root_domain *rd) { int next; int cpu; /* * When starting the IPI RT pushing, the rto_cpu is set to -1, * rt_next_cpu() will simply return the first CPU found in * the rto_mask. * * If rto_next_cpu() is called with rto_cpu is a valid CPU, it * will return the next CPU found in the rto_mask. * * If there are no more CPUs left in the rto_mask, then a check is made * against rto_loop and rto_loop_next. rto_loop is only updated with * the rto_lock held, but any CPU may increment the rto_loop_next * without any locking. */ for (;;) { /* When rto_cpu is -1 this acts like cpumask_first() */ cpu = cpumask_next(rd->rto_cpu, rd->rto_mask); rd->rto_cpu = cpu; //正常情況下從這里就返回了 if (cpu < nr_cpu_ids) return cpu; //rto_mask中沒有cpu掩碼了,賦值為-1 rd->rto_cpu = -1; /* * ACQUIRE ensures we see the @rto_mask changes * made prior to the @next value observed. * * Matches WMB in rt_set_overload(). */ next = atomic_read_acquire(&rd->rto_loop_next); if (rd->rto_loop == next) break; rd->rto_loop = next; } return -1; }
(3) rto_push_irq_work_func 函數.
注意備注,是在硬中斷上下文調用的。
/* Called from hardirq context */ void rto_push_irq_work_func(struct irq_work *work) { struct root_domain *rd =container_of(work, struct root_domain, rto_push_work); struct rq *rq; int cpu; rq = this_rq(); /* * We do not need to grab the lock to check for has_pushable_tasks. * When it gets updated, a check is made if a push is possible. */ if (has_pushable_tasks(rq)) { raw_spin_lock(&rq->lock); //觸發push任務的流程 push_rt_tasks(rq); raw_spin_unlock(&rq->lock); } raw_spin_lock(&rd->rto_lock); /* Pass the IPI to the next rt overloaded queue */ //取出下一個rto cpu cpu = rto_next_cpu(rd); raw_spin_unlock(&rd->rto_lock); if (cpu < 0) { sched_put_rd(rd); return; } /* Try the next RT overloaded CPU */ /* * 自己queue自己,但是queue的cpu卻是下一個rto cpu了,直到所有 * 的rto cpu都執行了push task的操作才停止。 */ irq_work_queue_on(&rd->rto_push_work, cpu); //rto_push_irq_work_func }
五、總結
1. RT負載均衡分為PUSH和PULL兩種方式
(1) PUSH任務的基本思想
根據cpupri搜索出一組cpu優先級最低的cpu作為候選cpu,然后push本rq上queue者的優先級最高的並且可push的RT任務過去。持續循環執行,直到沒有可push的任務為止。
源cpu就是 push_rt_task(struct rq *rq) 參數中的rq所屬的cpu,從這個cpu的rq上往外push RT任務。
(2) PUSH任務的時機
push_rt_task()函數會在以下時間點被調用:
a. rt_mutex鎖優先級改變、__sched_setscheduler()導致調度類改變、__schedule()任務切換
b. 有cpu執行拉RT任務的時候,告訴其它CPU推出去一些任務
c. 若喚醒的是RT任務又認為不能及時得到調度執行,就將其從喚醒的rq上push走
(3) PUSH任務的基本思想
當選下一個RT任務時,若發現rq上的最高優先級的RT任務的優先級比prev還低,就認為需要pull rt任務過來。此時又分兩種情況:
a. 不使能 RT_PUSH_IPI: 從runnable RT最高優先級比自己高的cpu上拉rt任務過來,對每個cpu都執行這樣的操作,然后觸發本cpu搶占調度。
b. 使能 RT_PUSH_IPI(默認): 采用逐個向每個rto cpu上queue irq_work 的方式來觸發rto cpu進行push task,然后走push task的處理邏輯,以push task的方式代替pull task. #######
(4) PUSH任務的時機
a. 在要選擇下一個RT任務時。need_pull_rt_task 用來判斷是否需要pull任務,只要當前rq上queue的RT線程的最高優先級還比prev任務的優先級低,就認為需要pull任務到rq中來。
b. queue_balance_callback,同 push_rt_task。
