Linux作業系統支援SMP,並且同時會運行多個進程,這些進程是如何在多個CPU之間調度的,如何進行負載平衡。
進程在如下時機決定在哪個CPU運行
1.進程被喚醒的時候,比如進程收到網路資料包,或者完成磁碟IO操作
2.進程剛被fork出來
Linux通過select_task_rq函數決定進程在哪一個CPU上進行運行。該函數在./kernel/sched/core.c中實現。
int select_task_rq(struct task_struct *p, int cpu, int sd_flags, int wake_flags)
select_task_rq的調用時機:
wake_up_new_task: 進程剛被fork出來
try_to_wake_up:進程被喚醒的時候
select_task_rq函數在決定進程在哪個CPU上運行會考慮哪些因素。
static int select_task_rq_fair(struct task_struct *p, int prev_cpu, int sd_flag, int wake_flags)
{
int cpu = smp_processor_id();
for_each_domain(cpu, tmp) {
if (!(tmp->flags & SD_LOAD_BALANCE))
break; //如果調度域不支援SD_LOAD_BALANCE,直接退出。
/*
* If both cpu and prev_cpu are part of this domain,
* cpu is a valid SD_WAKE_AFFINE target.
*/
if (want_affine && (tmp->flags & SD_WAKE_AFFINE) &&
cpumask_test_cpu(prev_cpu, sched_domain_span(tmp))) {
affine_sd = tmp;
break;
} //找到prev_cpu和cpu共同所在的最近的調度域
if (tmp->flags & sd_flag)
sd = tmp; //根據sd_flag找到滿足sd_flag最大對應的調度域
else if (!want_affine)
break;
}
if (affine_sd) { //如果找到affine_sd,則使用當前cpu作為調度的CPU
sd = NULL; /* Prefer wake_affine over balance flags */
if (cpu != prev_cpu && wake_affine(affine_sd, p, sync))
new_cpu = cpu;
}
if (!sd) { //如果找不到滿足sd_flags的調度域,則找附近的空閑CPU。
if (sd_flag & SD_BALANCE_WAKE) /* XXX always ? */
new_cpu = select_idle_sibling(p, new_cpu);
} else while (sd) {
struct sched_group *group;
int weight;
if (!(sd->flags & sd_flag)) {
sd = sd->child;
continue;
}
//然後找sd中負載最小的cpu作為調度cpu
new_cpu = find_idlest_cpu(group, p, cpu);
}
}
wake_flags參數的含義:
/*
* wake flags
*/
#define WF_SYNC 0x01 /* waker goes to sleep after wakeup */
#define WF_FORK 0x02 /* child wakeup after fork */
#define WF_MIGRATED 0x4 /* internal use, task got migrated */
sd_flag的含義:
#define SD_LOAD_BALANCE 0x0001/* Do load balancing on this domain. */
#define SD_BALANCE_NEWIDLE 0x0002/* Balance when about to become idle */
#define SD_BALANCE_EXEC 0x0004/* Balance on exec */
#define SD_BALANCE_FORK 0x0008/* Balance on fork, clone */
#define SD_BALANCE_WAKE 0x0010 /* Balance on wakeup */
#define SD_WAKE_AFFINE 0x0020/* Wake task to waking CPU */
#define SD_PREFER_LOCAL 0x0040 /* Prefer to keep tasks local to this domain */
#define SD_SHARE_CPUPOWER 0x0080/* Domain members share cpu power */
#define SD_POWERSAVINGS_BALANCE 0x0100/* Balance for power savings */
#define SD_SHARE_PKG_RESOURCES 0x0200/* Domain members share cpu pkg resources */
#define SD_SERIALIZE 0x0400/* Only a single load balancing instance */
#define SD_ASYM_PACKING 0x0800 /* Place busy groups earlier in the domain */
#define SD_PREFER_SIBLING 0x1000/* Prefer to place tasks in a sibling domain */
SD_LOAD_BALANCE的含義:如果對應的調度域支援SD_LOAD_BALANCE,則進行重新選擇運行隊列。
SD_SHARE_PKG_RESOURCES:select_idle_sibling在判斷空閑core的時候,是否尋找所在doman的其他cpu是否空閑。
select_task_rq_fair需要gdb調試一下,看看domain的組織圖,尋找空閑cpu的時候什麼時間停止。
除了進程在喚醒或者剛fork完成,還有哪些機制會導致進程運行在哪個CPU上。
如果進程只有有喚醒的時候決定在哪個CPU上,難免會導致CPU負荷不均,僅此有必要進行負載平衡。
負載平衡的調用時機:
1.周期性的調用,保證負載平衡
open_softirq(SCHED_SOFTIRQ, run_rebalance_domains);
trigger_load_balance<-scheduler_tick<-周期性的時鐘中斷。
在每一個CPU上通過周期性的定時中斷進行觸發調用。
負載平衡有兩種,一種是在CPU空閑觸發,一種是CPU忙時觸發。
rq->idle_balance = idle_cpu(cpu);
trigger_load_balance(rq);
作為參數傳遞給負載平衡函數。
static void rebalance_domains(struct rq *rq, enum cpu_idle_type idle)