Upload
hiraku-toyooka
View
4.205
Download
7
Embed Size (px)
DESCRIPTION
NLKB-005
Citation preview
Copyright Hitachi Ltd. 2014. All rights reserved.
Linuxのプロセススケジューラ (Reading the Linux process scheduler)
日立製作所 横浜研究所 豊岡 拓 ([email protected])
!
Linux 3.15.0版
Copyright Hitachi Ltd. 2014. All rights reserved.
プロセススケジューラ
スケジューリング・クラス
プロセススケジューラに関するトピックの全体像
CFS Real-time
Dead-line
ロードバランス グループ・スケジューリング
カーネル内 プリエンプション
stop
idle
省電力
wait-queue, セマフォ,
ミューテックス, etc..ユーザ空間へのI/F(システムコール等)
依存
割り込み, スピンロック
プロセス/スレッド, 時間管理, 高精度タイマ, tick管理, etc..
2
共通部(優先度、データ構造、関数)
Copyright Hitachi Ltd. 2014. All rights reserved.
プロセススケジューリングとは•複数のプロセスに対して、どれだけのCPU時間を、どのような順序で割り当てるかを決める方法
• 目標(全部を同時には満たせない)
• 高スループット
• 低レイテンシ
• 公平性の実現
• 実時間制約を満たす
• 省電力
3
Copyright Hitachi Ltd. 2014. All rights reserved.
スケジューリングクラスとポリシー• Fairクラス
• SCHED_OTHER, SCHED_BATCH, SCHED_IDLE
• Real-timeクラス
• SCHED_FIFO, SCHED_RR
• Deadlineクラス
• SCHED_DEADLINE
• (Stopクラス、Idleクラス)
• ユーザは使用できない(stop_machineやidleスレッドの実装)
4
Copyright Hitachi Ltd. 2014. All rights reserved.
Fairクラス(CFS)• プロセス間の公平性を保ちつつ、CPU利用効率の最大化とイベント処理へのレスポンス高速化を両立
• 累積実行時間が最も少ないプロセスにCPUを割り当てる
• ただし、実行可能プロセス数が多い時にプロセス切り替えが頻繁に起こるのを防ぐため、最小のタイムスライスを持つ
• SCHED_OTHER: デフォルトのポリシー
• SCHED_BATCH: CPU-intensiveだと仮定してスイッチを起こしにくくする
• SCHED_IDLE: 他に動けるプロセスがいない時だけ実行される
5
Copyright Hitachi Ltd. 2014. All rights reserved.
Real-timeクラス• 静的に与えられた優先度の高い順にプロセスをスケジュール
• SCHED_FIFO
• 自発的にCPUを手放さない限り実行し続ける
• SCHED_RR
• 最高優先度のプロセスが複数ある場合、与えられたタイムスライスによってラウンドロビン実行する
6
Copyright Hitachi Ltd. 2014. All rights reserved.
Deadlineクラス
• SCHED_DEADLINE
• 処理周期、期限、最大実行時間をプロセスに与え、期限の早いプロセスから順番にスケジュール
7
Copyright Hitachi Ltd. 2014. All rights reserved.
優先度
8
ポリシー 優先度 (カーネル内)
優先度 (ユーザ空間) nice値 優先度
(ps)SCHED_DEADLINE -1 -
-
-101
SCHED_FIFO, SCHED_RR
0 99 -1001 98 -99. .
.
.. .
97 2 -398 1 -2
SCHED_OTHER, SCHED_BATCH
100
0
-20 0101 -19 1. .
.
.. .
120(default) 0 20. .
.
.. .
138 18 38139 19 39
SCHED_IDLE - - -
高い
低い
Copyright Hitachi Ltd. 2014. All rights reserved.
データ構造
• プロセス構造体 (struct task_struct)
• プロセスの状態フラグ
• スケジューリング・クラス (struct sched_class)
• ランキュー (struct rq)
9
Copyright Hitachi Ltd. 2014. All rights reserved.
struct task_struct• プロセスを表す構造体
• クラス固有のスケジューリング情報は各クラスの「エンティティ」で表される
10
型 名前 説明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.
プロセスの状態フラグ
11
フラグ 説明TASK_RUNNING 実行可能状態(実行中を含む)TASK_INTERRUPTIBLE 条件待ちによる停止中TASK_UNINTERRUPTIBLE 同上。シグナルによる割り込み不可EXIT_ZOMBIE 実行終了後、プロセス構造体の回収待ちEXIT_DEAD プロセス構造体の回収開始後の状態
TASK_DEAD exit終了後の最後のプロセススイッチ時にスケジューリング・クラス固有の処理を呼び出す
TASK_STOPPED シグナル受信による停止中
TASK_TRACED デバッガにより停止中(ptraceで監視中にシグナル受信)
TASK_WAKING 起床中
Copyright Hitachi Ltd. 2014. All rights reserved.
プロセスの一生
12
RUNNINGINTERRUPTIBLE
or UNINTERRUPTIBLE
or STOPPED or
TRACEDEXIT_ZOMBIE
EXIT_DEAD
(none)
fork/clone
exit
wait
WAKING
Copyright Hitachi Ltd. 2014. All rights reserved.
struct sched_class
• スケジューリングクラスを表す構造体
• クラス固有処理の関数ポインタの表
• スケジューラ共通処理からクラス固有処理への呼び出しインタフェース
13
Copyright Hitachi Ltd. 2014. All rights reserved.
struct sched_class
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);
Copyright Hitachi Ltd. 2014. All rights reserved.
struct sched_class
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); };
Copyright Hitachi Ltd. 2014. All rights reserved.
struct rq
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
• CPUごとに存在するランキューを表す構造体
• ランキュー実体は各クラスのランキュー構造体
Copyright Hitachi Ltd. 2014. All rights reserved.
スケジューラ関数• scheduler_tick(void), hrtick(void)
• タイマーtick割り込み時に呼び出され、スケジューリング・クラスごとのtick時処理(アカウンティングなど)を行う
• try_to_wake_up(p, state, wake_flags)
• プロセスpがstateの条件に合致するならpを起床させる
• schedule(void)
• 次に実行するプロセスを選択し、プロセス切り替えを行う
17
Copyright Hitachi Ltd. 2014. All rights reserved.
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()18
Copyright Hitachi Ltd. 2013. All rights reserved. 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カウンタの進む速度が変わるケース
プロセスのアカウンティングに使うランキュー操作時刻を更新 • さらにCPUごとの割り込み処理時間&仮想マシン実行時間の統計情
ランキューのデータを保護するためスピンロックを取得 (割り込みコンテキストなのでraw_)
Copyright Hitachi Ltd. 2013. All rights reserved. 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. 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. 2014. All rights reserved.
hrtick()• 通常のtickは1msおき(HZ=1000の場合)
• プロセス切替は、カレントプロセスがタイムスライスを使い切った後のtickで実行される
• タイムスライスを使い切るタイミングでtickを上げることで高精度な時分割を実現
• HRTICK機能がONの場合(/sys/kernel/debug/sched_features)
22
Copyright Hitachi Ltd. 2013. All rights reserved. 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. 2014. All rights reserved.
try_to_wake_up()• 下記の3関数から呼ばれる
• wake_up_process()
• TASK_{UN}INTERRUPTIBLEのプロセスのみ起床
• wake_up_state()
• 指定した状態のプロセスのみ起床
• default_wake_function()
• wait queue等のコールバックとして使われる
• wake_flagsを使用(今回は未調査)
24
Copyright Hitachi Ltd. 2014. All rights reserved. 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がまだランキューに残っている場合、ttwu_remoteを呼び出してランキュー操作の不要な軽量起床を行う 1. pと起床先CPUのカレントプロセスを比較して必要ならプリエンプト要求を出す
2. pの状態をTASK_RUNNINGへ戻す 3. pが起床先CPUですぐに実行できない場合、他CPUで実行できるか試みる(SMPかつrt, deadlineクラスのみ)
p: 起床させるプロセス state: pがどの状態であれば起床させるかの条件 wake_flags:
Copyright Hitachi Ltd. 2014. All rights reserved. 26
#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); 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; }
起床先CPUで(ランキューから外されている)プロセスpが別のプロセスへ切り替わるのを待つ
プロセスpの状態をWAKING(起床中)に変更
プロセスpの所属クラスのtask_waking()メソッド呼び出し • fairクラスのみ: 仮想実行時間(vruntime)の補充
Copyright Hitachi Ltd. 2014. All rights reserved. 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); 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の所属クラスのselect_task_rq()メソッドを呼び出し、起床先CPUを決定
プロセスpの元の動作CPUと起床先CPUが異なる場合、pの動作CPUを新たにセットする
Copyright Hitachi Ltd. 2014. All rights reserved. 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; ! 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; }
基本パス 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間でランキューのロック競合を起こさずに済む
Copyright Hitachi Ltd. 2014. All rights reserved.
schedule()• 明示的な呼び出し
• ミューテックス、セマフォ、wait-queue
• プリエンプションによる呼び出し
• カレントプロセスにTIF_NEED_RESCHEDフラグが立っている状態でチェックポイント(後述)を実行した時
29
Copyright Hitachi Ltd. 2014. All rights reserved.
TIF_NEED_RESCHEDの チェックポイント
• CONFIG_PREEMPT=y の時
• システムコールや例外中にプリエンプションが許可された時
• 割り込みハンドラ終了時
• CONFIG_PREEMPT is not set の時
• cond_resched()が呼ばれた時
• システムコール/例外/割り込みからユーザ空間へ戻る時
30
Copyright Hitachi Ltd. 2014. All rights reserved.
schedule()
31
asmlinkage __visible void __sched schedule(void) { struct task_struct *tsk = current; ! sched_submit_work(tsk); __schedule(); }
スリープする前にキューイングしておいたブロックI/Oをsubmitする
Copyright Hitachi Ltd. 2014. All rights reserved. 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. 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 { 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);
prevプロセスがTASK_RUNNINGでない && 明示的な__schedule()呼び出し
Copyright Hitachi Ltd. 2014. All rights reserved. 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 { 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);
prevプロセスにペンディング中のシグナルがある場合、prevを再度RUNNING状態へ戻す
Copyright Hitachi Ltd. 2014. All rights reserved. 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; ! 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);
シグナルペンディング中でないなら、prevプロセスをランキューから外す
Copyright Hitachi Ltd. 2014. All rights reserved. 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)) { 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; }
全スケジューリングクラスのプロセスのうち、最も優先度の高いものをnextプロセスとして選択
prev != nextならプロセス切り替えへ入る。 カレントプロセスをnextに変更。
プロセス間のコンテキストを切り替える。 戻ってきた時にはrq->lockはunlockされている。
カーネルスタックが切り替わっているので、ローカル変数を更新
全スケジューリングクラスのプロセスのうち、最も優先度の高いものをnextプロセスとして選択
prev != nextならプロセス切り替えへ入る。 カレントプロセスをnextに変更。
プロセス間のコンテキストを切り替える。 戻ってきた時にはrq->lockはunlockされている。
Copyright Hitachi Ltd. 2014. All rights reserved. 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()をネストさせないようにプリエンプション処理は関数内のループにより行う