スレッド
スレッドの構造体はstruct thread。以下に主なフィールドを示す。
(*1) カーネルスタックの予約
(*2) スタックの割り当てルーチン
フィールド | 用途 |
---|---|
runq | スレッドが属しているRunQueueへのポインタ。RunQueueに入っていない場合はRUN_QUEUE_NULL。 |
wait_queue | スレッドがWaitQueueに入っている時は、WaitQueueへのポインタ。 そうでなければ、WAIT_QUEUE_NULL。 |
reason | ブロック要因 AST_QUANTUM CPU時間(Quantum)を使いきった。 AST_HANDOFF CPU時間が残っているのに切替えた。(現状Idleプロセスのみ?) AST_PREEMPT プリエンプトされた。 AST_YIELD thread_switch()(Machのスレッドインタフェース)などによって明示的にコンテキストスイッチが発生 |
continuation | ブロックしているスレッドの再開ポイント。 thread_block()でスレッドをブロックさせる時に指定できる。 |
kernel_stack | KernelStackへのポインタ。continuation付きでブロックした場合など、カーネルスタックが他のスレッドに流用される場合があり、この場合、kernel_stackはNULLとなる(コンテキストスイッチ参照)。 |
reserved_stack | KernelStackを予約しておくポインタ。予約をしておくとstack_alloc_try()が必ず成功する(スタックが割り当てられなかった場合は、予約していたスタックを割り当てるので)。リアルタイムプロセスでスタックが割り当てられずに実行が遅延するのを防ぐことができる。(*1) |
state | スレッドの状態を示すBitMask TH_WAIT スレッドが待ち状態。WaitQueueにつながれている。 TH_SUSP Suspend状態 TH_RUN Running状態 TH_UNINT シグナルを受け付けない状態での待ち。シグナルが来てもWakeupしない。(TH_WAIT | TH_UNINT)のようにTH_WAITと組み合わされて使われる。 : |
sched_mode | スケジューリングモードのビットマップ TH_MODE_REALTIME リアルタイムプロセス TH_MODE_TIMESHARE ラウンドロビンで動作 TH_MODE_PREEMPT カーネルコンテキスト実行中でもプリエンプトできるプロセス : |
sched_pri | スケジューリングで使用される優先度。priorityを元に計算される。更新はキューの場所が変わる可能性があるのでset_sched_pri()を通して行う。 |
priority | 優先度の基本値。値が大きいほど優先度が高い。osfmk/kern/sched.h参照。 通常のスレッドはBASEPRI_DEFAULT(31) |
current_quantum | Quantum値 このスレッドがCPUを使える残り時間(ms)。Quantum値がなくなるとコンテキストスイッチが発生する(要因:AST_QUANTUM)。 |
system_timer | CPUを使用したsystem時間。 |
user_timer | CPUを使用したuser時間。 |
bound_processor | スレッドを実行するCPUを固定している場合、そのprocessorへのポインタ。thread_bind()でCPUを割り当てる。Idleスレッドなどは各CPUにbindされている。 |
last_processor | スレッドが最後に実行されたprocessorへのポインタ。 |
sched_usage | タイムシェアリングに使ったCPU使用量(?)。cpu_usageと似ているが、CPUあたりのタイムシェアリングスレッド実行数が1未満の場合は、使用量は加算されない。スケジューリング用のプロセスの優先度を算出するのに使われる。 2CPUで1つのタイムシェアリングスレッドしか動作していない場合など、sched_usageは加算されないので、プライオリティの余計な減算がなくなる? |
pri_shift | 使用量(sched_usage)をPriorityに変換するshift値。(*3) |
cpu_usage | CPU使用量 (*4) |
map | アドレスマップ(vmmap)へのポインタ。 |
(*1) カーネルスタックの予約
Xnuではプロセスがブロックした時、カーネルスタックが解放される場合があるため、thread_invoke()でコンテキストスイッチする際に新スレッドへのカーネルスタックの割り当て処理が走ることがある。スタックの割り当てに失敗した場合、Thread Stack
Daemonによるスタック割り当てが完了するまでスレッドの実行が遅れてしまい、リアルタイムプロセスでは致命的となる。このため、thread_invoke()で新スレッドがリアルタイムプロセスだった場合はthread->reserved_stackにスタックを予約している。予約をしておくとstack_alloc_try()によるスタック割り当ては必ず成功するようになる(*2 参照)ので余計な遅延がなくなる。
リアルタイムプロセスの他にカーネルスレッドもスタックを予約している(kernel_thread_create())。カーネルスレッドもリアルタイム性が重要なためだと思われる。
そもそも、リアルタイムプロセスなどではスタックを解放しないようにした方がいいような気がしないでもない。
(*2) スタックの割り当てルーチン
stack_alloc()はスタックキャッシュ(CPU毎にある)からスタックを取得しスレッドのカーネルスタックとして割り当てる。キャッシュになければ、メモリを確保してスタックを割り当てる。メモリ確保の際、ブロックする可能性がある。これに対しstack_alloc_try()はブロックは発生しない。スタックキャッシュからスタックを取得できなかった場合は、予約済みのスタックがあればそれをカーネルスタックに割り当てる。予約スタックもなければエラーリターンする。
(*3) pri_shiftpri_shiftは以下のようにsched_usageをプライオリティに変換してスレッドのプライオリティを計算するのに使用する。
pri_shiftは以下のように負荷(load_now)によって値が変わる(compute_averages()で算出)。負荷が高いほどpri_shiftは小さくなり、結果としてpriが小さくなる(優先度が低くなる)
sched_pri_shift:
load_now:
sched_load_shifts[]の格納値:
(*4) CPU使用量(pri) = (thread)->priority - ((thread)->sched_usage >>
(thread)->pri_shift);
(do_priority_computation()によるプライオリティの計算式)
pri_shiftは以下のように負荷(load_now)によって値が変わる(compute_averages()で算出)。負荷が高いほどpri_shiftは小さくなり、結果としてpriが小さくなる(優先度が低くなる)
pset->pri_shift = sched_pri_shift - sched_load_shifts[load_now];
sched_pri_shift:
pri_shiftのベース値。CPUを100%使った時のsched_usageの値(sched_tick_interval*5/3)をshiftしてちょうどBASEPRI_DEFAULT(31)未満になるように設定されている。
load_now:
CPUあたりのRunning状態のタイムシェアリングスレッド数
sched_load_shifts[]の格納値:
-128, 0, 1, 1, 2, 2, 2, 2, 3, 3,...3が合わせて8回つづく, 4, ...
無負荷(load_now=0)の時はpset->pri_shiftはINT8_MAXより大きくなるのでthread->sched_usageに新たなdeltaは加算されない。
無負荷(load_now=0)の時はpset->pri_shiftはINT8_MAXより大きくなるのでthread->sched_usageに新たなdeltaは加算されない。