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でも基本はあまり違わないのではないだろうか。

*1:IPを配信できるということはRFC 1055なのかな? オリジナル1822とformat flagが同じなので区別できない。