當linux啟動時,最先會通過彙編代碼進行硬體和CPU的初始化,最後會跳轉到C代碼,而最初跳轉到的C代碼入口為
/* 代碼位址: linux/init/Main.c */
asmlinkage __visible void __init start_kernel(void)
在start_kerenl函數中,進行了系統啟動過程中幾乎所有重要的初始化(有一部分在boot中初始化,有一部分在start_kernel之前的彙編代碼進行初始化),包括記憶體、頁表、必要資料結構、信号、排程器、硬體裝置等。而這些初始化是由誰來負責的?就是由init_task這個程序。init_task是靜态定義的一個程序,也就是說當核心被放入記憶體時,它就已經存在,它沒有自己的使用者空間,一直處于核心空間中運作,并且也隻處于核心空間運作。當它執行到最後,将start_kernel中所有的初始化執行完成後,會在核心中啟動一個kernel_init核心線程和一個kthreadd核心線程,kernel_init核心線程執行到最後會通過execve系統調用執行轉變為我們所熟悉的init程序,而kthreadd核心線程是核心用于管理排程其他的核心線程的守護線程。在最後init_task将變成一個idle程序,用于在CPU沒有程序運作時運作它,它在此時僅僅用于空轉。
在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
#ifdef CONFIG_CPUMASK_OFFSTACK
alloc_size += num_possible_cpus() * cpumask_size();
if (alloc_size) {
/* 配置設定記憶體 */
ptr = (unsigned long)kzalloc(alloc_size, GFP_NOWAIT);
/* 設定 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;
#endif /* CONFIG_FAIR_GROUP_SCHED */
/* 設定 root_task_group 每個CPU上的實時排程實體指針se */
root_task_group.rt_se = (struct sched_rt_entity **)ptr;
/* 設定 root_task_group 每個CPU上的實時運作隊列指針rt_rq */
root_task_group.rt_rq = (struct rt_rq **)ptr;
#endif /* CONFIG_RT_GROUP_SCHED */
for_each_possible_cpu(i) {
per_cpu(load_balance_mask, i) = (void *)ptr;
ptr += cpumask_size();
}
#endif /* CONFIG_CPUMASK_OFFSTACK */
}
/* 初始化實時程序的帶寬限制,用于設定實時程序在CPU中所占用比的 */
init_rt_bandwidth(&def_rt_bandwidth,
global_rt_period(), global_rt_runtime());
init_dl_bandwidth(&def_dl_bandwidth,
#ifdef CONFIG_SMP
/* 初始化預設的排程域,排程域包含一個或多個CPU,負載均衡是在排程域内執行的,互相之間隔離 */
init_defrootdomain();
init_rt_bandwidth(&root_task_group.rt_bandwidth,
#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);
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);
rq->rt.rt_runtime = def_rt_bandwidth.rt_runtime;
init_tg_rt_entry(&root_task_group, &rq->rt, NULL, i, NULL);
/* 初始化該隊列所儲存的每個CPU的負載情況 */
for (j = 0; j CPU_LOAD_IDX_MAX; j++)
rq->cpu_load[j] = 0;
/* 該隊列最後一次更新cpu_load的時間值為目前 */
rq->last_load_update_tick = jiffies;
/* 這些參數都是負載均衡使用的 */
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;
#ifdef CONFIG_NO_HZ_FULL
/* 也是動态時鐘才使用的标志位,用于儲存上次排程tick發生時間 */
rq->last_sched_tick = 0;
/* 初始化運作隊列定時器,這個是高精度定時器,但是隻是初始化,這時并沒有使用 */
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);
/*
* 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;
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();
init_sched_fair_class();
/* 這裡隻是标記排程器開始運作了,但是此時系統隻有一個init_task(idle)程序,并且定時器都還沒啟動。并不會排程到其他程序,也沒有其他程序可供排程 */
scheduler_running = 1;
}
排程器的初始化還是比較簡單的,畢竟排程器的核心不在此,重頭戲在它的運作時處理,之後的文章會詳細分析排程器的運作時處理。