每個CPU(多核COU中的每個核)都有自己的struct rq隊列,而rq隊列中又有着自己的struct cfs_rq和struct rt_rq。在初始化時就是對這三個結構進行初始化。
1 init_task和init進程
在start_kernel函數中,進行了系統啓動過程中幾乎所有重要的初始化(有一部分在boot中初始化,有一部分在start_kernel之前的彙編代碼進行初始化),包括內存、頁表、必要數據結構、信號、調度器、硬件設備等。而這些初始化是由誰來負責的?就是由init_task這個進程。init_task是靜態定義的一個進程,也就是說當內核被放入內存時,它就已經存在,它沒有自己的用戶空間,一直處於內核空間中運行,並且也只處於內核空間運行。當它執行到最後,將start_kernel中所有的初始化執行完成後,會在內核中啓動一個kernel_init內核線程和一個kthreadd內核線程,kernel_init內核線程執行到最後會通過execve系統調用執行轉變爲我們所熟悉的init進程,而kthreadd內核線程是內核用於管理調度其他的內核線程的守護線程。在最後init_task將變成一個idle進程,用於在CPU沒有進程運行時運行它,它在此時僅僅用於空轉。
2 sched_init
在start_kernel中對調度器進行初始化的函數就是sched_init,其主要工作爲
- 對相關數據結構分配內存
- 初始化root_task_group
- 初始化每個CPU的rq隊列(包括其中的cfs隊列和實時進程隊列)
- 將init_task進程轉變爲idle進程
需要說明的是init_task在這裏會被轉變爲idle進程,但是它還會繼續執行初始化工作,相當於這裏只是給init_task掛個idle進程的名號,它其實還是init_task進程,只有到最後init_task進程開啓了kernel_init和kthreadd進程之後,才轉變爲真正意義上的idle進程。
/* 代碼路徑: 內核源代碼目錄/kernel/sched/Core.c */
/* 執行到此時內核只有一個進程init_task,current就爲init_task。之後的init進程在初始化到最後的rest_init中啓動 */
void __init sched_init(void)
{
int i, j;
unsigned long alloc_size = 0, ptr;
/* 計算所需要分配的數據結構空間 */
#ifdef CONFIG_FAIR_GROUP_SCHED
alloc_size += 2 * nr_cpu_ids * sizeof(void **);
#endif
#ifdef CONFIG_RT_GROUP_SCHED
alloc_size += 2 * nr_cpu_ids * sizeof(void **);
#endif
#ifdef CONFIG_CPUMASK_OFFSTACK
alloc_size += num_possible_cpus() * cpumask_size();
#endif
if (alloc_size) {
/* 分配內存 */
ptr = (unsigned long)kzalloc(alloc_size, GFP_NOWAIT);
#ifdef CONFIG_FAIR_GROUP_SCHED
/* 設置 root_task_group 每個CPU上的調度實體指針se */
root_task_group.se = (struct sched_entity **)ptr;
ptr += nr_cpu_ids * sizeof(void **);
/* 設置 root_task_group 每個CPU上的CFS運行隊列指針cfs_rq */
root_task_group.cfs_rq = (struct cfs_rq **)ptr;
ptr += nr_cpu_ids * sizeof(void **);
#endif /* CONFIG_FAIR_GROUP_SCHED */
#ifdef CONFIG_RT_GROUP_SCHED
/* 設置 root_task_group 每個CPU上的實時調度實體指針se */
root_task_group.rt_se = (struct sched_rt_entity **)ptr;
ptr += nr_cpu_ids * sizeof(void **);
/* 設置 root_task_group 每個CPU上的實時運行隊列指針rt_rq */
root_task_group.rt_rq = (struct rt_rq **)ptr;
ptr += nr_cpu_ids * sizeof(void **);
#endif /* CONFIG_RT_GROUP_SCHED */
#ifdef CONFIG_CPUMASK_OFFSTACK
for_each_possible_cpu(i) {
per_cpu(load_balance_mask, i) = (void *)ptr;
ptr += cpumask_size();
}
#endif /* CONFIG_CPUMASK_OFFSTACK */
}
/* 初始化實時進程的帶寬限制,用於設置實時進程在CPU中所佔用比的 */
-------------------------------------------------------------------(1)
init_rt_bandwidth(&def_rt_bandwidth,
global_rt_period(), global_rt_runtime());
init_dl_bandwidth(&def_dl_bandwidth,
global_rt_period(), global_rt_runtime());
#ifdef CONFIG_SMP
/* 初始化默認的調度域,調度域包含一個或多個CPU,負載均衡是在調度域內執行的,相互之間隔離 */
init_defrootdomain();
#endif
#ifdef CONFIG_RT_GROUP_SCHED
/* 初始化實時進程的帶寬限制,用於設置實時進程在CPU中所佔用比的 */
init_rt_bandwidth(&root_task_group.rt_bandwidth,
global_rt_period(), global_rt_runtime());
#endif /* CONFIG_RT_GROUP_SCHED */
#ifdef CONFIG_CGROUP_SCHED
/* 將分配好空間的 root_task_group 加入 task_groups 鏈表 */
list_add(&root_task_group.list, &task_groups);
INIT_LIST_HEAD(&root_task_group.children);
INIT_LIST_HEAD(&root_task_group.siblings);
/* 自動分組初始化,每個tty(控制檯)動態的創建進程組,這樣就可以降低高負載情況下的桌面延遲 */
autogroup_init(&init_task);
#endif /* CONFIG_CGROUP_SCHED */
/* 遍歷設置每一個CPU */
for_each_possible_cpu(i) {
struct rq *rq;
/* 獲取CPUi的rq隊列 */
rq = cpu_rq(i);
/* 初始化rq隊列的自旋鎖 */
raw_spin_lock_init(&rq->lock);
/* CPU運行隊列中調度實體(sched_entity)數量爲0 */
rq->nr_running = 0;
/* CPU負載 */
rq->calc_load_active = 0;
/* 負載下次更新時間 */
rq->calc_load_update = jiffies + LOAD_FREQ;
/* 初始化CFS運行隊列 */
init_cfs_rq(&rq->cfs);
/* 初始化實時進程運行隊列 */
init_rt_rq(&rq->rt, rq);
init_dl_rq(&rq->dl, rq);
#ifdef CONFIG_FAIR_GROUP_SCHED
root_task_group.shares = ROOT_TASK_GROUP_LOAD;
INIT_LIST_HEAD(&rq->leaf_cfs_rq_list);
/*
* How much cpu bandwidth does root_task_group get?
*
* In case of task-groups formed thr' the cgroup filesystem, it
* gets 100% of the cpu resources in the system. This overall
* system cpu resource is divided among the tasks of
* root_task_group and its child task-groups in a fair manner,
* based on each entity's (task or task-group's) weight
* (se->load.weight).
*
* In other words, if root_task_group has 10 tasks of weight
* 1024) and two child groups A0 and A1 (of weight 1024 each),
* then A0's share of the cpu resource is:
*
* A0's bandwidth = 1024 / (10*1024 + 1024 + 1024) = 8.33%
*
* We achieve this by letting root_task_group's tasks sit
* directly in rq->cfs (i.e root_task_group->se[] = NULL).
*/
/* 初始化CFS的帶寬限制,用於設置普通進程在CPU中所佔用比的 */
init_cfs_bandwidth(&root_task_group.cfs_bandwidth);
init_tg_cfs_entry(&root_task_group, &rq->cfs, NULL, i, NULL);
#endif /* CONFIG_FAIR_GROUP_SCHED */
//設置rt隊列的每個調度週期的最大運行時間
rq->rt.rt_runtime = def_rt_bandwidth.rt_runtime;
#ifdef CONFIG_RT_GROUP_SCHED
init_tg_rt_entry(&root_task_group, &rq->rt, NULL, i, NULL);
#endif
/* 初始化該隊列所保存的每個CPU的負載情況 */
for (j = 0; j < CPU_LOAD_IDX_MAX; j++)
rq->cpu_load[j] = 0;
/* 該隊列最後一次更新cpu_load的時間值爲當前 */
rq->last_load_update_tick = jiffies;
#ifdef CONFIG_SMP
/* 這些參數都是負載均衡使用的 */
rq->sd = NULL;
rq->rd = NULL;
rq->cpu_capacity = SCHED_CAPACITY_SCALE;
rq->post_schedule = 0;
rq->active_balance = 0;
rq->next_balance = jiffies;
rq->push_cpu = 0;
rq->cpu = i;
rq->online = 0;
rq->idle_stamp = 0;
rq->avg_idle = 2*sysctl_sched_migration_cost;
rq->max_idle_balance_cost = sysctl_sched_migration_cost;
INIT_LIST_HEAD(&rq->cfs_tasks);
/* 將CPU運行隊列加入到默認調度域中 */
rq_attach_root(rq, &def_root_domain);
#ifdef CONFIG_NO_HZ_COMMON
/* 動態時鐘使用的標誌位,初始時動態時鐘是不使用的 */
rq->nohz_flags = 0;
#endif
#ifdef CONFIG_NO_HZ_FULL
/* 也是動態時鐘才使用的標誌位,用於保存上次調度tick發生時間 */
rq->last_sched_tick = 0;
#endif
#endif
/* 初始化運行隊列定時器,這個是高精度定時器,但是隻是初始化,這時並沒有使用 */
init_rq_hrtick(rq);
atomic_set(&rq->nr_iowait, 0);
}
/* 設置 init_task 進程的權重 */
set_load_weight(&init_task);
#ifdef CONFIG_PREEMPT_NOTIFIERS
/* 初始化通知鏈 */
INIT_HLIST_HEAD(&init_task.preempt_notifiers);
#endif
/*
* The boot idle thread does lazy MMU switching as well:
*/
atomic_inc(&init_mm.mm_count);
enter_lazy_tlb(&init_mm, current);
/*
* Make us the idle thread. Technically, schedule() should not be
* called from this thread, however somewhere below it might be,
* but because we are the idle thread, we just pick up running again
* when this runqueue becomes "idle".
*/
/* 將當前進程初始化爲idle進程,idle進程用於當CPU沒有進程可運行時運行,空轉 */
init_idle(current, smp_processor_id());
/* 下次負載更新時間(是一個相對時間) */
calc_load_update = jiffies + LOAD_FREQ;
/*
* During early bootup we pretend to be a normal task:
*/
/* 設置idle進程使用CFS調度策略 */
current->sched_class = &fair_sched_class;
#ifdef CONFIG_SMP
zalloc_cpumask_var(&sched_domains_tmpmask, GFP_NOWAIT);
/* May be allocated at isolcpus cmdline parse time */
if (cpu_isolated_map == NULL)
zalloc_cpumask_var(&cpu_isolated_map, GFP_NOWAIT);
idle_thread_set_boot_cpu();
set_cpu_rq_start_time();
#endif
init_sched_fair_class();
/* 這裏只是標記調度器開始運行了,但是此時系統只有一個init_task(idle)進程,並且定時器都還沒啓動。並不會調度到其他進程,也沒有其他進程可供調度 */
scheduler_running = 1;
}
step1:
初始化def_rt_bandwidth
在單cpu環境中,rt_bandwidth限制了cpu上的實時任務在rt_period週期內運行時間不能夠超過rt_runtime;而在SMP多cpu環境中rt_bandwidth限制了系統中實時任務在rt_period週期內的cpu佔用時間比例不能夠超過rt_runtime/rt_period。
int sysctl_sched_rt_runtime = 950000;
#define NSEC_PER_USEC 1000L
static inline u64 global_rt_period(void)
{
return (u64)sysctl_sched_rt_period * NSEC_PER_USEC;
}
static inline u64 global_rt_runtime(void)
{
if (sysctl_sched_rt_runtime < 0)
return RUNTIME_INF;
return (u64)sysctl_sched_rt_runtime * NSEC_PER_USEC;
}
void init_rt_bandwidth(struct rt_bandwidth *rt_b, u64 period, u64 runtime)
{
rt_b->rt_period = ns_to_ktime(period);
rt_b->rt_runtime = runtime; //初始化一個調度週期中的運行時間
raw_spin_lock_init(&rt_b->rt_runtime_lock);
hrtimer_init(&rt_b->rt_period_timer,
CLOCK_MONOTONIC, HRTIMER_MODE_REL);//該定時器和rt進程的週期調度相關
rt_b->rt_period_timer.function = sched_rt_period_timer;
}
如果一個隊列調度限制使能的情況下(即rt_rq->rt_throttled爲1),將無法得到調度運行的機會;但是任務不可能一直處於調度限制,因爲那樣的話任務就永遠得不到執行了。這個時候就需要一種檢查機制,一旦調度限制已經讓任務得到了應有的“懲罰”,就需要解除這個限制,讓它重獲自由。內核中在struct task_group結構rt_bandwidth的高精度時鐘rt_period_timer來實現此功能。