linux調度器源碼分析 - 初始化(二)


本文為原創,轉載請注明:http://www.cnblogs.com/tolimit/

 

引言

  上期文章linux調度器源碼分析 - 概述(一)已經把調度器相關的數據結構介紹了一遍,本篇着重通過代碼說明調度器在系統啟動初始化階段是如何初始化和工作的。通過上期文章我們知道,在多核CPU和SMP系統中,每個CPU(多核COU中的每個核)都有自己的struct rq隊列,而rq隊列中又有着自己的struct cfs_rq和struct rt_rq。在初始化時就是對這三個結構進行初始化。

 

init_task和init進程

  當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沒有進程運行時運行它,它在此時僅僅用於空轉。

 

 

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進程。

  1 /* 代碼路徑: 內核源代碼目錄/kernel/sched/Core.c */
  2 
  3 /* 執行到此時內核只有一個進程init_task,current就為init_task。之后的init進程在初始化到最后的rest_init中啟動 */
  4 void __init sched_init(void)
  5 {
  6     int i, j;
  7     unsigned long alloc_size = 0, ptr;
  8 
  9     /* 計算所需要分配的數據結構空間 */
 10 #ifdef CONFIG_FAIR_GROUP_SCHED    
 11     alloc_size += 2 * nr_cpu_ids * sizeof(void **);
 12 #endif
 13 
 14 #ifdef CONFIG_RT_GROUP_SCHED
 15     alloc_size += 2 * nr_cpu_ids * sizeof(void **);
 16 #endif
 17 #ifdef CONFIG_CPUMASK_OFFSTACK
 18     alloc_size += num_possible_cpus() * cpumask_size();
 19 #endif
 20     if (alloc_size) {
 21         /* 分配內存 */
 22         ptr = (unsigned long)kzalloc(alloc_size, GFP_NOWAIT);
 23 
 24 #ifdef CONFIG_FAIR_GROUP_SCHED
 25         /* 設置 root_task_group 每個CPU上的調度實體指針se */
 26         root_task_group.se = (struct sched_entity **)ptr;
 27         ptr += nr_cpu_ids * sizeof(void **);
 28 
 29         /* 設置 root_task_group 每個CPU上的CFS運行隊列指針cfs_rq */
 30         root_task_group.cfs_rq = (struct cfs_rq **)ptr;
 31         ptr += nr_cpu_ids * sizeof(void **);
 32 
 33 #endif /* CONFIG_FAIR_GROUP_SCHED */
 34 #ifdef CONFIG_RT_GROUP_SCHED
 35         /* 設置 root_task_group 每個CPU上的實時調度實體指針se */
 36         root_task_group.rt_se = (struct sched_rt_entity **)ptr;
 37         ptr += nr_cpu_ids * sizeof(void **);
 38 
 39         /* 設置 root_task_group 每個CPU上的實時運行隊列指針rt_rq */
 40         root_task_group.rt_rq = (struct rt_rq **)ptr;
 41         ptr += nr_cpu_ids * sizeof(void **);
 42 
 43 #endif /* CONFIG_RT_GROUP_SCHED */
 44 #ifdef CONFIG_CPUMASK_OFFSTACK
 45         for_each_possible_cpu(i) {
 46             per_cpu(load_balance_mask, i) = (void *)ptr;
 47             ptr += cpumask_size();
 48         }
 49 #endif /* CONFIG_CPUMASK_OFFSTACK */
 50     }
 51     /* 初始化實時進程的帶寬限制,用於設置實時進程在CPU中所占用比的 */
 52     init_rt_bandwidth(&def_rt_bandwidth,
 53             global_rt_period(), global_rt_runtime());
 54     init_dl_bandwidth(&def_dl_bandwidth,
 55             global_rt_period(), global_rt_runtime());
 56 
 57 #ifdef CONFIG_SMP
 58     /* 初始化默認的調度域,調度域包含一個或多個CPU,負載均衡是在調度域內執行的,相互之間隔離 */
 59     init_defrootdomain();
 60 #endif
 61 
 62 #ifdef CONFIG_RT_GROUP_SCHED
 63     /* 初始化實時進程的帶寬限制,用於設置實時進程在CPU中所占用比的 */
 64     init_rt_bandwidth(&root_task_group.rt_bandwidth,
 65             global_rt_period(), global_rt_runtime());
 66 #endif /* CONFIG_RT_GROUP_SCHED */
 67 
 68 #ifdef CONFIG_CGROUP_SCHED
 69     /* 將分配好空間的 root_task_group 加入 task_groups 鏈表 */
 70     list_add(&root_task_group.list, &task_groups);
 71     INIT_LIST_HEAD(&root_task_group.children);
 72     INIT_LIST_HEAD(&root_task_group.siblings);
 73     /* 自動分組初始化,每個tty(控制台)動態的創建進程組,這樣就可以降低高負載情況下的桌面延遲 */
 74     autogroup_init(&init_task);
 75 
 76 #endif /* CONFIG_CGROUP_SCHED */
 77     /* 遍歷設置每一個CPU */
 78     for_each_possible_cpu(i) {
 79         struct rq *rq;
 80         /* 獲取CPUi的rq隊列 */
 81         rq = cpu_rq(i);
 82         /* 初始化rq隊列的自旋鎖 */
 83         raw_spin_lock_init(&rq->lock);
 84         /* CPU運行隊列中調度實體(sched_entity)數量為0 */
 85         rq->nr_running = 0;
 86         /* CPU負載 */
 87         rq->calc_load_active = 0;
 88         /* 負載下次更新時間 */
 89         rq->calc_load_update = jiffies + LOAD_FREQ;
 90         /* 初始化CFS運行隊列 */
 91         init_cfs_rq(&rq->cfs);
 92         /* 初始化實時進程運行隊列 */
 93         init_rt_rq(&rq->rt, rq);
 94         init_dl_rq(&rq->dl, rq);
 95 #ifdef CONFIG_FAIR_GROUP_SCHED
 96         root_task_group.shares = ROOT_TASK_GROUP_LOAD;
 97         INIT_LIST_HEAD(&rq->leaf_cfs_rq_list);
 98         /*
 99          * How much cpu bandwidth does root_task_group get?
100          *
101          * In case of task-groups formed thr' the cgroup filesystem, it
102          * gets 100% of the cpu resources in the system. This overall
103          * system cpu resource is divided among the tasks of
104          * root_task_group and its child task-groups in a fair manner,
105          * based on each entity's (task or task-group's) weight
106          * (se->load.weight).
107          *
108          * In other words, if root_task_group has 10 tasks of weight
109          * 1024) and two child groups A0 and A1 (of weight 1024 each),
110          * then A0's share of the cpu resource is:
111          *
112          *    A0's bandwidth = 1024 / (10*1024 + 1024 + 1024) = 8.33%
113          *
114          * We achieve this by letting root_task_group's tasks sit
115          * directly in rq->cfs (i.e root_task_group->se[] = NULL).
116          */
117         /* 初始化CFS的帶寬限制,用於設置普通進程在CPU中所占用比的 */
118         init_cfs_bandwidth(&root_task_group.cfs_bandwidth);
119         init_tg_cfs_entry(&root_task_group, &rq->cfs, NULL, i, NULL);
120 #endif /* CONFIG_FAIR_GROUP_SCHED */
121 
122         rq->rt.rt_runtime = def_rt_bandwidth.rt_runtime;
123 #ifdef CONFIG_RT_GROUP_SCHED
124         init_tg_rt_entry(&root_task_group, &rq->rt, NULL, i, NULL);
125 #endif
126         /* 初始化該隊列所保存的每個CPU的負載情況 */
127         for (j = 0; j < CPU_LOAD_IDX_MAX; j++)
128             rq->cpu_load[j] = 0;
129         /* 該隊列最后一次更新cpu_load的時間值為當前 */
130         rq->last_load_update_tick = jiffies;
131 
132 #ifdef CONFIG_SMP
133         /* 這些參數都是負載均衡使用的 */
134         rq->sd = NULL;
135         rq->rd = NULL;
136         rq->cpu_capacity = SCHED_CAPACITY_SCALE;
137         rq->post_schedule = 0;
138         rq->active_balance = 0;
139         rq->next_balance = jiffies;
140         rq->push_cpu = 0;
141         rq->cpu = i;
142         rq->online = 0;
143         rq->idle_stamp = 0;
144         rq->avg_idle = 2*sysctl_sched_migration_cost;
145         rq->max_idle_balance_cost = sysctl_sched_migration_cost;
146 
147         INIT_LIST_HEAD(&rq->cfs_tasks);
148         /* 將CPU運行隊列加入到默認調度域中 */
149         rq_attach_root(rq, &def_root_domain);
150 #ifdef CONFIG_NO_HZ_COMMON
151         /* 動態時鍾使用的標志位,初始時動態時鍾是不使用的 */
152         rq->nohz_flags = 0;
153 #endif
154 #ifdef CONFIG_NO_HZ_FULL
155         /* 也是動態時鍾才使用的標志位,用於保存上次調度tick發生時間 */
156         rq->last_sched_tick = 0;
157 #endif
158 #endif
159         /* 初始化運行隊列定時器,這個是高精度定時器,但是只是初始化,這時並沒有使用 */
160         init_rq_hrtick(rq);
161         atomic_set(&rq->nr_iowait, 0);
162     }
163     /* 設置 init_task 進程的權重 */
164     set_load_weight(&init_task);
165 
166 #ifdef CONFIG_PREEMPT_NOTIFIERS
167     /* 初始化通知鏈 */
168     INIT_HLIST_HEAD(&init_task.preempt_notifiers);
169 #endif
170 
171     /*
172      * The boot idle thread does lazy MMU switching as well:
173      */
174     atomic_inc(&init_mm.mm_count);
175     enter_lazy_tlb(&init_mm, current);
176 
177     /*
178      * Make us the idle thread. Technically, schedule() should not be
179      * called from this thread, however somewhere below it might be,
180      * but because we are the idle thread, we just pick up running again
181      * when this runqueue becomes "idle".
182      */
183     /* 將當前進程初始化為idle進程,idle進程用於當CPU沒有進程可運行時運行,空轉 */
184     init_idle(current, smp_processor_id());
185     /* 下次負載更新時間(是一個相對時間) */
186     calc_load_update = jiffies + LOAD_FREQ;
187 
188     /*
189      * During early bootup we pretend to be a normal task:
190      */
191     /* 設置idle進程使用CFS調度策略 */
192     current->sched_class = &fair_sched_class;
193 
194 #ifdef CONFIG_SMP
195     zalloc_cpumask_var(&sched_domains_tmpmask, GFP_NOWAIT);
196     /* May be allocated at isolcpus cmdline parse time */
197     if (cpu_isolated_map == NULL)
198         zalloc_cpumask_var(&cpu_isolated_map, GFP_NOWAIT);
199     idle_thread_set_boot_cpu();
200     set_cpu_rq_start_time();
201 #endif
202     init_sched_fair_class();
203     /* 這里只是標記調度器開始運行了,但是此時系統只有一個init_task(idle)進程,並且定時器都還沒啟動。並不會調度到其他進程,也沒有其他進程可供調度 */
204     scheduler_running = 1;
205 }

  

總結

  調度器的初始化還是比較簡單的,畢竟調度器的核心不在此,重頭戲在它的運行時處理,之后的文章會詳細分析調度器的運行時處理。

 


免責聲明!

本站轉載的文章為個人學習借鑒使用,本站對版權不負任何法律責任。如果侵犯了您的隱私權益,請聯系本站郵箱yoyou2525@163.com刪除。



 
粵ICP備18138465號   © 2018-2025 CODEPRJ.COM