- 必要とする知識など:
- 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() の呼び出しですが、今回はここで終了です。