Contenu connexe Similaire à Linuxのプロセススケジューラ(Reading the Linux process scheduler) Similaire à Linuxのプロセススケジューラ(Reading the Linux process scheduler) (20) Linuxのプロセススケジューラ(Reading the Linux process scheduler)1. Linuxのプロセススケジューラ
(Reading the Linux process scheduler)
Copyright Hitachi Ltd. 2014. All rights reserved.
日立製作所 横浜研究所
豊岡 拓 (hiraku.toyooka.gu@hitachi.com)
!
Linux 3.15.0版
2. プロセススケジューラに関す
るトピックの全体像
プロセススケジューラ
スケジューリング・クラス
CFS Real-time
Copyright Hitachi Ltd. 2014. All rights reserved.
Dead-line
ロードバランスグループ・
スケジューリング
カーネル内
プリエンプション
stop
idle
省電力
割り込み,
スピンロック
プロセス/スレッド,
時間管理,
高精度タイマ,
tick管理,
etc..
wait-queue,
セマフォ,
ミューテックス,
依存
ユーザ空間へのI/F(システムコール等) etc..
2
共通部(優先度、データ構造、関数)
4. スケジューリングクラスとポリシー
• Fairクラス
• SCHED_OTHER, SCHED_BATCH, SCHED_IDLE
• Real-timeクラス
• SCHED_FIFO, SCHED_RR
• Deadlineクラス
• SCHED_DEADLINE
• (Stopクラス、Idleクラス)
• ユーザは使用できない(stop_machineやidleスレッドの実装)
Copyright Hitachi Ltd. 2014. All rights reserved.
4
5. Fairクラス(CFS)
• プロセス間の公平性を保ちつつ、CPU利用効率の最大化とイベント処理へ
のレスポンス高速化を両立
• 累積実行時間が最も少ないプロセスにCPUを割り当てる
• ただし、実行可能プロセス数が多い時にプロセス切り替えが頻繁に起こ
るのを防ぐため、最小のタイムスライスを持つ
• SCHED_OTHER: デフォルトのポリシー
• SCHED_BATCH: CPU-intensiveだと仮定してスイッチを起こしにくくする
• SCHED_IDLE: 他に動けるプロセスがいない時だけ実行される
Copyright Hitachi Ltd. 2014. All rights reserved.
5
8. Copyright Hitachi Ltd. 2014. All rights reserved.
優先度
8
ポリシー優先度
(カーネル内)
優先度
優先度
(ユーザ空間) nice値(ps)
SCHED_DEADLINE -1 -
-
-101
SCHED_FIFO,
SCHED_RR
0 99 -100
1 98 -99
.
.
.
.
.
.
97 2 -3
98 1 -2
SCHED_OTHER,
SCHED_BATCH
100
0
-20 0
101 -19 1
.
.
.
.
.
.
120(default) 0 20
.
.
.
.
.
.
138 18 38
139 19 39
SCHED_IDLE - - -
高い
低い
9. データ構造
• プロセス構造体 (struct task_struct)
• プロセスの状態フラグ
• スケジューリング・クラス (struct sched_class)
• ランキュー (struct rq)
Copyright Hitachi Ltd. 2014. All rights reserved.
9
10. struct task_struct
• プロセスを表す構造体
• クラス固有のスケジューリング情報は各クラスの
「エンティティ」で表される
型名前説明
long state プロセスの状態フラグ
int on_rq ランキュー上にプロセスが存在するか否か
int prio プロセスの優先度
unsigned int policy プロセスのスケジューリングポリシー
struct sched_class *sched_class プロセスのスケジューリングクラス
struct sched_entity se fairクラス用のスケジューリングエンティティ
struct sched_rt_entity rt rtクラス用のスケジューリングエンティティ
struct sched_dl_entity dl deadlineクラス用のスケジューリングエンティティ
Copyright Hitachi Ltd. 2014. All rights reserved.
10
11. プロセスの状態フラグ
フラグ説明
TASK_RUNNING 実行可能状態(実行中を含む)
TASK_INTERRUPTIBLE 条件待ちによる停止中
TASK_UNINTERRUPTIBLE 同上。シグナルによる割り込み不可
EXIT_ZOMBIE 実行終了後、プロセス構造体の回収待ち
EXIT_DEAD プロセス構造体の回収開始後の状態
TASK_DEAD exit終了後の最後のプロセススイッチ時にスケ
Copyright Hitachi Ltd. 2014. All rights reserved.
11
ジューリング・クラス固有の処理を呼び出す
TASK_STOPPED シグナル受信による停止中
TASK_TRACED デバッガにより停止中(ptraceで監視中にシグ
ナル受信)
TASK_WAKING 起床中
12. プロセスの一生
(none)
fork/clone
Copyright Hitachi Ltd. 2014. All rights reserved.
12
RUNNING
INTERRUPTIBLE
or
UNINTERRUPTIBLE
or
STOPPED
or
TRACED
EXIT_ZOMBIE
EXIT_DEAD
exit
wait
WAKING
13. struct sched_class
• スケジューリングクラスを表す構造体
• クラス固有処理の関数ポインタの表
• スケジューラ共通処理からクラス固有処理への
呼び出しインタフェース
Copyright Hitachi Ltd. 2014. All rights reserved.
13
14. struct sched_class
Copyright Hitachi Ltd. 2014. All rights reserved.
14
struct sched_class {
const struct sched_class *next;
!
void (*enqueue_task) (struct rq *rq, struct task_struct *p, int flags);
void (*dequeue_task) (struct rq *rq, struct task_struct *p, int flags);
void (*yield_task) (struct rq *rq);
bool (*yield_to_task) (struct rq *rq, struct task_struct *p, bool preempt);
void (*check_preempt_curr) (struct rq *rq, struct task_struct *p, int flags);
struct task_struct * (*pick_next_task) (struct rq *rq, struct task_struct *prev);
void (*put_prev_task) (struct rq *rq, struct task_struct *p);
int (*select_task_rq)(struct task_struct *p, int task_cpu, int sd_flag, int flags);
void (*migrate_task_rq)(struct task_struct *p, int next_cpu);
void (*post_schedule) (struct rq *this_rq);
void (*task_waking) (struct task_struct *task);
void (*task_woken) (struct rq *this_rq, struct task_struct *task);
void (*set_cpus_allowed)(struct task_struct *p, const struct cpumask *newmask);
15. struct sched_class
Copyright Hitachi Ltd. 2014. All rights reserved.
15
!
void (*rq_online)(struct rq *rq);
void (*rq_offline)(struct rq *rq);
void (*set_curr_task) (struct rq *rq);
void (*task_tick) (struct rq *rq, struct task_struct *p, int queued);
void (*task_fork) (struct task_struct *p);
void (*task_dead) (struct task_struct *p);
void (*switched_from) (struct rq *this_rq, struct task_struct *task);
void (*switched_to) (struct rq *this_rq, struct task_struct *task);
void (*prio_changed) (struct rq *this_rq, struct task_struct *task, int oldprio);
unsigned int (*get_rr_interval) (struct rq *rq, struct task_struct *task);
void (*task_move_group) (struct task_struct *p, int on_rq);
};
16. struct rq
• CPUごとに存在するランキューを表す構造体
• ランキュー実体は各クラスのランキュー構造体
Copyright Hitachi Ltd. 2014. All rights reserved.
16
型名前説明
raw_spinlock_t lock ランキュー自体を保護するロック
unsigned int nr_running ランキュー内のTASK_RUNNING状態のプロセ
ス数
struct cfs_rq cfs fairクラスのランキュー
struct rt_rq rt rtクラスのランキュー
struct dl_rq dl deadlineクラスのランキュー
struct task_struct * curr カレントプロセス
u64 clock ランキュー操作時刻
int cpu このランキューの存在するCPU
17. スケジューラ関数
• scheduler_tick(void), hrtick(void)
• タイマーtick割り込み時に呼び出され、スケジューリング・
クラスごとのtick時処理(アカウンティングなど)を行う
• try_to_wake_up(p, state, wake_flags)
• プロセスpがstateの条件に合致するならpを起床させる
• schedule(void)
• 次に実行するプロセスを選択し、プロセス切り替えを行う
Copyright Hitachi Ltd. 2014. All rights reserved.
17
18. scheduler_tick()
(x86における)呼び出し経路
• smp_apic_timer_interrupt()
• local_apic_timer_interrupt()
• hrtimer_interrupt()
• __run_hrtimer()
• tick_sched_timer()
• tick_sched_handle()
• update_process_times()
• scheduler_tick()
Copyright Hitachi Ltd. 2014. All rights reserved.
18
19. void scheduler_tick(void)
{
int cpu = smp_processor_id();
struct rq *rq = cpu_rq(cpu);
struct task_struct *curr = rq->curr;
!
sched_clock_tick();
!
raw_spin_lock(&rq->lock);
update_rq_clock(rq);
curr->sched_class->task_tick(rq, curr, 0);
update_cpu_load_active(rq);
raw_spin_unlock(&rq->lock);
!
perf_event_task_tick();
!
#ifdef CONFIG_SMP
rq->idle_balance = idle_cpu(cpu);
trigger_load_balance(rq);
#endif
rq_last_tick_reset(rq);
}
(スケジューラで利用する時刻情報の
取得関数である)sched_clock()が不安
定な環境のためにsched_clock_dataを
更新
• idle中にHWカウンタが止まるケース
• 周波数の動的な変更でHWカウンタの
進む速度が変わるケース
ランキューのデータを保護するため
スピンロックを取得
(割り込みコンテキストなのでraw_)
プロセスのアカウンティングに使う
ランキュー操作時刻を更新
• さらにCPUごとの割り込み処理時
間&仮想マシン実行時間の統計情
Copyright Hitachi Ltd. 2013. All rights reserved. 19
20. void scheduler_tick(void)
{
int cpu = smp_processor_id();
struct rq *rq = cpu_rq(cpu);
struct task_struct *curr = rq->curr;
!
sched_clock_tick();
!
raw_spin_lock(&rq->lock);
update_rq_clock(rq);
curr->sched_class->task_tick(rq, curr, 0);
update_cpu_load_active(rq);
raw_spin_unlock(&rq->lock);
!
perf_event_task_tick();
!
#ifdef CONFIG_SMP
rq->idle_balance = idle_cpu(cpu);
trigger_load_balance(rq);
#endif
rq_last_tick_reset(rq);
}
カレントプロセスの属するスケジュー
リングクラスのtask_tick()メソッドを
呼び出す。
• プロセスのアカウンティング(タイ
ムスライスの更新など)
• プロセス切替え(リスケジューリン
グ)の必要性の確認
• etc.
Copyright Hitachi Ltd. 2013. All rights reserved. 20
21. void scheduler_tick(void)
{
int cpu = smp_processor_id();
struct rq *rq = cpu_rq(cpu);
struct task_struct *curr = rq->curr;
!
sched_clock_tick();
!
raw_spin_lock(&rq->lock);
update_rq_clock(rq);
curr->sched_class->task_tick(rq, curr, 0);
update_cpu_load_active(rq);
raw_spin_unlock(&rq->lock);
!
perf_event_task_tick();
!
#ifdef CONFIG_SMP
rq->idle_balance = idle_cpu(cpu);
trigger_load_balance(rq);
#endif
rq_last_tick_reset(rq);
}
CPU負荷の計算
ロードバランスの開始要求
(raise_softirq(SCHED_SOFTIRQ))
tick割り込み終了後に開始される
Copyright Hitachi Ltd. 2013. All rights reserved. 21
22. Copyright Hitachi Ltd. 2014. All rights reserved.
hrtick()
• 通常のtickは1msおき(HZ=1000の場合)
• プロセス切替は、カレントプロセスがタイムスライス
を使い切った後のtickで実行される
• タイムスライスを使い切るタイミングでtickを上
げることで高精度な時分割を実現
• HRTICK機能がONの場合(/sys/kernel/debug/sched_features)
22
23. static enum hrtimer_restart hrtick(struct hrtimer *timer)
{
struct rq *rq = container_of(timer, struct rq, hrtick_timer);
!
WARN_ON_ONCE(cpu_of(rq) != smp_processor_id());
!
raw_spin_lock(&rq->lock);
update_rq_clock(rq);
rq->curr->sched_class->task_tick(rq, rq->curr, 1);
raw_spin_unlock(&rq->lock);
!
return HRTIMER_NORESTART;
} task_tickの第3引数(int queue)で、
hrtick()からの呼び出しであることを
伝える(=queued tick)
• プリエンプトすべきかどうかのチェッ
クを省略する
Copyright Hitachi Ltd. 2013. All rights reserved. 23
24. try_to_wake_up()
• 下記の3関数から呼ばれる
• wake_up_process()
• TASK_{UN}INTERRUPTIBLEのプロセスのみ起床
• wake_up_state()
• 指定した状態のプロセスのみ起床
• default_wake_function()
• wait queue等のコールバックとして使われる
• wake_flagsを使用(今回は未調査)
Copyright Hitachi Ltd. 2014. All rights reserved.
24
25. static int
try_to_wake_up(struct task_struct *p, unsigned int state, int wake_flags)
{
unsigned long flags;
int cpu, success = 0;
!
smp_mb__before_spinlock();
raw_spin_lock_irqsave(&p->pi_lock, flags);
if (!(p->state & state))
goto out;
!
success = 1; /*we're going to change ->state*/
cpu = task_cpu(p);
!
if (p->on_rq && ttwu_remote(p, wake_flags))
goto stat;
p: 起床させるプロセス
state: pがどの状態であれば起床させ
るかの条件
wake_flags:
プロセスpが指定されたstateでない
なら終了
プロセスpがまだランキューに残ってい
る場合、ttwu_remoteを呼び出してラ
ンキュー操作の不要な軽量起床を行う
1. pと起床先CPUのカレントプロセスを
比較して必要ならプリエンプト要求
を出す
2. pの状態をTASK_RUNNINGへ戻す
3. pが起床先CPUですぐに実行できな
い場合、他CPUで実行できるか試み
る(SMPかつrt, deadlineクラスのみ)
Copyright Hitachi Ltd. 2014. All rights reserved. 25
26. #ifdef CONFIG_SMP
while (p->on_cpu)
cpu_relax();
起床先CPUで(ランキューから外さ
れている)プロセスpが別のプロセス
へ切り替わるのを待つ
smp_rmb();
p->sched_contributes_to_load = !!task_contributes_to_load(p);
p->state = TASK_WAKING;
!
if (p->sched_class->task_waking)
p->sched_class->task_waking(p);
プロセスpの状態をWAKING(起床中)に変更
プロセスpの所属クラスの
task_waking()メソッド呼び出し
• fairクラスのみ: 仮想実行時間
(vruntime)の補充
cpu = select_task_rq(p, p->wake_cpu, SD_BALANCE_WAKE, wake_flags);
if (task_cpu(p) != cpu) {
wake_flags |= WF_MIGRATED;
set_task_cpu(p, cpu);
}
#endif /* CONFIG_SMP */
!
ttwu_queue(p, cpu);
stat:
ttwu_stat(p, cpu, wake_flags);
out:
raw_spin_unlock_irqrestore(&p->pi_lock, flags);
return success;
}
Copyright Hitachi Ltd. 2014. All rights reserved. 26
27. #ifdef CONFIG_SMP
while (p->on_cpu)
cpu_relax();
smp_rmb();
p->sched_contributes_to_load = !!task_contributes_to_load(p);
p->state = TASK_WAKING;
!
if (p->sched_class->task_waking)
p->sched_class->task_waking(p);
プロセスpの所属クラスの
select_task_rq()メソッドを呼び
出し、起床先CPUを決定
cpu = select_task_rq(p, p->wake_cpu, SD_BALANCE_WAKE, wake_flags);
if (task_cpu(p) != cpu) {
wake_flags |= WF_MIGRATED;
set_task_cpu(p, cpu);
}
#endif /* CONFIG_SMP */
!
ttwu_queue(p, cpu);
stat:
ttwu_stat(p, cpu, wake_flags);
out:
raw_spin_unlock_irqrestore(&p->pi_lock, flags);
return success;
}
プロセスpの元の動作CPUと起床
先CPUが異なる場合、pの動作CPU
を新たにセットする
Copyright Hitachi Ltd. 2014. All rights reserved. 27
28. #ifdef CONFIG_SMP
while (p->on_cpu)
cpu_relax();
smp_rmb();
p->sched_contributes_to_load = !!task_contributes_to_load(p);
p->state = TASK_WAKING;
!
基本パス
1. プロセスpをランキューへ入れる
2. pと起床先CPUのカレントプロセスを比較して必要
ならプリエンプト要求を出す
3. pの状態をTASK_RUNNINGへ戻す
4. pが起床先CPUですぐに実行できない場合、他CPU
で実行できるか試みる(SMPかつrt, deadlineクラス
のみ)
!
TTWU_QUEUE機能がON、かつ、起床元CPUと起床先
CPUがlast-levelキャッシュを共有していない場合
1. 起床先CPUランキューのwake_listへ繋ぐ
2. rescheduling IPIを起床先CPUへ送る
→CPU間でランキューのロック競合を起こさずに済む
if (p->sched_class->task_waking)
p->sched_class->task_waking(p);
cpu = select_task_rq(p, p->wake_cpu, SD_BALANCE_WAKE, wake_flags);
if (task_cpu(p) != cpu) {
wake_flags |= WF_MIGRATED;
set_task_cpu(p, cpu);
}
#endif /* CONFIG_SMP */
!
ttwu_queue(p, cpu);
stat:
ttwu_stat(p, cpu, wake_flags);
out:
raw_spin_unlock_irqrestore(&p->pi_lock, flags);
return success;
}
Copyright Hitachi Ltd. 2014. All rights reserved. 28
29. schedule()
• 明示的な呼び出し
• ミューテックス、セマフォ、wait-queue
• プリエンプションによる呼び出し
• カレントプロセスにTIF_NEED_RESCHEDフラグが
立っている状態でチェックポイント(後述)を実行
した時
Copyright Hitachi Ltd. 2014. All rights reserved.
29
30. TIF_NEED_RESCHEDの
チェックポイント
• CONFIG_PREEMPT=y の時
• システムコールや例外中にプリエンプションが許可された時
• 割り込みハンドラ終了時
• CONFIG_PREEMPT is not set の時
• cond_resched()が呼ばれた時
• システムコール/例外/割り込みからユーザ空間へ戻る時
Copyright Hitachi Ltd. 2014. All rights reserved.
30
31. schedule()
Copyright Hitachi Ltd. 2014. All rights reserved.
31
asmlinkage __visible void __sched schedule(void)
{
struct task_struct *tsk = current;
!
sched_submit_work(tsk);
__schedule();
}
スリープする前にキューイングして
おいたブロックI/Oをsubmitする
32. static void __sched __schedule(void)
{
struct task_struct *prev, *next;
unsigned long *switch_count;
struct rq *rq;
int cpu;
!
need_resched:
preempt_disable();
cpu = smp_processor_id();
rq = cpu_rq(cpu);
rcu_note_context_switch(cpu);
prev = rq->curr;
!
schedule_debug(prev);
!
if (sched_feat(HRTICK))
hrtick_clear(rq);
!
smp_mb__before_spinlock();
raw_spin_lock_irq(&rq->lock);
プリエンプションがネストしないよ
うに無効化しておく
prev(切り替え前)プロセス
=カレントプロセス
ランキューのロック獲得
Copyright Hitachi Ltd. 2014. All rights reserved. 32
33. switch_count = &prev->nivcsw;
if (prev->state && !(preempt_count() & PREEMPT_ACTIVE)) {
if (unlikely(signal_pending_state(prev->state, prev))) {
prev->state = TASK_RUNNING;
} else {
prevプロセスがTASK_RUNNINGで
ない
&&
明示的な__schedule()呼び出し
deactivate_task(rq, prev, DEQUEUE_SLEEP);
prev->on_rq = 0;
!
if (prev->flags & PF_WQ_WORKER) {
struct task_struct *to_wakeup;
!
to_wakeup = wq_worker_sleeping(prev, cpu);
if (to_wakeup)
try_to_wake_up_local(to_wakeup);
}
}
switch_count = &prev->nvcsw;
}
!
if (prev->on_rq || rq->skip_clock_update < 0)
update_rq_clock(rq);
Copyright Hitachi Ltd. 2014. All rights reserved. 33
34. switch_count = &prev->nivcsw;
if (prev->state && !(preempt_count() & PREEMPT_ACTIVE)) {
if (unlikely(signal_pending_state(prev->state, prev))) {
prev->state = TASK_RUNNING;
} else {
prevプロセスにペンディング中のシ
グナルがある場合、prevを再度
RUNNING状態へ戻す
deactivate_task(rq, prev, DEQUEUE_SLEEP);
prev->on_rq = 0;
!
if (prev->flags & PF_WQ_WORKER) {
struct task_struct *to_wakeup;
!
to_wakeup = wq_worker_sleeping(prev, cpu);
if (to_wakeup)
try_to_wake_up_local(to_wakeup);
}
}
switch_count = &prev->nvcsw;
}
!
if (prev->on_rq || rq->skip_clock_update < 0)
update_rq_clock(rq);
Copyright Hitachi Ltd. 2014. All rights reserved. 34
35. switch_count = &prev->nivcsw;
if (prev->state && !(preempt_count() & PREEMPT_ACTIVE)) {
if (unlikely(signal_pending_state(prev->state, prev))) {
prev->state = TASK_RUNNING;
} else {
deactivate_task(rq, prev, DEQUEUE_SLEEP);
prev->on_rq = 0;
!
if (prev->flags & PF_WQ_WORKER) {
struct task_struct *to_wakeup;
!
シグナルペンディング中でないなら、
prevプロセスをランキューから外す
to_wakeup = wq_worker_sleeping(prev, cpu);
if (to_wakeup)
try_to_wake_up_local(to_wakeup);
}
}
switch_count = &prev->nvcsw;
}
!
if (prev->on_rq || rq->skip_clock_update < 0)
update_rq_clock(rq);
Copyright Hitachi Ltd. 2014. All rights reserved. 35
36. next = pick_next_task(rq, prev);
clear_tsk_need_resched(prev);
clear_preempt_need_resched();
rq->skip_clock_update = 0;
!
if (likely(prev != next)) {
全スケジューリングクラスのプロセ
スのうち、最も優先度の高いものを
nextプロセスとして選択
prev != nextならプロセス切り替えへ入る。
カレントプロセスをnextに変更。
rq->nr_switches++;
rq->curr = next;
++*switch_count;
context_switch(rq, prev, next); /* unlocks the rq */
!
cpu = smp_processor_id();
rq = cpu_rq(cpu);
} else
raw_spin_unlock_irq(&rq->lock);
!
post_schedule(rq);
!
sched_preempt_enable_no_resched();
if (need_resched())
goto need_resched;
}
プロセス間のコンテキストを切り替える。
戻ってきた時にはrq->lockはunlockされて
いる。
カーネルスタックが切り替わっているので、
ローカル変数を更新
Copyright Hitachi Ltd. 2014. All rights reserved. 36
37. next = pick_next_task(rq, prev);
clear_tsk_need_resched(prev);
clear_preempt_need_resched();
rq->skip_clock_update = 0;
!
if (likely(prev != next)) {
rq->nr_switches++;
rq->curr = next;
++*switch_count;
context_switch(rq, prev, next); /* unlocks the rq */
!
cpu = smp_processor_id();
rq = cpu_rq(cpu);
} else
raw_spin_unlock_irq(&rq->lock);
!
post_schedule(rq);
!
sched_preempt_enable_no_resched();
if (need_resched())
goto need_resched;
}
各スケジューリングクラスのプロセス切り
替え後に実行すべき処理を実行
• rt, deadlineクラスのみ: カレントプロセ
ス以外で優先度の高いプロセスを他のCPU
で実行できないか試みる
• システム全体で優先度順になるように
プリエンプションを有効化する。
ただし、__schedule()をネストさ
せないようにプリエンプション処
理は関数内のループにより行う
Copyright Hitachi Ltd. 2014. All rights reserved. 37