More Related Content Similar to Linuxのプロセススケジューラ(Reading the Linux process scheduler) Similar to 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