netbsdのソフト割り込み

ちょいと訳あって、netbsdのcurrentのスケジューリング方式を知りたくなった。
その中でも割り込み系の扱いを知りたくなったのでソースを読んだので、メモとして残す。

(注)以下の説明は、__HAVE_FAST_SOFTINTSが定義されていない場合を想定しています。
また、rumpも本文書の対象外です。


[ソフト割り込み]
ソフト割り込みはカーネルスレッドで実現されている。

スレッド生成はブート時に行われ、main() --> mi_cpu_attach() --> softint_init() --> softint_init_isr() --> kthread_create()の流れで行われる。

softint_init_isr()では、kthread_create()の第二引数のflagにKTHREAD_INTRを指定してスレッドを生成する。
これによって、スレッド構造体(lwp)のl_pflagにLP_INTRが立ち、「該当スレッドがソフト割り込みを処理するスレッド」ということを明示することができる。

ソフト割り込みスレッドは、4つあり、それぞれ以下の役割と優先度を持つ。
なお、4つともスレッドの実体はsoftint_thread()である。
softnet ... 優先度222
softbio ... 優先度221
softclock ... 優先度220
softserial ... 優先度223
いずれもカーネル内リアルタイム処理の優先度範囲に属する。

[ソフト割り込みの実行]
ソフト割り込みを起こしたい場合、softint_schedule()を呼び出す。
もし、ソフト割り込みを実行できる状態であれば、softint_trigger()がsoftint_schedule()から呼ばれる。
softint_trigger()では以下の処理を行う。
(1)現在のスレッドが動作しているCPUの情報(cpu_info)を取得する。
(2)(1)で取得した情報のci_data.cpu_softintsにソフト割り込み要因を設定する。
これによってci_data.cpu_softintsは0以外の値になる。
(3)スケジューリングが必要な旨のフラグを立てる。この処理はCPUアーキ依存であるが大体aston()を呼ぶ。これによって、以下の2つのルートのいずれかからmi_switch()がコールされ、スレッドスケジューラが動作する。

ルート1.例外の後処理のAST(asynchronous system trap)内からpreempt()経由でコール。呼ばれるタイミングはCPUアーキによって多少異なる。
ルート2.アイドルスレッドがmi_switch()をコールする。

mi_switch()には切り替え対象のスレッドを選択する処理がある。
この中に以下のコードが存在する。

	else if (ci->ci_data.cpu_softints != 0) {
		/* There are pending soft interrupts, so pick one. */
		newl = softint_picklwp();
		newl->l_stat = LSONPROC;
		newl->l_pflag |= LP_RUNNING;
	}


softint_trigger()の(2)では、cpu_softintsに0以外の値を設定したので、softint_pickup()がコールされる。
softint_pickup()は、cpu_softintsに設定された要因を優先度の高いものから順に見て、最初に見つけた要因に対応したカーネルスレッドを返す。

そして、softint_pickup()で取得したスレッドにスイッチングし、ソフト割り込みが処理される。

[ソフト割り込みの処理]
ソフト割り込みを処理するスレッドの実体であるsoftint_thread()は以下の処理を行う。
(1)softint_execute()を呼び出し、キューイングされている処理を実行する。
ソフト割り込みでの処理要求はl_private内にあるsoftint_t型変数内のsi_qにキューイングされている。

(2)自分のlwp構造体のl_statをISIDLにする。
これを行うことでこのスレッドは実行可能状態(LSRUN)でなくなる。
つまり、スレッドスケジューラは用のないときにソフト割り込みスレッドを実行対象スレッドとして選択することがない。

[特例的処理]

lwp_userret()からsoftint_overlay()という処理がコールされる。
要は、カーネル -> ユーザランドへ処理が戻るタイミングでもソフト割り込みを処理すると思えばよい。

この中でソフト割り込みを処理する際には、先に書いたソフト割り込み用のカーネルスレッドでなく、現在のスレッドを一時的にソフト割り込み用スレッドとして扱う。
具体的には、LP_INTRを一時的に現在のlwpのl_pflagに設定する。

そして、各ソフト割り込み要因ごとに要因の有無をチェックし、もし要因があればsoftint_execute()をsoftint_overlay()から呼び出す。

ソフト割り込みの処理を全て終了したら、lwpのl_pflagのLP_INTRビットを落として処理を完了する。

[データ構造]
cpu_info内にはcpu_data構造体(へのポインタ)がある。
cpu_data構造体はcpu_softcpuというソフト割り込みに関する情報を保持する。
この中の値はsoftcpu_t型の値が格納されており、sc_int配列が存在する。
この配列はソフト割り込みの種別個だけ要素が存在し、softint_t型である。
softint_t型は以下のとおりである。

typedef struct softint {
	SIMPLEQ_HEAD(, softhand) si_q;
	struct lwp		*si_lwp;
	struct cpu_info		*si_cpu;
	uintptr_t		si_machdep;
	struct evcnt		si_evcnt;
	struct evcnt		si_evcnt_block;
	int			si_active;
	char			si_name[8];
	char			si_name_block[8+6];
} softint_t;


この中でも重要なのが以下2点。
(1)si_lwpでソフト割り込みを処理するカーネルスレッドが格納される。ブート時にスレッド生成した時に値が格納され、softint_pickup()でこの値が取り出される。
(2)si_q。この中にはsofthand_t型のエントリが格納される。要するに関数ポインタと引数の組が格納されると思えばよい。

typedef struct softhand {
	SIMPLEQ_ENTRY(softhand)	sh_q;
	void			(*sh_func)(void *);
	void			*sh_arg;
	softint_t		*sh_isr;
	u_int			sh_flags;
} softhand_t;