前言
負載均衡不僅僅是簡單地將任務從繁忙CPU遷移到空閒CPU,而是一個涉及CPU拓撲感知、緩存友好性、實時性響應和能效管理的複雜系統工程。Linux 2.6.10 通過精巧的調度域(Sched Domain)層次結構、智能的任務選擇算法和高效的遷移機制,構建了一個既保證性能又兼顧穩定性的負載均衡體系。
本文將從 sched_exec 函數調用入口開始,沿着任務遷移的完整路徑,逐層分析每個關鍵函數的實現細節、設計哲學和性能考量。通過對 find_idlest_cpu 的負載評估算法、move_tasks 的任務選擇策略、can_migrate_task 的遷移條件判斷以及 pull_task 的遷移操作等核心組件的深入解讀,揭示Linux調度器在負載均衡方面的精妙設計。
特別值得關注的是,本文還將探遷移線程在異步任務遷移中的關鍵角色。這些跨子系統的協作機制體現了Linux內核作為一個整體系統的複雜性和協調性。
通過本文的詳細分析,讀者不僅能夠深入理解Linux 2.6.10調度器負載均衡的技術細節,更能領略到操作系統內核設計中平衡性能、功耗、實時性和穩定性的藝術。這些設計思想對於現代多核處理器環境下的操作系統優化仍然具有重要的參考價值。
將任務遷移到更合適的CPU上執行sched_exec
void sched_exec(void)
{
struct sched_domain *tmp, *sd = NULL;
int new_cpu, this_cpu = get_cpu();
schedstat_inc(this_rq(), sbe_cnt);
/* Prefer the current CPU if there's only this task running */
if (this_rq()->nr_running <= 1)
goto out;
for_each_domain(this_cpu, tmp)
if (tmp->flags & SD_BALANCE_EXEC)
sd = tmp;
if (sd) {
schedstat_inc(sd, sbe_attempts);
new_cpu = find_idlest_cpu(current, this_cpu, sd);
if (new_cpu != this_cpu) {
schedstat_inc(sd, sbe_pushed);
put_cpu();
sched_migrate_task(current, new_cpu);
return;
}
}
out:
put_cpu();
}
函數功能概述
sched_exec 函數在進程執行 execve 系統調用時被調用,用於在需要時將任務遷移到更合適的CPU上執行,以優化系統性能和負載均衡
代碼逐段解析
變量聲明和初始化
struct sched_domain *tmp, *sd = NULL;
int new_cpu, this_cpu = get_cpu();
struct sched_domain *tmp, *sd = NULL:
tmp:臨時變量,用於遍歷調度域sd:找到的目標調度域,初始為NULL
int new_cpu, this_cpu = get_cpu():
new_cpu:要遷移到的目標CPUthis_cpu = get_cpu():獲取當前CPU編號並禁用內核搶佔get_cpu()宏會返回當前CPU ID並增加preempt_count
統計計數和簡單情況檢查
schedstat_inc(this_rq(), sbe_cnt);
/* Prefer the current CPU if there's only this task running */
if (this_rq()->nr_running <= 1)
goto out;
schedstat_inc(this_rq(), sbe_cnt):增加調度統計計數器sbe_cnt(schedule balance exec count)this_rq()->nr_running <= 1:檢查當前運行隊列中是否只有當前任務在運行- 如果只有當前任務,直接跳轉到
out標籤(保持當前CPU) - 這是優化:如果當前CPU很空閒,就不需要遷移
查找支持執行負載均衡的調度域
for_each_domain(this_cpu, tmp)
if (tmp->flags & SD_BALANCE_EXEC)
sd = tmp;
for_each_domain(this_cpu, tmp):遍歷當前CPU的所有調度域tmp->flags & SD_BALANCE_EXEC:檢查調度域是否支持執行時的負載均衡sd = tmp:記錄最後一個找到的支持SD_BALANCE_EXEC的調度域- 調度域是Linux調度器中用於描述CPU拓撲和負載均衡範圍的層次結構
找到合適調度域後的處理
if (sd) {
schedstat_inc(sd, sbe_attempts);
new_cpu = find_idlest_cpu(current, this_cpu, sd);
if (sd):如果找到了合適的調度域schedstat_inc(sd, sbe_attempts):增加嘗試遷移的統計計數new_cpu = find_idlest_cpu(current, this_cpu, sd):在調度域內查找最空閒的CPU
current:當前任務this_cpu:當前CPUsd:目標調度域
執行任務遷移
if (new_cpu != this_cpu) {
schedstat_inc(sd, sbe_pushed);
put_cpu();
sched_migrate_task(current, new_cpu);
return;
}
}
if (new_cpu != this_cpu):如果找到的最空閒CPU不是當前CPUschedstat_inc(sd, sbe_pushed):增加成功遷移的統計計數put_cpu():啓用內核搶佔(與前面的get_cpu()配對)sched_migrate_task(current, new_cpu):將當前任務遷移到新的CPU
- 為什麼是遷移當前進程呢
- 因為execve是替換當前進程的上下文,並不是創建新進程,所以遷移當前進程相當於遷移新進程
- 之所以要進行負載均衡,是因為新的執行程序可能變成重量級程序
return:直接返回,不再執行後面的代碼
退出處理
out:
put_cpu();
out標籤:統一的退出點put_cpu():啓用內核搶佔- 這個路徑表示:要麼不需要遷移,要麼沒有找到更合適的CPU
函數功能總結
主要功能:在進程執行execve時進行智能的CPU遷移決策,優化系統負載均衡
- 時機選擇:在程序執行時重新評估CPU分配
- 負載均衡:將任務遷移到更合適的CPU
- 查找當前調度域內最空閒的CPU
- 避免CPU熱點,提高整體系統性能
- 條件優化:只在必要時才遷移
- 當前CPU空閒時不遷移
- 沒有合適目標CPU時不遷移
統計信息:
sbe_cnt:執行調度平衡的總次數sbe_attempts:嘗試遷移的次數sbe_pushed:實際遷移的次數
在指定的調度域內查找最空閒的CPUfind_idlest_cpu
static int find_idlest_cpu(struct task_struct *p, int this_cpu,
struct sched_domain *sd)
{
unsigned long load, min_load, this_load;
int i, min_cpu;
cpumask_t mask;
min_cpu = UINT_MAX;
min_load = ULONG_MAX;
cpus_and(mask, sd->span, p->cpus_allowed);
for_each_cpu_mask(i, mask) {
load = target_load(i);
if (load < min_load) {
min_cpu = i;
min_load = load;
/* break out early on an idle CPU: */
if (!min_load)
break;
}
}
/* add +1 to account for the new task */
this_load = source_load(this_cpu) + SCHED_LOAD_SCALE;
/*
* Would with the addition of the new task to the
* current CPU there be an imbalance between this
* CPU and the idlest CPU?
*
* Use half of the balancing threshold - new-context is
* a good opportunity to balance.
*/
if (min_load*(100 + (sd->imbalance_pct-100)/2) < this_load*100)
return min_cpu;
return this_cpu;
}
函數功能概述
find_idlest_cpu 函數在指定的調度域內查找最空閒的CPU,並決定是否將任務遷移到該CPU上
代碼逐段解析
變量聲明和初始化
unsigned long load, min_load, this_load;
int i, min_cpu;
cpumask_t mask;
min_cpu = UINT_MAX;
min_load = ULONG_MAX;
load:臨時存儲每個CPU的負載值min_load:記錄找到的最小負載值,初始設為最大無符號長整數值this_load:當前CPU的負載值i:循環計數器min_cpu:記錄找到的最空閒CPU編號,初始設為最大無符號整數值mask:CPU掩碼,用於存儲允許搜索的CPU集合- 初始化
min_cpu和min_load為最大值,確保第一次比較能正確更新
計算允許搜索的CPU集合
cpus_and(mask, sd->span, p->cpus_allowed);
cpus_and(mask, sd->span, p->cpus_allowed):計算三個集合的交集
sd->span:調度域包含的所有CPUp->cpus_allowed:任務允許運行的CPU(受cpuset、affinity等限制)mask:結果掩碼,只包含既在調度域內又允許任務運行的CPU
遍歷所有候選CPU查找最空閒的
for_each_cpu_mask(i, mask) {
load = target_load(i);
if (load < min_load) {
min_cpu = i;
min_load = load;
/* break out early on an idle CPU: */
if (!min_load)
break;
}
}
for_each_cpu_mask(i, mask):遍歷掩碼中的每個CPUload = target_load(i):獲取CPU i的負載,取平均負載和當前瞬時負載的較大值if (load < min_load):如果找到更空閒的CPU
- 更新
min_cpu和min_load
if (!min_load) break:優化策略,如果找到完全空閒的CPU(負載為0),立即終止搜索
計算當前CPU的預估負載
/* add +1 to account for the new task */
this_load = source_load(this_cpu) + SCHED_LOAD_SCALE;
source_load(this_cpu):獲取當前CPU的負載,取平均負載和當前瞬時負載的較小值+ SCHED_LOAD_SCALE:加上新任務的負載貢獻
SCHED_LOAD_SCALE通常是128,表示一個標準任務的負載單位
- 這計算的是如果新任務留在當前CPU的預估總負載
負載均衡決策
/*
* Would with the addition of the new task to the
* current CPU there be an imbalance between this
* CPU and the idlest CPU?
*
* Use half of the balancing threshold - new-context is
* a good opportunity to balance.
*/
if (min_load*(100 + (sd->imbalance_pct-100)/2) < this_load*100)
return min_cpu;
- 使用一半的平衡閾值,因為新上下文是平衡的好機會
min_load*(100 + (sd->imbalance_pct-100)/2):計算調整後的最小負載
sd->imbalance_pct:調度域的不平衡百分比(通常125,表示25%的不平衡容忍度)(sd->imbalance_pct-100)/2:計算一半的不平衡容忍度- 例如:如果
imbalance_pct = 125,則(125-100)/2 = 12 - 所以是
min_load * 112
this_load*100:當前CPU負載的100倍- 條件判斷:如果
調整後最閒CPU負載 < 當前CPU負載,則遷移
保持當前CPU
return this_cpu;
- 如果負載差異不夠大,不值得遷移,返回當前CPU
關鍵函數和概念詳解
target_load(i) 和 source_load(i)
- 這兩個函數都返回CPU的負載,但使用不同的計算策略
target_load用於評估目標CPU的負載,取平均負載和當前瞬時負載的較大值source_load用於評估源CPU的負載,取平均負載和當前瞬時負載的較小值- 任務遷移一般是偏向於不遷移,所以會盡量讓目標CPU的負載往大了算,源CPU往小了算,這樣可以避免因為負載抖動頻繁遷移
負載均衡算法詳解
min_load * (100 + (imbalance_pct - 100) / 2) < this_load * 100
- 一般進行任務遷移時,不平衡容忍度是25%
- 當前因為是新的上下文,不用考慮CPU緩存,可以採取激進一點的負載均衡,取不平衡容忍度為12%
函數功能總結
主要功能:在調度域內智能選擇最合適的CPU來運行新任務
- 搜索範圍限制:
- 只在調度域內搜索
- 遵守任務的CPU親和性限制
- 最閒CPU查找:
- 遍歷所有候選CPU
- 找到負載最小的CPU
- 優化:發現完全空閒CPU時提前終止
- 遷移決策:
- 計算當前CPU接納新任務後的預估負載
- 使用調整閾值避免不必要的遷移
- 只在負載不平衡明顯時才遷移
將任務遷移到指定的目標CPUsched_migrate_task
static void sched_migrate_task(task_t *p, int dest_cpu)
{
migration_req_t req;
runqueue_t *rq;
unsigned long flags;
rq = task_rq_lock(p, &flags);
if (!cpu_isset(dest_cpu, p->cpus_allowed)
|| unlikely(cpu_is_offline(dest_cpu)))
goto out;
schedstat_inc(rq, smt_cnt);
/* force the process onto the specified CPU */
if (migrate_task(p, dest_cpu, &req)) {
/* Need to wait for migration thread (might exit: take ref). */
struct task_struct *mt = rq->migration_thread;
get_task_struct(mt);
task_rq_unlock(rq, &flags);
wake_up_process(mt);
put_task_struct(mt);
wait_for_completion(&req.done);
return;
}
out:
task_rq_unlock(rq, &flags);
}
函數功能概述
sched_migrate_task 函數用於將任務遷移到指定的目標CPU,處理遷移過程中的各種邊界條件和同步機制
代碼逐段解析
變量聲明和鎖獲取
migration_req_t req;
runqueue_t *rq;
unsigned long flags;
rq = task_rq_lock(p, &flags);
migration_req_t req:遷移請求結構,用於遷移線程和調用者之間的同步runqueue_t *rq:當前任務所在運行隊列的指針unsigned long flags:用於保存中斷狀態的標誌rq = task_rq_lock(p, &flags):鎖定任務當前所在的運行隊列,並禁用中斷
- 這是關鍵的同步操作,防止在遷移過程中其他CPU修改任務狀態
目標CPU有效性檢查
if (!cpu_isset(dest_cpu, p->cpus_allowed)
|| unlikely(cpu_is_offline(dest_cpu)))
goto out;
!cpu_isset(dest_cpu, p->cpus_allowed):檢查目標CPU是否在任務的允許CPU集合中unlikely(cpu_is_offline(dest_cpu)):檢查目標CPU是否離線
unlikely提示編譯器這個條件很少成立,優化分支預測
- 如果任一條件滿足,跳轉到
out標籤直接解鎖返回
遷移統計計數
schedstat_inc(rq, smt_cnt);
schedstat_inc(rq, smt_cnt):增加調度統計計數器smt_cnt表示任務遷移的次數統計- 用於監控系統負載均衡活動的頻率
嘗試遷移任務
/* force the process onto the specified CPU */
if (migrate_task(p, dest_cpu, &req)) {
- 強制將進程放到指定的CPU上
migrate_task(p, dest_cpu, &req):嘗試執行遷移操作
- 返回值為真表示需要異步遷移(任務當前正在運行)
- 返回值為假表示遷移立即完成或失敗
異步遷移處理
/* Need to wait for migration thread (might exit: take ref). */
struct task_struct *mt = rq->migration_thread;
get_task_struct(mt);
task_rq_unlock(rq, &flags);
wake_up_process(mt);
put_task_struct(mt);
wait_for_completion(&req.done);
return;
struct task_struct *mt = rq->migration_thread:獲取當前運行隊列的遷移線程get_task_struct(mt):增加遷移線程的引用計數,防止在操作過程中線程退出task_rq_unlock(rq, &flags):釋放運行隊列鎖,允許其他操作繼續wake_up_process(mt):喚醒遷移線程來處理實際的遷移工作put_task_struct(mt):減少遷移線程的引用計數wait_for_completion(&req.done):等待遷移完成
- 遷移線程完成後會調用
complete(&req.done)
return:直接返回,不執行後面的解鎖代碼
快速退出路徑
out:
task_rq_unlock(rq, &flags);
out標籤:用於無效目標CPU或同步遷移完成的情況task_rq_unlock(rq, &flags):釋放運行隊列鎖並恢復中斷狀態
關鍵機制詳解
為什麼需要異步遷移?
場景1:任務正在運行
CPU A: 任務P正在執行
CPU B: 調用 sched_migrate_task(P, CPU_B)
→ 不能立即停止P在CPU A上的執行
→ 需要異步遷移
場景2:任務可立即遷移
CPU A: 任務P處於睡眠狀態
CPU B: 調用 sched_migrate_task(P, CPU_B)
→ 可以立即遷移
→ 同步完成
函數功能總結
主要功能:安全地將任務遷移到指定的目標CPU,處理同步和異步遷移場景
- 安全性檢查:
- 驗證目標CPU的有效性
- 檢查CPU親和性約束
- 確保目標CPU在線
- 遷移策略:
- 立即遷移(任務未運行)
- 異步遷移(任務正在運行)
判斷任務是否可以立即遷移migrate_task
static int migrate_task(task_t *p, int dest_cpu, migration_req_t *req)
{
runqueue_t *rq = task_rq(p);
/*
* If the task is not on a runqueue (and not running), then
* it is sufficient to simply update the task's cpu field.
*/
if (!p->array && !task_running(rq, p)) {
set_task_cpu(p, dest_cpu);
return 0;
}
init_completion(&req->done);
req->type = REQ_MOVE_TASK;
req->task = p;
req->dest_cpu = dest_cpu;
list_add(&req->list, &rq->migration_queue);
return 1;
}
函數功能概述
migrate_task 函數是任務遷移的核心函數,決定任務是否可以立即遷移,或者需要排隊進行異步遷移
代碼逐段解析
獲取運行隊列
runqueue_t *rq = task_rq(p);
runqueue_t *rq = task_rq(p):獲取任務當前所在的運行隊列task_rq(p)是一個宏,返回指向任務p所在CPU運行隊列的指針- 這個運行隊列包含了該CPU上所有可運行任務的信息
立即遷移條件檢查
/*
* If the task is not on a runqueue (and not running), then
* it is sufficient to simply update the task's cpu field.
*/
if (!p->array && !task_running(rq, p)) {
set_task_cpu(p, dest_cpu);
return 0;
}
如果任務不在運行隊列中(且不在運行狀態),那麼只需簡單更新任務的
cpu字段即可
!p->array:檢查任務是否不在任何運行隊列的活動數組中
p->array指向任務所屬的運行隊列優先級數組- 如果為NULL,表示任務不在可運行狀態(可能是睡眠、停止等)
!task_running(rq, p):檢查任務是否當前沒有在執行
task_running(rq, p)檢查任務是否正在CPU上運行
立即遷移執行
set_task_cpu(p, dest_cpu):直接設置任務的CPU字段為目標CPUreturn 0:返回0表示遷移立即完成,不需要異步處理
初始化遷移請求
init_completion(&req->done);
init_completion(&req->done):初始化完成量機制req->done是一個struct completion,用於同步遷移操作的完成- 遷移線程完成後會調用
complete(&req->done)來通知等待者
設置遷移請求參數
req->type = REQ_MOVE_TASK;
req->task = p;
req->dest_cpu = dest_cpu;
req->type = REQ_MOVE_TASK:設置請求類型為"移動任務"req->task = p:設置要遷移的任務指針req->dest_cpu = dest_cpu:設置目標CPU編號
將請求加入遷移隊列
list_add(&req->list, &rq->migration_queue);
list_add(&req->list, &rq->migration_queue):將遷移請求添加到運行隊列的遷移隊列中&req->list:遷移請求的鏈表節點&rq->migration_queue:運行隊列的遷移請求鏈表頭- 遷移線程會從這個隊列中取出請求並處理
返回異步遷移標誌
return 1;
return 1:返回1表示需要異步遷移- 調用者需要等待遷移線程完成實際操作
函數功能總結
主要功能:根據任務當前狀態決定遷移策略,支持立即遷移和異步遷移兩種模式
- 立即遷移條件:
- 任務不在運行隊列中(
!p->array) - 任務當前沒有在執行(
!task_running(rq, p)) - 直接更新CPU字段即可
- 異步遷移條件:
- 任務正在運行或準備運行
- 需要遷移線程介入處理
- 通過完成量機制同步
返回值語義:
- 0:遷移立即完成,調用者無需等待
- 1:需要異步遷移,調用者必須等待遷移線程完成
遷移線程migration_thread
static int migration_thread(void * data)
{
runqueue_t *rq;
int cpu = (long)data;
rq = cpu_rq(cpu);
BUG_ON(rq->migration_thread != current);
set_current_state(TASK_INTERRUPTIBLE);
while (!kthread_should_stop()) {
struct list_head *head;
migration_req_t *req;
if (current->flags & PF_FREEZE)
refrigerator(PF_FREEZE);
spin_lock_irq(&rq->lock);
if (cpu_is_offline(cpu)) {
spin_unlock_irq(&rq->lock);
goto wait_to_die;
}
if (rq->active_balance) {
active_load_balance(rq, cpu);
rq->active_balance = 0;
}
head = &rq->migration_queue;
if (list_empty(head)) {
spin_unlock_irq(&rq->lock);
schedule();
set_current_state(TASK_INTERRUPTIBLE);
continue;
}
req = list_entry(head->next, migration_req_t, list);
list_del_init(head->next);
if (req->type == REQ_MOVE_TASK) {
spin_unlock(&rq->lock);
__migrate_task(req->task, smp_processor_id(),
req->dest_cpu);
local_irq_enable();
} else if (req->type == REQ_SET_DOMAIN) {
rq->sd = req->sd;
spin_unlock_irq(&rq->lock);
} else {
spin_unlock_irq(&rq->lock);
WARN_ON(1);
}
complete(&req->done);
}
__set_current_state(TASK_RUNNING);
return 0;
wait_to_die:
/* Wait for kthread_stop */
set_current_state(TASK_INTERRUPTIBLE);
while (!kthread_should_stop()) {
schedule();
set_current_state(TASK_INTERRUPTIBLE);
}
__set_current_state(TASK_RUNNING);
return 0;
}
函數功能概述
migration_thread 是每個CPU的遷移線程主函數,負責處理任務遷移請求和主動負載均衡
代碼逐段解析
線程初始化和設置
runqueue_t *rq;
int cpu = (long)data;
rq = cpu_rq(cpu);
BUG_ON(rq->migration_thread != current);
set_current_state(TASK_INTERRUPTIBLE);
runqueue_t *rq:運行隊列指針int cpu = (long)data:從參數獲取CPU編號(線程綁定的CPU)rq = cpu_rq(cpu):獲取該CPU的運行隊列BUG_ON(rq->migration_thread != current):驗證當前線程確實是該CPU的遷移線程set_current_state(TASK_INTERRUPTIBLE):設置線程為可中斷睡眠狀態
主循環開始
while (!kthread_should_stop()) {
struct list_head *head;
migration_req_t *req;
while (!kthread_should_stop()):主循環,直到收到停止信號head:遷移隊列鏈表頭指針req:遷移請求指針
凍結檢查
if (current->flags & PF_FREEZE)
refrigerator(PF_FREEZE);
- 檢查當前線程是否有凍結標誌
refrigerator(PF_FREEZE):如果系統正在掛起,進入凍結狀態
加鎖和CPU離線檢查
spin_lock_irq(&rq->lock);
if (cpu_is_offline(cpu)) {
spin_unlock_irq(&rq->lock);
goto wait_to_die;
}
spin_lock_irq(&rq->lock):獲取運行隊列鎖並禁用中斷cpu_is_offline(cpu):檢查綁定的CPU是否已離線- 如果CPU離線,釋放鎖並跳轉到等待退出路徑
主動負載均衡處理
if (rq->active_balance) {
active_load_balance(rq, cpu);
rq->active_balance = 0;
}
rq->active_balance:檢查是否需要主動負載均衡active_load_balance(rq, cpu):執行主動負載均衡操作rq->active_balance = 0:清除主動平衡標誌
遷移隊列檢查
head = &rq->migration_queue;
if (list_empty(head)) {
spin_unlock_irq(&rq->lock);
schedule();
set_current_state(TASK_INTERRUPTIBLE);
continue;
}
head = &rq->migration_queue:獲取遷移隊列鏈表頭list_empty(head):檢查遷移隊列是否為空- 如果隊列為空:
- 釋放鎖
schedule():讓出CPU,等待喚醒- 重新設置為可中斷睡眠狀態
continue:繼續循環
獲取遷移請求
req = list_entry(head->next, migration_req_t, list);
list_del_init(head->next);
req = list_entry(head->next, migration_req_t, list):從鏈表獲取第一個遷移請求list_del_init(head->next):從隊列中刪除該請求
處理不同類型的遷移請求
if (req->type == REQ_MOVE_TASK) {
spin_unlock(&rq->lock);
__migrate_task(req->task, smp_processor_id(), req->dest_cpu);
local_irq_enable();
} else if (req->type == REQ_SET_DOMAIN) {
rq->sd = req->sd;
spin_unlock_irq(&rq->lock);
} else {
spin_unlock_irq(&rq->lock);
WARN_ON(1);
}
任務遷移請求(REQ_MOVE_TASK):
spin_unlock(&rq->lock):釋放運行隊列鎖(但保持中斷禁用)__migrate_task(req->task, smp_processor_id(), req->dest_cpu):執行實際的任務遷移
smp_processor_id():獲取當前CPU(遷移線程運行的CPU)
local_irq_enable():啓用本地中斷
設置調度域請求(REQ_SET_DOMAIN):
rq->sd = req->sd:更新運行隊列的調度域指針spin_unlock_irq(&rq->lock):釋放鎖並啓用中斷
未知請求類型:
WARN_ON(1):輸出警告信息
完成請求通知
complete(&req->done);
complete(&req->done):通知等待者遷移操作已完成
正常退出路徑
__set_current_state(TASK_RUNNING);
return 0;
- 循環結束後設置線程為運行狀態並返回
CPU離線等待退出路徑
wait_to_die:
/* Wait for kthread_stop */
set_current_state(TASK_INTERRUPTIBLE);
while (!kthread_should_stop()) {
schedule();
set_current_state(TASK_INTERRUPTIBLE);
}
__set_current_state(TASK_RUNNING);
return 0;
wait_to_die標籤:CPU離線時的專用退出路徑- 在可中斷睡眠狀態中等待停止信號
- 收到停止信號後設置運行狀態並返回
關鍵機制詳解
請求類型處理
REQ_MOVE_TASK(任務遷移):
- 需要執行實際的遷移操作
- 在遷移過程中需要仔細處理鎖和中斷狀態
REQ_SET_DOMAIN(設置調度域):
- 更新調度拓撲信息
- 相對簡單的操作
函數功能總結
主要功能:每個CPU的專用遷移線程,處理異步任務遷移和負載均衡操作
- 任務遷移處理:
- 從遷移隊列中取出請求
- 執行實際的任務遷移操作
- 通知請求完成
- 主動負載均衡:
- 檢測並執行主動負載均衡
- 優化系統整體性能
- 調度域管理:
- 響應調度域更新請求
- 維護CPU調度拓撲信息
將任務從源CPU遷移到目標CPU__migrate_task
static void __migrate_task(struct task_struct *p, int src_cpu, int dest_cpu)
{
runqueue_t *rq_dest, *rq_src;
if (unlikely(cpu_is_offline(dest_cpu)))
return;
rq_src = cpu_rq(src_cpu);
rq_dest = cpu_rq(dest_cpu);
double_rq_lock(rq_src, rq_dest);
/* Already moved. */
if (task_cpu(p) != src_cpu)
goto out;
/* Affinity changed (again). */
if (!cpu_isset(dest_cpu, p->cpus_allowed))
goto out;
set_task_cpu(p, dest_cpu);
if (p->array) {
/*
* Sync timestamp with rq_dest's before activating.
* The same thing could be achieved by doing this step
* afterwards, and pretending it was a local activate.
* This way is cleaner and logically correct.
*/
p->timestamp = p->timestamp - rq_src->timestamp_last_tick
+ rq_dest->timestamp_last_tick;
deactivate_task(p, rq_src);
activate_task(p, rq_dest, 0);
if (TASK_PREEMPTS_CURR(p, rq_dest))
resched_task(rq_dest->curr);
}
out:
double_rq_unlock(rq_src, rq_dest);
}
函數功能概述
__migrate_task 函數是實際執行任務遷移的核心函數,負責將任務從源CPU遷移到目標CPU,處理所有必要的狀態更新和調度器數據結構調整
代碼逐段解析
目標CPU離線檢查
if (unlikely(cpu_is_offline(dest_cpu)))
return;
unlikely(cpu_is_offline(dest_cpu)):檢查目標CPU是否離線unlikely宏提示編譯器這個條件很少成立,優化分支預測- 如果目標CPU離線,直接返回,不執行遷移
獲取運行隊列
rq_src = cpu_rq(src_cpu);
rq_dest = cpu_rq(dest_cpu);
rq_src = cpu_rq(src_cpu):獲取源CPU的運行隊列rq_dest = cpu_rq(dest_cpu):獲取目標CPU的運行隊列- 運行隊列包含每個CPU的調度狀態和任務列表
雙重鎖獲取
double_rq_lock(rq_src, rq_dest);
double_rq_lock(rq_src, rq_dest):同時鎖定兩個運行隊列- 這是關鍵操作,防止在遷移過程中其他CPU修改任務狀態
- 鎖獲取順序遵循固定的CPU編號順序,避免死鎖
任務位置驗證
/* Already moved. */
if (task_cpu(p) != src_cpu)
goto out;
- 任務可能已經被移動了
task_cpu(p) != src_cpu:檢查任務的當前CPU是否還是源CPU- 如果不是,説明任務已經被其他遷移操作移動,跳轉到out標籤
CPU親和性驗證
/* Affinity changed (again). */
if (!cpu_isset(dest_cpu, p->cpus_allowed))
goto out;
- 親和性可能改變了
!cpu_isset(dest_cpu, p->cpus_allowed):檢查目標CPU是否在任務的允許CPU集合中- 如果不在,説明任務的CPU親和性在遷移過程中被修改,跳轉到out標籤
更新任務CPU字段
set_task_cpu(p, dest_cpu);
set_task_cpu(p, dest_cpu):更新任務的CPU字段為目標CPU- 這是遷移操作的第一步,更新任務的基本屬性
運行中任務處理
if (p->array) {
p->timestamp = p->timestamp - rq_src->timestamp_last_tick
+ rq_dest->timestamp_last_tick;
if (p->array):檢查任務是否在運行隊列中(可運行狀態)p->timestamp = p->timestamp - rq_src->timestamp_last_tick + rq_dest->timestamp_last_tick:
- 調整任務的時間戳,使其相對於目標運行隊列的時鐘
- 確保任務的調度統計在目標CPU上正確
停用和激活任務
deactivate_task(p, rq_src);
activate_task(p, rq_dest, 0);
deactivate_task(p, rq_src):從源運行隊列中移除任務
- 將任務從運行隊列的優先級數組中刪除
- 更新運行隊列的任務計數
activate_task(p, rq_dest, 0):將任務添加到目標運行隊列
0參數表示跨CPU添加任務- 將任務插入到目標運行隊列的適當優先級位置
搶佔檢查
if (TASK_PREEMPTS_CURR(p, rq_dest))
resched_task(rq_dest->curr);
TASK_PREEMPTS_CURR(p, rq_dest):檢查遷移過來的任務是否應該搶佔目標CPU上當前運行的任務- 判斷標準:基於任務優先級
resched_task(rq_dest->curr):如果需要搶佔,設置目標CPU當前任務的重調度標誌
解鎖和退出
out:
double_rq_unlock(rq_src, rq_dest);
out標籤:統一的退出點double_rq_unlock(rq_src, rq_dest):釋放兩個運行隊列的鎖
關鍵機制詳解
雙重鎖機制
- 死鎖避免:通過固定的鎖獲取順序(按CPU編號大小排序)防止死鎖
- 性能優化:如果是同一個運行隊列,只獲取一次鎖
時間戳同步的重要性
p->timestamp = p->timestamp - rq_src->timestamp_last_tick + rq_dest->timestamp_last_tick;
為什麼需要同步?
- 每個運行隊列有自己的時間戳基準
- 調度決策依賴於相對時間
- 不同步會導致任務在目標CPU上得到不公平的調度待遇
函數功能總結
主要功能:安全地將任務從一個CPU遷移到另一個CPU,維護調度器數據結構的完整性
- 安全性驗證:
- 目標CPU在線檢查
- 任務還未遷移確認
- CPU親和性驗證
- 狀態轉移:
- 更新任務CPU字段
- 時間戳同步
- 運行隊列操作(停用+激活)
- 調度決策:
- 搶佔性檢查
- 重調度觸發
將當前任務置於冷凍狀態refrigerator
void refrigerator(unsigned long flag)
{
/* Hmm, should we be allowed to suspend when there are realtime
processes around? */
long save;
save = current->state;
current->state = TASK_UNINTERRUPTIBLE;
pr_debug("%s entered refrigerator\n", current->comm);
printk("=");
current->flags &= ~PF_FREEZE;
spin_lock_irq(¤t->sighand->siglock);
recalc_sigpending(); /* We sent fake signal, clean it up */
spin_unlock_irq(¤t->sighand->siglock);
current->flags |= PF_FROZEN;
while (current->flags & PF_FROZEN)
schedule();
pr_debug("%s left refrigerator\n", current->comm);
current->state = save;
}
函數功能概述
refrigerator 函數用於將當前任務"冷凍",進入一種特殊的睡眠狀態,主要用於系統掛起(suspend)和休眠(hibernate)操作
代碼逐段解析
變量聲明
long save;
long save:用於保存當前任務狀態的變量
保存當前狀態並設置為不可中斷狀態
save = current->state;
current->state = TASK_UNINTERRUPTIBLE;
save = current->state:保存任務的當前狀態(運行、睡眠等)current->state = TASK_UNINTERRUPTIBLE:將任務狀態設置為不可中斷睡眠
- 確保任務不會被信號喚醒
- 這是進入冷凍狀態的前提
調試信息輸出
pr_debug("%s entered refrigerator\n", current->comm);
printk("=");
pr_debug("%s entered refrigerator\n", current->comm):輸出調試信息,顯示哪個任務進入了冷凍狀態printk("="):在系統日誌中輸出等號字符,作為可視化的進度指示
清除凍結標誌
current->flags &= ~PF_FREEZE;
current->flags &= ~PF_FREEZE:清除任務的PF_FREEZE標誌PF_FREEZE標誌表示任務需要進入冷凍狀態,現在已經開始處理,所以清除
信號處理清理
spin_lock_irq(¤t->sighand->siglock);
recalc_sigpending(); /* We sent fake signal, clean it up */
spin_unlock_irq(¤t->sighand->siglock);
spin_lock_irq(¤t->sighand->siglock):獲取信號處理鎖並禁用中斷recalc_sigpending():重新計算待處理信號
- 在進入冷凍前可能發送了喚醒信號,需要清理信號狀態
spin_unlock_irq(¤t->sighand->siglock):釋放信號鎖並啓用中斷
設置冷凍標誌並進入等待循環
current->flags |= PF_FROZEN;
while (current->flags & PF_FROZEN)
schedule();
current->flags |= PF_FROZEN:設置任務的PF_FROZEN標誌,表示任務已被冷凍while (current->flags & PF_FROZEN) schedule():循環調用schedule()讓出CPU,直到PF_FROZEN標誌被清除
- 這是核心的等待機制,任務在這裏"休眠"
- 只有當系統恢復時,其他代碼才會清除這個標誌
退出冷凍狀態
pr_debug("%s left refrigerator\n", current->comm);
current->state = save;
pr_debug("%s left refrigerator\n", current->comm):輸出調試信息,顯示任務離開冷凍狀態current->state = save:恢復任務原來的狀態
- 如果原來是運行狀態,現在恢復為運行狀態
- 如果原來是睡眠狀態,保持睡眠狀態
關鍵機制詳解
任務狀態標誌
// PF_FREEZE: 需要進入冷凍狀態(輸入標誌)
// PF_FROZEN: 已經進入冷凍狀態(狀態標誌)
// 狀態流轉:
// 進入前: PF_FREEZE = 1, PF_FROZEN = 0
// 進入後: PF_FREEZE = 0, PF_FROZEN = 1
// 退出後: PF_FREEZE = 0, PF_FROZEN = 0
為什麼使用 TASK_UNINTERRUPTIBLE?
current->state = TASK_UNINTERRUPTIBLE;
// 對比:
// TASK_INTERRUPTIBLE: 可被信號喚醒 - 不適合掛起操作
// TASK_UNINTERRUPTIBLE: 不可被信號喚醒 - 確保任務停留在冷凍狀態
信號清理的必要性
在進入冷凍前,系統可能向任務發送了偽信號來喚醒它們進行冷凍操作。現在需要清理這些信號狀態,避免任務被錯誤的信號喚醒
函數功能總結
主要功能:將當前任務置於冷凍狀態,用於系統掛起和休眠操作。
- 狀態管理:
- 保存和恢復任務狀態
- 設置適當的任務標誌
- 安全進入:
- 確保任務不會被意外喚醒
- 清理可能干擾的信號狀態
- 等待機制:
- 在冷凍狀態中循環讓出CPU
- 等待系統恢復指令
- 調試支持:
- 提供進入和離開的調試信息
- 可視化進度指示
主動從繁忙CPU遷移任務到空閒CPUactive_load_balance
static void active_load_balance(runqueue_t *busiest_rq, int busiest_cpu)
{
struct sched_domain *sd;
struct sched_group *cpu_group;
cpumask_t visited_cpus;
schedstat_inc(busiest_rq, alb_cnt);
/*
* Search for suitable CPUs to push tasks to in successively higher
* domains with SD_LOAD_BALANCE set.
*/
visited_cpus = CPU_MASK_NONE;
for_each_domain(busiest_cpu, sd) {
if (!(sd->flags & SD_LOAD_BALANCE) || busiest_rq->nr_running <= 1)
break; /* no more domains to search or no more tasks to move */
cpu_group = sd->groups;
do { /* sched_groups should either use list_heads or be merged into the domains structure */
int cpu, target_cpu = -1;
runqueue_t *target_rq;
for_each_cpu_mask(cpu, cpu_group->cpumask) {
if (cpu_isset(cpu, visited_cpus) || cpu == busiest_cpu ||
!cpu_and_siblings_are_idle(cpu)) {
cpu_set(cpu, visited_cpus);
continue;
}
target_cpu = cpu;
break;
}
if (target_cpu == -1)
goto next_group; /* failed to find a suitable target cpu in this domain */
target_rq = cpu_rq(target_cpu);
/*
* This condition is "impossible", if it occurs we need to fix it
* Reported by Bjorn Helgaas on a 128-cpu setup.
*/
BUG_ON(busiest_rq == target_rq);
/* move a task from busiest_rq to target_rq */
double_lock_balance(busiest_rq, target_rq);
if (move_tasks(target_rq, target_cpu, busiest_rq, 1, sd, SCHED_IDLE)) {
schedstat_inc(busiest_rq, alb_lost);
schedstat_inc(target_rq, alb_gained);
} else {
schedstat_inc(busiest_rq, alb_failed);
}
spin_unlock(&target_rq->lock);
next_group:
cpu_group = cpu_group->next;
} while (cpu_group != sd->groups && busiest_rq->nr_running > 1);
}
}
函數功能概述
active_load_balance 函數是主動負載均衡的核心實現,用於在檢測到CPU負載不平衡時,主動從繁忙CPU遷移任務到空閒CPU
代碼逐段解析
變量聲明和初始化
struct sched_domain *sd;
struct sched_group *cpu_group;
cpumask_t visited_cpus;
schedstat_inc(busiest_rq, alb_cnt);
struct sched_domain *sd:調度域指針,用於遍歷CPU拓撲層次struct sched_group *cpu_group:CPU組指針,表示調度域內的CPU分組cpumask_t visited_cpus:CPU掩碼,記錄已經訪問過的CPU,避免重複處理schedstat_inc(busiest_rq, alb_cnt):增加主動負載均衡的統計計數器
循環初始化
visited_cpus = CPU_MASK_NONE;
for_each_domain(busiest_cpu, sd) {
- 在設置了SD_LOAD_BALANCE標誌的逐級更高的域中搜索合適的CPU來推送任務
visited_cpus = CPU_MASK_NONE:初始化已訪問CPU掩碼為空for_each_domain(busiest_cpu, sd):從繁忙CPU開始,遍歷所有調度域層次
調度域檢查
if (!(sd->flags & SD_LOAD_BALANCE) || busiest_rq->nr_running <= 1)
break; /* no more domains to search or no more tasks to move */
!(sd->flags & SD_LOAD_BALANCE):檢查調度域是否支持負載均衡busiest_rq->nr_running <= 1:檢查繁忙運行隊列是否只有1個或更少任務在運行- 如果任一條件滿足,跳出循環(沒有更多域可搜索或沒有更多任務可移動)
CPU組循環開始
cpu_group = sd->groups;
do {
int cpu, target_cpu = -1;
runqueue_t *target_rq;
cpu_group = sd->groups:獲取調度域的第一個CPU組target_cpu = -1:初始化目標CPU為無效值target_rq:目標運行隊列指針
在CPU組中查找合適的目標CPU
for_each_cpu_mask(cpu, cpu_group->cpumask) {
if (cpu_isset(cpu, visited_cpus) || cpu == busiest_cpu ||
!cpu_and_siblings_are_idle(cpu)) {
cpu_set(cpu, visited_cpus);
continue;
}
target_cpu = cpu;
break;
}
for_each_cpu_mask(cpu, cpu_group->cpumask):遍歷CPU組中的所有CPU- 排除條件檢查:
cpu_isset(cpu, visited_cpus):CPU已經被訪問過cpu == busiest_cpu:CPU是繁忙CPU本身!cpu_and_siblings_are_idle(cpu):CPU及其附近CPU不空閒
- 如果滿足排除條件,標記為已訪問並繼續查找
- 找到合適的CPU後,設置
target_cpu並跳出循環
目標CPU檢查
if (target_cpu == -1)
goto next_group; /* failed to find a suitable target cpu in this domain */
- 如果沒有找到合適的目標CPU,跳轉到下一個CPU組
目標運行隊列獲取和驗證
target_rq = cpu_rq(target_cpu);
BUG_ON(busiest_rq == target_rq);
target_rq = cpu_rq(target_cpu):獲取目標CPU的運行隊列BUG_ON(busiest_rq == target_rq):如果繁忙隊列和目標隊列相同,觸發內核BUG
任務遷移執行
/* move a task from busiest_rq to target_rq */
double_lock_balance(busiest_rq, target_rq);
if (move_tasks(target_rq, target_cpu, busiest_rq, 1, sd, SCHED_IDLE)) {
schedstat_inc(busiest_rq, alb_lost);
schedstat_inc(target_rq, alb_gained);
} else {
schedstat_inc(busiest_rq, alb_failed);
}
spin_unlock(&target_rq->lock);
double_lock_balance(busiest_rq, target_rq):同時鎖定兩個運行隊列move_tasks(target_rq, target_cpu, busiest_rq, 1, sd, SCHED_IDLE):嘗試移動任務
- 參數説明:目標隊列、目標CPU、源隊列、移動數量、調度域、優先級
- 統計更新:
- 成功:增加
alb_lost(源隊列失去任務)和alb_gained(目標隊列獲得任務) - 失敗:增加
alb_failed(遷移失敗)
spin_unlock(&target_rq->lock):釋放目標運行隊列鎖
繼續處理下一個CPU組
next_group:
cpu_group = cpu_group->next;
} while (cpu_group != sd->groups && busiest_rq->nr_running > 1);
}
next_group標籤:下一個CPU組的入口點cpu_group = cpu_group->next:移動到下一個CPU組- 循環條件:不是初始組且繁忙隊列仍有多個任務
關鍵機制詳解
CPU選擇條件
理想目標CPU的特徵:
- 還未被遍歷訪問過
- 不是繁忙CPU自身
- CPU及其兄弟核心都空閒
- 在當前的調度組內
統計信息説明
alb_cnt:主動負載均衡觸發次數alb_lost/alb_gained:成功遷移的任務數alb_failed:遷移失敗次數
函數功能總結
主要功能:在多核系統中執行主動負載均衡,將任務從繁忙CPU遷移到空閒CPU
- 層次化搜索:從近到遠在調度域層次中尋找目標CPU
- 智能目標選擇:優先選擇完全空閒的CPU核心
- 條件遷移:只在明顯不平衡時才執行遷移
- 統計監控:跟蹤負載均衡的效果和頻率
從繁忙運行隊列中遷移任務到當前運行隊列move_tasks
static int move_tasks(runqueue_t *this_rq, int this_cpu, runqueue_t *busiest,
unsigned long max_nr_move, struct sched_domain *sd,
enum idle_type idle)
{
prio_array_t *array, *dst_array;
struct list_head *head, *curr;
int idx, pulled = 0;
task_t *tmp;
if (max_nr_move <= 0 || busiest->nr_running <= 1)
goto out;
/*
* We first consider expired tasks. Those will likely not be
* executed in the near future, and they are most likely to
* be cache-cold, thus switching CPUs has the least effect
* on them.
*/
if (busiest->expired->nr_active) {
array = busiest->expired;
dst_array = this_rq->expired;
} else {
array = busiest->active;
dst_array = this_rq->active;
}
new_array:
/* Start searching at priority 0: */
idx = 0;
skip_bitmap:
if (!idx)
idx = sched_find_first_bit(array->bitmap);
else
idx = find_next_bit(array->bitmap, MAX_PRIO, idx);
if (idx >= MAX_PRIO) {
if (array == busiest->expired && busiest->active->nr_active) {
array = busiest->active;
dst_array = this_rq->active;
goto new_array;
}
goto out;
}
head = array->queue + idx;
curr = head->prev;
skip_queue:
tmp = list_entry(curr, task_t, run_list);
curr = curr->prev;
if (!can_migrate_task(tmp, busiest, this_cpu, sd, idle)) {
if (curr != head)
goto skip_queue;
idx++;
goto skip_bitmap;
}
/*
* Right now, this is the only place pull_task() is called,
* so we can safely collect pull_task() stats here rather than
* inside pull_task().
*/
schedstat_inc(this_rq, pt_gained[idle]);
schedstat_inc(busiest, pt_lost[idle]);
pull_task(busiest, array, tmp, this_rq, dst_array, this_cpu);
pulled++;
/* We only want to steal up to the prescribed number of tasks. */
if (pulled < max_nr_move) {
if (curr != head)
goto skip_queue;
idx++;
goto skip_bitmap;
}
out:
return pulled;
}
函數功能概述
move_tasks 函數是負載均衡的核心實現,負責從繁忙運行隊列中遷移任務到當前運行隊列,採用智能的任務選擇策略
代碼逐段解析
變量聲明和基礎檢查
prio_array_t *array, *dst_array;
struct list_head *head, *curr;
int idx, pulled = 0;
task_t *tmp;
if (max_nr_move <= 0 || busiest->nr_running <= 1)
goto out;
prio_array_t *array, *dst_array:源和目標優先級數組指針struct list_head *head, *curr:鏈表頭和當前節點指針int idx, pulled = 0:優先級索引和已遷移任務計數task_t *tmp:臨時任務指針- 基礎檢查:如果要遷移數量≤0或繁忙隊列任務數≤1,直接跳轉到結束
選擇源任務數組
if (busiest->expired->nr_active) {
array = busiest->expired;
dst_array = this_rq->expired;
} else {
array = busiest->active;
dst_array = this_rq->active;
}
- 首先考慮過期任務。這些任務在近期不太可能被執行,而且很可能是緩存冷的,因此切換CPU對它們的影響最小
- 優先選擇過期任務數組(如果其中有活動任務)
- 否則選擇活動任務數組
新數組處理標籤
new_array:
/* Start searching at priority 0: */
idx = 0;
new_array標籤:開始處理新數組的入口點idx = 0:從優先級0開始搜索
位圖搜索循環
skip_bitmap:
if (!idx)
idx = sched_find_first_bit(array->bitmap);
else
idx = find_next_bit(array->bitmap, MAX_PRIO, idx);
if (idx >= MAX_PRIO) {
if (array == busiest->expired && busiest->active->nr_active) {
array = busiest->active;
dst_array = this_rq->active;
goto new_array;
}
goto out;
}
skip_bitmap標籤:為後續切換活動數組後或者跳過當前優先級後繼續搜索- 如果是第一次搜索(
idx=0),找到第一個設置位,即有任務的優先級數組 - 否則找到下一個設置位
- 如果搜索完所有優先級(
idx ≥ MAX_PRIO):
- 如果當前是過期數組且活動數組有任務,切換到活動數組重新開始
- 否則跳轉到結束
獲取任務鏈表
head = array->queue + idx;
curr = head->prev;
head = array->queue + idx:獲取當前優先級對應的任務鏈表頭curr = head->prev:從鏈表尾部開始,先處理最近加入的任務
任務遷移檢查
skip_queue:
tmp = list_entry(curr, task_t, run_list);
curr = curr->prev;
if (!can_migrate_task(tmp, busiest, this_cpu, sd, idle)) {
if (curr != head)
goto skip_queue;
idx++;
goto skip_bitmap;
}
skip_queue標籤:跳過當前任務繼續搜索tmp = list_entry(curr, task_t, run_list):從鏈表節點獲取任務結構curr = curr->prev:移動到前一個節點can_migrate_task檢查任務是否可以遷移:
- 如果不可遷移,且還有任務,繼續當前鏈表
- 如果鏈表遍歷完,移動到下一個優先級
遷移任務和統計更新
schedstat_inc(this_rq, pt_gained[idle]);
schedstat_inc(busiest, pt_lost[idle]);
pull_task(busiest, array, tmp, this_rq, dst_array, this_cpu);
pulled++;
- 更新統計:目標隊列獲得任務,源隊列失去任務
pull_task執行實際的任務遷移pulled++增加已遷移任務計數
繼續遷移檢查
/* We only want to steal up to the prescribed number of tasks. */
if (pulled < max_nr_move) {
if (curr != head)
goto skip_queue;
idx++;
goto skip_bitmap;
}
- 如果還未達到最大遷移數量:
- 當前鏈表還有任務,繼續處理
- 否則移動到下一個優先級
返回結果
out:
return pulled;
out標籤:函數結束點- 返回實際遷移的任務數量
關鍵機制詳解
優先級數組選擇策略
// 優先遷移過期任務的原因:
// 1. 近期不會執行 - 遷移對性能影響小
// 2. 緩存冷的可能性大 - 緩存失效代價低
任務搜索算法
- 按優先級從高到低(位圖搜索)
- 每個優先級內從新到舊(鏈表尾部到頭部)
- 先過期數組後活動數組
統計信息説明
pt_gained[idle]:根據空閒類型統計獲得的任務數pt_lost[idle]:根據空閒類型統計失去的任務數idle參數影響遷移策略的激進程度
函數功能總結
主要功能:從繁忙運行隊列中智能選擇並遷移任務到當前運行隊列
遷移優先級順序:
高優先級過期任務 → 低優先級過期任務 → 高優先級活動任務 → 低優先級活動任務
判斷一個任務是否可以從當前CPU遷移到目標CPUcan_migrate_task
static inline
int can_migrate_task(task_t *p, runqueue_t *rq, int this_cpu,
struct sched_domain *sd, enum idle_type idle)
{
/*
* We do not migrate tasks that are:
* 1) running (obviously), or
* 2) cannot be migrated to this CPU due to cpus_allowed, or
* 3) are cache-hot on their current CPU.
*/
if (task_running(rq, p))
return 0;
if (!cpu_isset(this_cpu, p->cpus_allowed))
return 0;
/* Aggressive migration if we've failed balancing */
if (idle == NEWLY_IDLE ||
sd->nr_balance_failed < sd->cache_nice_tries) {
if (task_hot(p, rq->timestamp_last_tick, sd))
return 0;
}
return 1;
}
函數功能概述
can_migrate_task 函數用於判斷一個任務是否可以從當前CPU遷移到目標CPU,考慮運行狀態、CPU親和性、緩存熱度等多種因素
代碼逐段解析
函數聲明和註釋説明
static inline
int can_migrate_task(task_t *p, runqueue_t *rq, int this_cpu,
struct sched_domain *sd, enum idle_type idle)
{
/*
* We do not migrate tasks that are:
* 1) running (obviously), or
* 2) cannot be migrated to this CPU due to cpus_allowed, or
* 3) are cache-hot on their current CPU.
*/
static inline:內聯函數,減少函數調用開銷- 參數説明:
p:要檢查的任務rq:任務當前所在的運行隊列this_cpu:目標CPU編號sd:調度域idle:目標CPU的空閒類型
- 註釋説明:我們不遷移以下任務:
- 正在運行的(顯然)
- 由於
cpus_allowed限制不能遷移到該CPU的 - 在當前CPU上緩存熱的
運行狀態檢查
if (task_running(rq, p))
return 0;
task_running(rq, p):檢查任務是否正在CPU上運行- 如果任務正在運行,返回0(不能遷移)
- 原因:不能遷移正在執行的任務,需要等待其被調度出去
CPU親和性檢查
if (!cpu_isset(this_cpu, p->cpus_allowed))
return 0;
!cpu_isset(this_cpu, p->cpus_allowed):檢查目標CPU是否在任務的允許CPU集合中- 如果不在允許集合中,返回0(不能遷移)
- 原因:遵守任務的CPU親和性設置
緩存熱度檢查條件
/* Aggressive migration if we've failed balancing */
if (idle == NEWLY_IDLE ||
sd->nr_balance_failed < sd->cache_nice_tries) {
當發起負載均衡但未成功時nr_balance_failed計數會增長,當進行緩存查詢成功時cache_nice_tries計數會增長,如果負載均衡失敗次數大於緩存命中次數,説明保證緩存的親和的代價太高,可以跳過緩存熱度檢查
idle == NEWLY_IDLE:目標CPU是新近空閒的sd->nr_balance_failed < sd->cache_nice_tries:調度域負載均衡失敗次數小於緩存友好嘗試次數- 這個條件決定是否進行緩存熱度檢查
緩存熱度檢查
if (task_hot(p, rq->timestamp_last_tick, sd))
return 0;
}
task_hot(p, rq->timestamp_last_tick, sd):檢查任務是否是緩存熱的
#define task_hot(p, now, sd) ((long long) ((now) - (p)->last_ran) \
< (long long) (sd)->cache_hot_time)
- 檢查進程最後在CPU運行的時間到現在過去的時間是否超過該調度域的緩存熱度時間
- 如果任務緩存熱,返回0(不能遷移)
- 原因:避免遷移緩存熱任務導致的性能損失
允許遷移
return 1;
}
- 如果通過所有檢查,返回1(可以遷移)
函數功能總結
主要功能:智能判斷任務是否適合遷移,平衡負載均衡和性能保護
- 基礎安全性:
- 運行狀態:不遷移正在執行的任務
- CPU親和性:遵守任務的位置限制
- 性能優化:
- 緩存熱度:避免遷移熱任務導致性能損失
- 條件檢查:根據系統狀態動態調整策略
將任務從源運行隊列移動到目標運行隊列pull_task
static inline
void pull_task(runqueue_t *src_rq, prio_array_t *src_array, task_t *p,
runqueue_t *this_rq, prio_array_t *this_array, int this_cpu)
{
dequeue_task(p, src_array);
src_rq->nr_running--;
set_task_cpu(p, this_cpu);
this_rq->nr_running++;
enqueue_task(p, this_array);
p->timestamp = (p->timestamp - src_rq->timestamp_last_tick)
+ this_rq->timestamp_last_tick;
/*
* Note that idle threads have a prio of MAX_PRIO, for this test
* to be always true for them.
*/
if (TASK_PREEMPTS_CURR(p, this_rq))
resched_task(this_rq->curr);
}
函數功能概述
pull_task 函數是實際執行任務遷移的核心函數,負責將任務從源運行隊列移動到目標運行隊列,並更新所有相關的調度器數據結構
代碼逐段解析
函數聲明
static inline
void pull_task(runqueue_t *src_rq, prio_array_t *src_array, task_t *p,
runqueue_t *this_rq, prio_array_t *this_array, int this_cpu)
static inline:內聯函數,減少函數調用開銷- 參數説明:
src_rq:源運行隊列(任務當前所在的隊列)src_array:源優先級數組(任務當前所在的數組)p:要遷移的任務this_rq:目標運行隊列(任務要遷移到的隊列)this_array:目標優先級數組this_cpu:目標CPU編號
從源隊列移除任務
dequeue_task(p, src_array);
src_rq->nr_running--;
dequeue_task(p, src_array):將任務從源優先級數組中移除
- 這包括從對應的優先級鏈表中刪除任務
- 如果該優先級鏈表沒有任務,清除優先級位圖中的相應位
src_rq->nr_running--:減少源運行隊列的任務計數
更新任務CPU和增加目標隊列計數
set_task_cpu(p, this_cpu);
this_rq->nr_running++;
set_task_cpu(p, this_cpu):設置任務的CPU字段為目標CPU
- 更新
p->cpu = this_cpu
this_rq->nr_running++:增加目標運行隊列的任務計數
將任務加入目標隊列
enqueue_task(p, this_array);
enqueue_task(p, this_array):將任務加入到目標優先級數組中
- 根據任務插入到對應的優先級鏈表
- 設置優先級位圖中的相應位
時間戳同步
p->timestamp = (p->timestamp - src_rq->timestamp_last_tick)
+ this_rq->timestamp_last_tick;
- 同步任務的時間戳到目標運行隊列的時間基準
- 計算原理:
p->timestamp - src_rq->timestamp_last_tick:計算相對於源隊列基準的相對時間+ this_rq->timestamp_last_tick:轉換到目標隊列的時間基準
搶佔檢查和重調度
if (TASK_PREEMPTS_CURR(p, this_rq))
resched_task(this_rq->curr);
TASK_PREEMPTS_CURR(p, this_rq):檢查遷移過來的任務是否應該搶佔目標CPU上當前運行的任務resched_task(this_rq->curr):如果需要搶佔,設置目標CPU當前任務的重調度標誌
- 這會在下次調度時機觸發任務切換
函數功能總結
主要功能:將任務從一個運行隊列遷移到另一個運行隊列,維護所有調度器數據結構的完整性
- 隊列管理:
- 從源隊列正確移除任務
- 向目標隊列正確添加任務
- 更新運行任務計數器
- 狀態更新:
- 更新任務的CPU歸屬
- 同步時間戳基準
- 維護優先級位圖
- 調度決策:
- 檢查是否需要立即搶佔
- 觸發重調度以響應優先級變化