1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
287
288
289
290
291
292
293
294
295
296
297
298
299
300
301
302
303
304
305
306
307
308
309
310
311
312
313
314
315
316
317
318
319
320
321
322
323
324
325
326
327
328
329
330
331
332
333
334
335
336
337
338
339
340
341
342
343
344
345
346
347
| /*每个处理器都会配置一个rq*/
struct rq {
/* runqueue lock: */
spinlock_t lock;
/*
* nr_running and cpu_load should be in the same cacheline because
* remote CPUs use both these fields when doing load calculation.
*/
/*用以记录目前处理器rq中执行task的数量*/
unsigned long nr_running;
#define CPU_LOAD_IDX_MAX 5
/*用以表示处理器的负载,在每个处理器的rq中
都会有对应到该处理器的cpu_load参数配置,在每次
处理器触发scheduler tick时,都会呼叫函数
update_cpu_load_active,进行cpu_load的更新。在系统初始化的时候
会呼叫函数sched_init把rq的cpu_load array初始化为0.
了解他的更新方式最好的方式是通过函数update_cpu_load,公式如下澹?
cpu_load[0]会直接等待rq中load.weight的值。
cpu_load[1]=(cpu_load[1]*(2-1)+cpu_load[0])/2
cpu_load[2]=(cpu_load[2]*(4-1)+cpu_load[0])/4
cpu_load[3]=(cpu_load[3]*(8-1)+cpu_load[0])/8
cpu_load[4]=(cpu_load[4]*(16-1)+cpu_load[0]/16
呼叫函数this_cpu_load时,所返回的cpu load值是cpu_load[0]
而在进行cpu blance或migration时,就会呼叫函数
source_load target_load取得对该处理器cpu_load index值,
来进行计算*/
unsigned long cpu_load[CPU_LOAD_IDX_MAX];
#ifdef CONFIG_NO_HZ
unsigned long last_tick_seen;
unsigned char in_nohz_recently;
#endif
/* capture load from *all* tasks on this cpu: */
/*load->weight值,会是目前所执行的schedule entity的
load->weight的总和,也就是说rq的load->weight越高,
也表示所负责的排程单元load->weight总和越高
表示处理器所负荷的执行单元也越重*/
struct load_weight load;
/*在每次scheduler tick中呼叫update_cpu_load时,
这个值就增加一,可以用来反馈目前cpu
load更新的次数*/
unsigned long nr_load_updates;
/*用来累加处理器进行context switch的次数,会在
函数schedule呼叫时进行累加,并可以通过函数
nr_context_switches统计目前所有处理器总共的context switch
次数,或是可以透过查看档案/proc/stat中的ctxt位得知目前
整个系统触发context switch的次数*/
u64 nr_switches;
u64 nr_migrations_in;
/*为cfs fair scheduling class 的rq*/
struct cfs_rq cfs;
/*为real-time scheduling class 的rq*/
struct rt_rq rt;
/*用以支援可以group cfs tasks的机制*/
#ifdef CONFIG_FAIR_GROUP_SCHED
/* list of leaf cfs_rq on this cpu: */
/*在有设置fair group scheduling 的环境下,
会基于原本cfs rq中包含有若干task的group
所成的排程集合,也就是说当有一个group a
就会有自己的cfs rq用来排程自己所属的tasks,
而属于这group a的tasks所使用到的处理器时间
就会以这group a总共所分的的时间为上限。
基于cgroup的fair group scheduling 架构,可以创造出
有阶层性的task组织,根据不同task的功能群组化
在配置给该群主对应的处理器资源,让属于
该群主下的task可以透过rq机制排程。使用属于
该群主下的资源。
这个变数主要是管理CFS RQ list,操作上可以透过函数
list_add_leaf_cfs_rq把一个group cfs rq加入到list中,或透过
函数list_del_leaf_cfs_rq把一个group cfs rq移除,并可以
透过for_each_leaf_cfs_rq把一个rq上得所有leaf cfs_rq走一遍
*/
struct list_head leaf_cfs_rq_list;
#endif
/*用以支援可以group real-time tasks的机制*/
#ifdef CONFIG_RT_GROUP_SCHED
/*类似leaf_cfs_rq_list所扮演的角色,只是这里
是针对属于real-time的task,在实际操作上可以
透过函数list_add_leaf_rt_rq,list_del_leaf_rt_rq或
巨集for_each_leaf_rt_rq*/
struct list_head leaf_rt_rq_list;
#endif
/*
* This is part of a global counter where only the total sum
* over all CPUs matters. A task can increase this counter on
* one CPU and if it got migrated afterwards it may decrease
* it on another CPU. Always updated under the runqueue lock:
*/
/*一般来说,linux kernel 的task状态可以为TASK_RUNNING
TASK_INTERRUPTIBLE(sleep),
TASK_UNINTERRUPTIBLE(Deactivate Task,此时Task会从rq中
移除)或TASK_STOPPED.
透过这个变数会统计目前rq中有多少task属于
TASK_UNINTERRUPTIBLE的状态。当呼叫函数
active_task时,会把nr_uninterruptible值减一,并透过 该函数
enqueue_task把对应的task依据所在的scheduling class
放在 对应的rq中,并把目前rq中nr_running值加一*/
unsigned long nr_uninterruptible;
/*curr:指向目前处理器正在执行的task;
idle:指向属于idle-task scheduling class 的idle task;
stop:指向目前最高等级属于stop-task scheduling class
的task;*/
struct task_struct *curr, *idle;
/*基于处理器的jiffies值,用以记录下次进行处理器
balancing 的时间点*/
unsigned long next_balance;
/*用以存储context-switch发生时,前一个task的memory management
结构并可用在函数finish_task_switch中,透过函数mmdrop释放前一个
task的记忆体资源*/
struct mm_struct *prev_mm;
/*用以记录目前rq的clock值,基本上该值会等于透过sched_clock_cpu
(cpu_of(rq))的回传值,并会在每次呼叫scheduler_tick时透过
函数update_rq_clock更新目前rq clock值。
在实作部分,函数sched_clock_cpu会透过sched_clock_local或
ched_clock_remote取得对应的sched_clock_data,而处理的sched_clock_data
值,会透过函数sched_clock_tick在每次呼叫scheduler_tick时进行更新;
*/
u64 clock;
/*用以记录目前rq中有多少task处于等待i/o的sleep状态
在实际的使用上,例如当driver接受来自task的调用,但处于等待i/o
回复的阶段时,为了充分利用处理器的执行资源,这时
就可以在driver中呼叫函数io_schedule,此时
就会把目前rq中的nr_iowait加一,并设定目前task的io_wait为1
然后触发scheduling 让其他task有机会可以得到处理器执行时间*/
atomic_t nr_iowait;
#ifdef CONFIG_SMP
/*root domain是基于多核心架构下的机制,
会由rq结构记住目前采用的root domain,其中包括了
目前的cpu mask(包括span,online rt overload), reference count 跟cpupri
当root domain有被rq参考到时,refcount 就加一,反之就减一。而cpu
mask span表示rq可挂上的cpu mask,noline为rq目前已经排程的
cpu mask cpu上执行real-time task.可以参考函数pull_rt_task,当一个rq中属于
real-time的task已经执行完毕,就会透过函数pull_rt_task从该
rq中属于rto_mask cpu mask 可以执行的处理器上,找出是否有一个处理器
有大于一个以上的real-time task,若有就会转到目前这个执行完成
real-time task 的处理器上
而cpupri不同于Task本身有区分140個(0-139)
Task Priority (0-99為RT Priority 而 100-139為Nice值 -20-19).
CPU Priority本身有102個Priority (包括,-1 為Invalid,
0為Idle,1為Normal,2-101對應到Real-Time Priority 0-99).
參考函式convert_prio, Task Priority如果是 140就會對應到
CPU Idle,如果是大於等於100就會對應到CPU Normal,
若是Task Priority介於0-99之間,就會對應到CPU Real-Time Priority 101-2之間.)
在實際的操作上,例如可以透過函式cpupri_find
帶入一個要插入的Real-Time Task,此時就會依據cpupri中
pri_to_cpu選擇一個目前執行Real-Time Task且該Task
的優先級比目前要插入的Task更低的處理器,
並透過CPU Mask(lowest_mask)返回目前可以選擇的處理器Mask.
實作的部份可以參考檔案kernel/sched_cpupri.c.
在初始化的過程中,會透過函式sched_init呼叫函式init_defrootdomain,
對Root Domain與 CPU Priority機制進行初始化.
*/
struct root_domain *rd;
/*Schedule Domain是基於多核心架構下的機制.
每個處理器都會有一個基礎的Scheduling Domain,
Scheduling Domain可以有階層性的架構,透過parent
可以找到上一層的Domain,或是透過child找到
下一層的 Domain (NULL表示結尾.).並可透過span
栏位,表示這個Domain所能涵蓋的處理器範圍.
通常Base Domain會涵蓋系統中所有處理器的個數,
而Child Domain所能涵蓋的處理器個數不超過它的
Parent Domain. 而當在進行Scheduling Domain 中的Task Balance
時,就會以該Domain所能涵蓋的處理器為最大範圍.
同時,每個Schedule Domain都會包括一個或一個以上的
CPU Groups (結構為struct sched_group),並透過next變數把
CPU Groups串連在一起(成為一個單向的Circular linked list),
每個CPU Group都會有變數cpumask來定义這個CPU Group
所涵蓋的處理器範圍.並且CPU Group所包括的處理器
範圍,必需涵蓋在所屬的Schedule Domain處理器範圍中.
當進行Scheduling Domain的Balancing時,會以其下的CPU Groups
為單位,根據cpu_power (會是該Group所涵蓋的處理器
Tasks Loading的總和)來比較不同的CPU Groups的負荷,
以進行Tasks的移動,達到Balancing的目的.
在有支援SMP的架構下,會在函式sched_init中,呼叫open_softirq,
註冊 SCHED_SOFTIRQ Software IRQ与其对应的 Callback函式
run_rebalance_domains. 並會在每次呼叫函式scheduler_tick時,
透過函式trigger_load_balance确认是否目前的jiffies值已經
大於RunQueue下一次要觸發Load Balance的next_balance時間值,
並透過函式raise_softirq觸發SCHED_SOFTIRQ Software IRQ.
在Software IRQ觸發後,就會呼叫函式run_rebalance_domains,
並在函式rebalance_domains中,進行后续處理器上的
Scheduling Domain Load Balance動作.
有關Scheduling Domain進一步的內容,也可以參考
Linux Kernel文件 Documentation/scheduler/sched-domains.txt.
*/
struct sched_domain *sd;
/*這值會等於函式idle_cpu的返回值,如果為1表示
目前CPU RunQueue中執行的為Idle Task. 反之為0,
則表示處理器執行的不是Idle Task (也就是說
處理器正在忙碌中.).*/
unsigned char idle_at_tick;
/* For active balancing */
/*若這值不為0,表示會有在Schedule排程動作
結束前,要呼叫的收尾函式. (实作為inline函式
post_schedule in kernel/sched.c),目前只有Real-Time Scheduling
Class有支援這個機制(會呼叫函式has_pushable_tasks
in kernel/sched_rt.c).*/
int post_schedule;
/*當RunQueue中此值為1,表示這個RunQueue正在進行
Fair Scheduling的Load Balance,此時會呼叫stop_one_cpu_nowait
暫停該RunQueue所屬處理器的排程,並透過函式
active_load_balance_cpu_stop,把Tasks從最忙碌的處理器,
移到Idle的處理器上執行.*/
int active_balance;
/*用以儲存目前進入Idle且負責進行 Load Balance
流程的處理器ID. 呼叫的流程為,在呼叫函式schedule時,
若該處理器RunQueue的nr_running為0 (也就是目前沒有
正在執行的Task),就會呼叫idle_balance,並觸發後續Load
Balance流程.*/
int push_cpu;
/* cpu of this runqueue: */
/*用以儲存目前运作這個RunQueue的處理器ID*/
int cpu;
/*為1表示目前此RunQueue有在對應的處理器掛上
並執行.*/
int online;
/*如果RunQueue中目前有Task正在執行,這個值會
等於目前該RunQueue的Load Weight除以目前RunQueue
中Task數目的均值.
(rq->avg_load_per_task = rq->load.weight / nr_running;).*/
unsigned long avg_load_per_task;
struct task_struct *migration_thread;
struct list_head migration_queue;
/*這個值會由Real-Time Scheduling Class呼叫函式
update_curr_rt,用以統計目前Real-Time Task執行時間的
均值,在這函式中會以目前RunQueue的clock_task
減去目前Task執行的起始時間,取得執行時間的
Delta值. (delta_exec = rq->clock_task – curr->se.exec_start; ).
在透過函式sched_rt_avg_update把這Delta值跟原本
RunQueue中的rt_avg值取平均值. 以運作的週期來看,
這個值可反應目前系統中Real-Time Task平均被
分配到的執行時間值.*/
u64 rt_avg;
/*這個值主要在函式sched_avg_update更新,以笔者手中
的Linux Kernel 2.6.38.6的實作來說,當RunQueue Clock
減去age_stamp大於 0.5秒 (=sched_avg_period),就會把這值
累加0.5秒 (單位都是nanoseconds). 從函式scale_rt_power
的實作來說,age_stamp值離RunQueue Clock越遠,表示total
值越大,available值也越大,而函式scale_rt_power返回的
div_u64計算結果也越大,最終 RunQueue的cpu_power
與Scheduling Domain中的Scheduling Group的cpu_power
值也就越大. (可參考函式update_cpu_power的實作).*/
u64 age_stamp;
/*這值會在觸發Scheduling時,若判斷目前處理器
RunQueue沒有正在運作的Task,就會透過函式
idle_balance更新這值為為目前RunQueue的clock值.
可用以表示這個處理器是何時進入到Idle的
狀態*/
u64 idle_stamp;
/*會在有Task運作且idle_stamp不為0 (表示前一個
狀態是在Idle)時以目前RunQueue的clock減去
idle_stmp所計算出的Delta值為依據,更新這個值
. 可反應目前處理器進入Idle狀態的時間長短*/
u64 avg_idle;
#endif
/* calc_load related fields */
/*用以記錄下一次計算CPU Load的時間,初始值
為目前的jiffies加上五秒與1次的Scheduling Tick的
間隔 (=jiffies + LOAD_FREQ,且LOAD_FREQ=(5*HZ+1))*/
unsigned long calc_load_update;
/*會等於RunQueue中nr_running與nr_uninterruptible的
總和.(可參考函式calc_load_fold_active).*/
long calc_load_active;
#ifdef CONFIG_SCHED_HRTICK
#ifdef CONFIG_SMP
/*在函式init_rq_hrtick初始化RunQueue High-Resolution
Tick時,此值預設為0.
在函式hrtick_start中,會判斷目前觸發的RunQueue
跟目前處理器所使用的RunQueue是否一致,
若是,就直接呼叫函式hrtimer_restart,反之就會
依據RunQueue中hrtick_csd_pending的值,如果
hrtick_csd_pending為0,就會透過函式
__smp_call_function_single讓RunQueue所在的另一個
處理器執行rq->hrtick_csd.func 所指到的函式
__hrtick_start. 並等待該處理器執行完畢後,
才重新把hrtick_csd_pending設定為1.
也就是說, RunQueue的hrtick_csd_pending是用來作為
SMP架構下,由處理器A觸發處理器B執行
_hrtick_start函式的一個保護機制.而有關在
SMP下如何由一個處理器觸發另一個處理器
執行函式的機制,可以參考kernel/smp.c中
相關smp_call_function_xxxxxxx的實作.s*/
int hrtick_csd_pending;
/*用以儲存hrtick機制中,要跨處理器執行的
函式結構.*/
struct call_single_data hrtick_csd;
#endif
/*為High-Resolution Tick的结构,會透過函式
hrtimer_init初始化.*/
struct hrtimer hrtick_timer;
#endif
#ifdef CONFIG_SCHEDSTATS
/* latency stats */
/*為Scheduling Info.的統計結構,可以參考
include/linux/sched.h中的宣告. 例如在每次觸發
Schedule時,呼叫函式schedule_debug對上一個Task
的lock_depth進行確認(Fork一個新的Process 時,
會把此值預設為-1就是No-Lock,當呼叫
Kernel Lock時, 就會把Current Task的lock_depth加一.),
若lock_depth>=0,就會累加Scheduling Info.的bkl_count值,
用以代表Task Blocking的次數.*/
struct sched_info rq_sched_info;
/*可用以表示RunQueue中的Task所得到CPU執行
時間的累加值.
在發生Task Switch時,會透過sched_info_switch呼叫
sched_info_arrive並以目前RunQueue Clock值更新
Task 的sched_info.last_arrival時間,而在Task所分配時間
結束後,會在函式sched_info_depart中以現在的
RunQueue Clock值減去Task的sched_info.last_arrival
時間值,得到的 Delta作為變數rq_cpu_time的累
加值.*/
unsigned long long rq_cpu_time;
/* could above be rq->cfs_rq.exec_clock + rq->rt_rq.rt_runtime ? */
/* sys_sched_yield() stats */
/*用以統計呼叫System Call sys_sched_yield的次數.*/
unsigned int yld_count;
/* schedule() stats */
unsigned int sched_switch;
/*可用以統計觸發Scheduling的次數. 在每次觸發
Scheduling時,會透過函式schedule呼叫schedule_debug,
呼叫schedstat_inc對這變數進行累加.*/
unsigned int sched_count;
/*可用以統計進入到Idle Task的次數. 會在函式
pick_next_task_idle中,呼叫schedstat_inc對這變數進行
累加.*/
unsigned int sched_goidle;
/* try_to_wake_up() stats */
/*用以統計Wake Up Task的次數.*/
unsigned int ttwu_count;
/*用以統計Wake Up 同一個處理器Task的次數.*/
unsigned int ttwu_local;
/* BKL stats */
unsigned int bkl_count;
#endif
};
|