Mac OS Xのカーネル Xnuのメモ書き

スレッド


スレッドの構造体はstruct thread。以下に主なフィールドを示す。

フィールド
用途
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_shift
pri_shiftは以下のようにsched_usageをプライオリティに変換してスレッドのプライオリティを計算するのに使用する。

(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は加算されない。

(*4) CPU使用量
CPU利用率(%)ではなくCPU使用時間をベースにした値。スレッドのCPU利用率の計算参照。



最終更新 2006/06/01 00:06:25 - kztomita
(2006/05/26 11:53:38 作成)