FreeBSD kernel SOCKET I/F 探検

第9回 socket() システムコール (7)

2014.03.20

必要とする知識など:
  • UNIX (含む Linux) 上での C 言語と SOCKET によるプログラミング経験および TCP/IP に関する知識。

socket() システムコール、七回目です。socreate() の続きです。

CURVNET_SET(so->so_vnet);
error = (*prp->pr_usrreqs->pru_attach)(so, proto, td);
CURVNET_RESTORE();

CURVNET_SET()/CURVNET_RESTORE() は、/usr/src/sys/net/vnet.h (http://svnweb.freebsd.org/base/release/9.1.0/sys/net/vnet.h?view=markup) で以下のように定義されています。

#ifdef VNET_DEBUG
...
#else /* !VNET_DEBUG */

#define CURVNET_SET_QUIET(arg)                                          \
        VNET_ASSERT((arg) != NULL && (arg)->vnet_magic_n == VNET_MAGIC_N, \
            ("CURVNET_SET at %s:%d %s() curvnet=%p vnet=%p",            \
            __FILE__, __LINE__, __func__, curvnet, (arg)));             \
        struct vnet *saved_vnet = curvnet;                              \
        curvnet = arg;

#define CURVNET_SET_VERBOSE(arg)                                        \
        CURVNET_SET_QUIET(arg)

#define CURVNET_SET(arg)        CURVNET_SET_VERBOSE(arg)

#define CURVNET_RESTORE()                                               \
        VNET_ASSERT(curvnet != NULL && (saved_vnet == NULL ||           \
            saved_vnet->vnet_magic_n == VNET_MAGIC_N),                  \
            ("CURVNET_RESTORE at %s:%d %s() curvnet=%p saved_vnet=%p",  \
            __FILE__, __LINE__, __func__, curvnet, saved_vnet));        \
        curvnet = saved_vnet;
#endif /* VNET_DEBUG */

GENERIC kernel のコンパイル時に VNET_DEBUG は定義されていないようです。また、VNET_ASSERT() は同じ /usr/src/sys/net/vnet.h で以下のように定義されています。

#if defined(INVARIANTS) || defined(VNET_DEBUG)
...
#else
#define VNET_ASSERT(exp, msg)   do {                                    \
} while (0)
#endif

以上のことから、CURVNET_SET()/CURVNET_RESTORE() を整理すると以下のようになります。

#define CURVNET_SET(arg)                                                \
        struct vnet *saved_vnet = curvnet;                              \
        curvnet = arg;

#define CURVNET_RESTORE()                                               \
        curvnet = saved_vnet;

curvnet は、/usr/src/sys/net/vnet.h で以下のように定義されています。

#define curvnet curthread->td_vnet

curthread は各アーキテクチャ毎に定義されているようです。i386 の場合、/usr/src/sys/i386/include/pcpu.h で以下のように定義されています。

__curthread(void)
{
        struct thread *td;

        __asm("movl %%fs:0,%0" : "=r" (td));
        return (td);
}
#define curthread               (__curthread())

恐らく現在処理中のスレッド用の情報 (struct thread) のポインタを取得しているものと思われます。

ここまでの情報より、上記 socreate() 内の CURVNET_SET()~ CURVNET_RESTORE() の部分を展開したものを以下に示します。

struct vnet *saved_vnet = (__curthread())->td_vnet;
(__curthread())->td_vnet = so->so_vnet;
error = (*prp->pr_usrreqs->pru_attach)(so, proto, td);
(__curthread())->td_vnet = saved_vnet;

結局 (* prp->pr_userreqs->pru_attach)() の呼び出しの前に (__curthread())->td_vnet を so->so_vnet に書き換え、(* prp->pr_usrreqs->pru_attach)() から戻ったところで、(__curthread())->td_vnet を元に戻しています。

なお、so->so_vnet はここまで初期化されていませんが、soalloc() で struct socket 用の領域を確保する際、以下のように uma_zalloc() の第二引数に M_ZERO を指定しています。

so = uma_zalloc(socket_zone, M_NOWAIT | M_ZERO);

