netimpのコード
昨日の続きで、netimpのコードを眺めている。まずは上下層とのインタフェースからざっくり全体像をつかんでみる。
netimp(以下、1822と呼ぶ)はネットワーク層プロトコルなので、下位層はデータリンク層であるネットワークインタフェース(NIC)になる。データリンク層とネットワーク層でパケットを授受するために、ネットワーク層プロトコルごとにインタフェースキューが用意されている。インタフェースキューの実装はstruct ifqueueで、IF_ENQUEUE、IF_DEQUEUEなどの操作マクロが定義されている。ここで関係するインタフェースキューは、NICの送信キューであるstruct ifnetのif_sndメンバ変数とimpintrq変数の二つである。
一方、上位層だが、netinet(TCP/IP)のように階層が多段の場合とは違って一階層なので、pr_input、pr_output関数をつかってやりとりすることはない。実際はrawソケットインタフェースで読み書きするか、IPパケットをカプセル化していた場合はIP層にパケットをキューイングし直すことになる*1。
ではoutput側のルーチンを見てみよう。上位層からのエントリポイントはimpoutput関数である。impoutput関数ではリーダ(ヘッダ)を初期化し、impsnd関数を呼ぶ。impsndではインタフェースの送信キューにパケットを挿入している。ポイントを抜粋する。
if (IF_QFULL(&ifp->if_snd)) { IF_DROP(&ifp->if_snd); m_freem(m); } IF_ENQUEUE(&inp->if_snd, m);
input側はもう少し複雑だ。下位層からのエントリポイントはimpinput関数である。impinput関数ではコントロールメッセージであればすぐ処理する。通常メッセージの場合、それがIPデータグラムであれば、ソフトウェア割込みNETISR_IPをスケジュールしてパケットをインタフェースキューipintrqに追加する。1822メッセージであればソフトウェア割込みNETISR_IMPをスケジュールしてインタフェースキューimpintrqに追加する。ポイントを抜粋する。
switch (ip->il_mtype) { case IMPTYPE_DATA: switch (ip->il_link) { case IMPLINK_IP: schednetisr(NETISR_IP); inq = &ipintrq; } } if (inq == &impintrq) schednetisr(NETISR_IMP); if (IF_QFULL(inq)) { IF_DROP(inq); m_freem(m); } IF_ENQUEUE(inq, m);
ソフトウェア割込みのエントリポイントはimpintr関数である。そして本関数ではキューimpintrqからメッセージを取り出してraw_input関数を呼ぶ。ポイントを抜粋する。
for (;;) { s = splimp(); IF_DEQUEUEIF(&impintrq, m, ifp); splx(s); if (m == 0) return; raw_intput(...);
ここまではあまりプロトコルに依存してないし、一般的な話だと思う。最近のBSDでも基本はあまり違わないのではないだろうか。