uma_zalloc() は最終的に zone_alloc_item() (/usr/src/sys/vm/uma_core.c (http://svnweb.freebsd.org/base/release/9.1.0/sys/vm/uma_core.c?view=markup)) を呼び出してメモリを確保しますが、zone_alloc_item() は M_ZERO を指定すると、bzero() を使用して確保した領域にゼロを書き込みます。従って、ポインタは NULL に初期化されているものとします。

prp->pr_usrreqs->pru_attach は udp_attach() のポインタが格納されています。udp_attach() は、/usr/src/sys/netinet/udp_usrreq.c (http://svnweb.freebsd.org/base/release/9.1.0/sys/netinet/udp_usrreq.c?view=markup) で以下のように定義されています。

udp_attach(struct socket *so, int proto, struct thread *td)
{
        struct inpcb *inp;
        int error;

struct inpcb は、/usr/src/sys/netine/in_pcb.h (http://svnweb.freebsd.org/base/release/9.1.0/sys/netinet/in_pcb.h?view=markup) で定義されています。

それでは、udp_attach() の処理を見ていきます。

inp = sotoinpcb(so);
KASSERT(inp == NULL, ("udp_attach: inp != NULL"));

sotoinpcb() は、/usr/src/sys/netinet/in_pcb.h (http://svnweb.freebsd.org/base/release/9.1.0/sys/netinet/in_pcb.h?view=markup) で以下のように定義されています。

#define sotoinpcb(so)   ((struct inpcb *)(so)->so_pcb)

ローカル変数 struct inpcb * inp に so->so_pcb を代入し、KASSERT() で inp が NULL であることを確認しています。so->so_pcb はここまで初期化されていませんが、上記の so_vnet と同様に NULL で初期化されているものとみなします。

ちなみに、so->so_pcb は /usr/src/sys/sys/socketvar.h (http://svnweb.freebsd.org/base/release/9.1.0/sys/sys/socketvar.h?view=markup) で以下のように定義されているため、

void    *so_pcb;                /* protocol control block */

sotoinpcb() ではキャスト ((struct inpcb * )...) を行なっています。

次の処理は、soreserve() の呼び出しです。

error = soreserve(so, udp_sendspace, udp_recvspace);

ここで渡している udp_sendspace, udp_recvspace は、/usr/src/sys/netinet/udp_usrreq.c (http://svnweb.freebsd.org/base/release/9.1.0/sys/netinet/udp_usrreq.c?view=markup) で以下のように定義されています。

u_long  udp_sendspace = 9216;           /* really max datagram size */
                                        /* 40 1K datagrams */
SYSCTL_ULONG(_net_inet_udp, UDPCTL_MAXDGRAM, maxdgram, CTLFLAG_RW,
    &udp_sendspace, 0, "Maximum outgoing UDP datagram size");

u_long  udp_recvspace = 40 * (1024 +
#ifdef INET6
                                      sizeof(struct sockaddr_in6)
#else
                                      sizeof(struct sockaddr_in)
#endif
                                      );

SYSCTL_ULONG(_net_inet_udp, UDPCTL_RECVSPACE, recvspace, CTLFLAG_RW,
    &udp_recvspace, 0, "Maximum space for incoming UDP datagrams");

いずれも SYSCTL(8) コマンドで変更可能な値です。

GENERIC kernel のコンパイル時に INET6 が定義されているので、udp_recvspace の定義は以下のようになります。

u_long  udp_recvspace = 40 * (1024 + sizeof(struct sockaddr_in6));

soreserve() は、/usr/src/sys/kern/uipc_sockbuf.c (http://svnweb.freebsd.org/base/release/9.1.0/sys/kern/uipc_sockbuf.c?view=markup) で以下のように定義されています。

soreserve(struct socket *so, u_long sndcc, u_long rcvcc)
{
        struct thread *td = curthread;

        SOCKBUF_LOCK(&so->so_snd);
        SOCKBUF_LOCK(&so->so_rcv);
        if (sbreserve_locked(&so->so_snd, sndcc, so, td) == 0)
                goto bad;
        if (sbreserve_locked(&so->so_rcv, rcvcc, so, td) == 0)
                goto bad2;
        if (so->so_rcv.sb_lowat == 0)
                so->so_rcv.sb_lowat = 1;
        if (so->so_snd.sb_lowat == 0)
                so->so_snd.sb_lowat = MCLBYTES;
        if (so->so_snd.sb_lowat > so->so_snd.sb_hiwat)
                so->so_snd.sb_lowat = so->so_snd.sb_hiwat;
        SOCKBUF_UNLOCK(&so->so_rcv);
        SOCKBUF_UNLOCK(&so->so_snd);
        return (0);
bad2:
        sbrelease_locked(&so->so_snd, so);
bad:
        SOCKBUF_UNLOCK(&so->so_rcv);
        SOCKBUF_UNLOCK(&so->so_snd);
        return (ENOBUFS);
}

先ほど出てきた curthread がここで使用されています。なぜ引数で渡さずにこのような処理になっているのかは不明です。

SOCKBUF_LOCK()/SOCKBUF_UNLOCK()は、/usr/src/sys/sys/sockbuf.h (http://svnweb.freebsd.org/base/release/9.1.0/sys/sys/sockbuf.h?view=markup) で以下のように定義されています。

#define SOCKBUF_LOCK(_sb)               mtx_lock(SOCKBUF_MTX(_sb))
#define SOCKBUF_UNLOCK(_sb)             mtx_unlock(SOCKBUF_MTX(_sb))

また、ここで使用している SOCKBUF_MTX() も同じ /usr/src/sys/sys/sockbuf.h で以下のように定義されています。

#define SOCKBUF_MTX(_sb)                (&(_sb)->sb_mtx)

詳細は割愛しますが、soreserve() の SOCKBUF_LOCK()/SOCKBUF_UNLOC() は、ソケットの送受信バッファに対する ロック/アンロック を実行しています。

次は sbreserve_locked() の呼び出しですが、今回はここで終了です。

著者プロフィール

asou

好きな OS は、FreeBSD です。が、最近購入した Mac mini がなかなか快適なので、Mac でいいかなと思うようになってきました。

記事一覧Index