- 必要とする知識など:
- UNIX (含む Linux) 上での C 言語と SOCKET によるプログラミング経験および TCP/IP に関する知識。
bind() システムコール、六回目です。sys_bind() ->kern_bind() -> sobind() -> udp_bind() -> in_pcbbind() -> in_pcbbind_setup() を呼び出して、in_pcbbind_setup() から in_pcbbind() に戻ったところです。以下が、該当部分です。
error = in_pcbbind_setup(inp, nam, &inp->inp_laddr.s_addr,
&inp->inp_lport, cred);
if (error)
return (error);
if (in_pcbinshash(inp) != 0) {
inp->inp_laddr.s_addr = INADDR_ANY;
inp->inp_lport = 0;
return (EAGAIN);
}
if (anonport)
inp->inp_flags |= INP_ANONPORT;
return (0);
}
in_pcbbind_setup() からは 0 が返却されているので、三行目の if (error) は偽となり、次の処理に進みます。
if (in_pcbinshash(inp) != 0) {
in_pcbinshash() は in_pcbbind() と同じく、/usr/src/sys/netinet/in_pcb.c (http://svnweb.freebsd.org/base/release/9.1.0/sys/netinet/in_pcb.c?view=markup) で以下のように定義されています。
/*
* For now, there are two public interfaces to insert an inpcb into the hash
* lists -- one that does update pcbgroups, and one that doesn't. The latter
* is used only in the TCP syncache, where in_pcbinshash is called before the
* full 4-tuple is set for the inpcb, and we don't want to install in the
* pcbgroup until later.
*
* XXXRW: This seems like a misfeature. in_pcbinshash should always update
* connection groups, and partially initialised inpcbs should not be exposed
* to either reservation hash tables or pcbgroups.
*/
int
in_pcbinshash(struct inpcb *inp)
{
return (in_pcbinshash_internal(inp, 1));
}
int
in_pcbinshash_nopcbgroup(struct inpcb *inp)
{
return (in_pcbinshash_internal(inp, 0));
}
コメントに色々と記述されていますが、今回使用するのは前者の in_pcbinshash() です。
in_pcbinshash() は、in_pcbinshhash_internal() を呼び出しているだけですが、この in_pcbinshhash_internal() も同様に、/usr/src/sys/netinet/in_pcb.c で以下のように定義されています。
/*
* Insert PCB onto various hash lists.
*/
static int
in_pcbinshash_internal(struct inpcb *inp, int do_pcbgroup_update)
{
struct inpcbhead *pcbhash;
struct inpcbporthead *pcbporthash;
struct inpcbinfo *pcbinfo = inp->inp_pcbinfo;
struct inpcbport *phd;
u_int32_t hashkey_faddr;
INP_WLOCK_ASSERT(inp);
INP_HASH_WLOCK_ASSERT(pcbinfo);
KASSERT((inp->inp_flags & INP_INHASHLIST) == 0,
("in_pcbinshash: INP_INHASHLIST"));
#ifdef INET6
if (inp->inp_vflag & INP_IPV6)
hashkey_faddr = inp->in6p_faddr.s6_addr32[3] /* XXX */;
else
#endif /* INET6 */
hashkey_faddr = inp->inp_faddr.s_addr;
pcbhash = &pcbinfo->ipi_hashbase[INP_PCBHASH(hashkey_faddr,
inp->inp_lport, inp->inp_fport, pcbinfo->ipi_hashmask)];
pcbporthash = &pcbinfo->ipi_porthashbase[
INP_PCBPORTHASH(inp->inp_lport, pcbinfo->ipi_porthashmask)];
ローカル変数宣言の三行目で pcbinfo の宣言と初期化を実行しています。
struct inpcbinfo *pcbinfo = inp->inp_pcbinfo;
この処理で inp->inp_pcbinfo に格納されているのは、グローバル変数struct inpcbinfo udbinfo へのポインタです。ということで、pcbinfo には struct inpcbinfo udbinfo へのポインタが代入されています。
この struct inpcbinfo udbinfo は、本連載の 十一回目 で簡単に解説しているだけでしたが、今回はこの struct inpcbinfo udbinfoについて少し説明します。なお、以後 udbinfo と省略して表記します。
udbinfo は、SOCKET(2) システムコールの呼出時にその領域を割り当てる struct socket や struct inpcb とは異なり、カーネルの起動時のネットワーク層の初期化処理で初期化されています。
udbinfo の初期化は、in_pcbinfo_init() で実施しています。この関数は、/usr/src/sys/netinet/in_pcb.c (http://svnweb.freebsd.org/base/release/9.1.0/sys/netinet/in_pcb.c?view=markup) で以下のように定義されています。
void
in_pcbinfo_init(struct inpcbinfo *pcbinfo, const char *name,
struct inpcbhead *listhead, int hash_nelements, int porthash_nelements,
char *inpcbzone_name, uma_init inpcbzone_init, uma_fini inpcbzone_fini,
uint32_t inpcbzone_flags, u_int hashfields)
{
INP_INFO_LOCK_INIT(pcbinfo, name);
INP_HASH_LOCK_INIT(pcbinfo, "pcbinfohash"); /* XXXRW: argument? */
#ifdef VIMAGE
pcbinfo->ipi_vnet = curvnet;
#endif
pcbinfo->ipi_listhead = listhead;
LIST_INIT(pcbinfo->ipi_listhead);
pcbinfo->ipi_count = 0;
pcbinfo->ipi_hashbase = hashinit(hash_nelements, M_PCB,
&pcbinfo->ipi_hashmask);
pcbinfo->ipi_porthashbase = hashinit(porthash_nelements, M_PCB,
&pcbinfo->ipi_porthashmask);
#ifdef PCBGROUP
in_pcbgroup_init(pcbinfo, hashfields, hash_nelements);
#endif
pcbinfo->ipi_zone = uma_zcreate(inpcbzone_name, sizeof(struct inpcb),
NULL, NULL, inpcbzone_init, inpcbzone_fini, UMA_ALIGN_PTR,
inpcbzone_flags);
uma_zone_set_max(pcbinfo->ipi_zone, maxsockets);
}
in_pcbinfo_init() は、udp_init() から呼び出されます。udp_init() は、/usr/src/sys/netinet/udp_usrreq.c (http://svnweb.freebsd.org/base/release/9.1.0/sys/netinet/udp_usrreq.c?view=markup) で以下のように定義されています。
void
udp_init(void)
{
in_pcbinfo_init(&V_udbinfo, "udp", &V_udb, UDBHASHSIZE, UDBHASHSIZE,
"udp_inpcb", udp_inpcb_init, NULL, UMA_ZONE_NOFREE,
IPI_HASHFIELDS_2TUPLE);
V_udpcb_zone = uma_zcreate("udpcb", sizeof(struct udpcb),
NULL, NULL, NULL, NULL, UMA_ALIGN_PTR, UMA_ZONE_NOFREE);
uma_zone_set_max(V_udpcb_zone, maxsockets);
EVENTHANDLER_REGISTER(maxsockets_change, udp_zone_change, NULL,
EVENTHANDLER_PRI_ANY);
}
udp_init() は、/usr/src/sys/netinet/in_proto.c (http://svnweb.freebsd.org/base/release/9.1.0/sys/netinet/in_proto.c?view=markup) で定義されている struct protosw inetsw[] に、以下のように関数のポインタが格納されています。
struct protosw inetsw[] = {
{
.pr_type = SOCK_DGRAM,
.pr_domain = &inetdomain,
.pr_protocol = IPPROTO_UDP,
.pr_flags = PR_ATOMIC|PR_ADDR,
.pr_input = udp_input,
.pr_ctlinput = udp_ctlinput,
.pr_ctloutput = udp_ctloutput,
.pr_init = udp_init,
#ifdef VIMAGE
.pr_destroy = udp_destroy,
#endif
.pr_usrreqs = &udp_usrreqs
},
ここで .pr_init に代入している関数 (udp_init) へのポインタは、/usr/src/sys/kern/uipc_domain.c (http://svnweb.freebsd.org/base/release/9.1.0/sys/kern/uipc_domain.c?view=markup) で定義されている protosw_init() 関数の最後で、以下のように呼び出されています。
static void
protosw_init(struct protosw *pr)
{
:
:
if (pr->pr_init)
(*pr->pr_init)();
}
protosw_init() は、同じく uipc_domain.c で定義されている domain_init() から、以下のように呼び出されています。
void
domain_init(void *arg)
{
struct domain *dp = arg;
struct protosw *pr;
if (dp->dom_init)
(*dp->dom_init)();
for (pr = dp->dom_protosw; pr < dp->dom_protoswNPROTOSW; pr++)
protosw_init(pr);
domain_init() は、同じく uipc_domain.c で定義されている vnet_domain_init()から、以下のように呼び出されています。
#ifdef VIMAGE
void
vnet_domain_init(void *arg)
{
/* Virtualized case is no different -- call init functions. */
domain_init(arg);
}
が、#ifdef VIMAGE ~ #endif [1] で括られているのでこれではなく、
/usr/src/sys/sys/domain.h (http://svnweb.freebsd.org/base/release/9.1.0/sys/sys/domain.h?view=markup) で定義されている以下のコードが使用されているものと思われます。
#define DOMAIN_SET(name) \
SYSINIT(domain_add_ ## name, SI_SUB_PROTO_DOMAIN, \
SI_ORDER_FIRST, domain_add, & name ## domain); \
SYSINIT(domain_init_ ## name, SI_SUB_PROTO_DOMAIN, \
SI_ORDER_SECOND, domain_init, & name ## domain);
呼出元を辿っていくのはここまでとして、in_pcbinfo_init() での udbinfo の初期化に関して調べていきます。
先ほど掲載した udp_init() を再度掲載します。
void
udp_init(void)
{
in_pcbinfo_init(&V_udbinfo, "udp", &V_udb, UDBHASHSIZE, UDBHASHSIZE,
"udp_inpcb", udp_inpcb_init, NULL, UMA_ZONE_NOFREE,
IPI_HASHFIELDS_2TUPLE);
V_udpcb_zone = uma_zcreate("udpcb", sizeof(struct udpcb),
NULL, NULL, NULL, NULL, UMA_ALIGN_PTR, UMA_ZONE_NOFREE);
uma_zone_set_max(V_udpcb_zone, maxsockets);
EVENTHANDLER_REGISTER(maxsockets_change, udp_zone_change, NULL,
EVENTHANDLER_PRI_ANY);
}
頭に「V_」が付く変数名がいくつか登場します。これに関しては本連載の 十一回目 で解説していますが、「V_」がないものと考えてください。
in_pcbinfo_init() への引数を一つずつ調べていきます。ただし、第二引数の “udp”、第六引数の “udp_inpcb”, 第八引数の NULL に関しては割愛します。
第一引数の udbinfo と第三引数の udb は、は、/usr/src/sys/netinet/udp_usrreq.c (http://svnweb.freebsd.org/base/release/9.1.0/sys/netinet/udp_usrreq.c?view=markup) で以下のように定義されています。
VNET_DEFINE(struct inpcbhead, udb); /* from udp_var.h */
VNET_DEFINE(struct inpcbinfo, udbinfo);
VNET_DEFINE() は、/usr/src/sys/net/net.h (http://svnweb.freebsd.org/base/release/9.1.0/sys/net/vnet.h?view=markup) で以下のように定義されています。
#else /* !VIMAGE */
:
:
#define VNET_DEFINE(t, n) t n
これを上記の 二つの VNET_DEFINE() に適用すると、以下のように展開されます。
struct inpcbhead udb;
struct inpcbinfo udbinfo;
struct inpcbhead と struct inpcbinfo は、/usr/src/sys/netinet/in_pcb.h (http://svnweb.freebsd.org/base/release/9.1.0/sys/netinet/in_pcb.h?view=markup) で以下のように定義されています。
LIST_HEAD(inpcbhead, inpcb);
struct inpcbinfo {
/*
* Global lock protecting global inpcb list, inpcb count, etc.
*/
struct rwlock ipi_lock;
:
:
LIST_HEAD() は、/usr/src/sys/sys/queue.h で (http://svnweb.freebsd.org/base/release/9.1.0/sys/sys/queue.h?view=markup) 以下のように定義されています。
#define LIST_HEAD(name, type) \
struct name { \
struct type *lh_first; /* first element */ \
}
これを上記の LIST_HEAD(inpcbhead, inpcb) に適用すると、以下のように展開されます。
struct inpcbhead {
struct inpcb *lh_first;
};
第四引数と第五引数で使用している UDBHASHSHZE は、/usr/src/sys/netinet/udp_usrreq.c (http://svnweb.freebsd.org/base/release/9.1.0/sys/netinet/udp_usrreq.c?view=markup) で以下のように定義されています。
#define UDBHASHSIZE 128
第七引数の udp_inpcb_init は、同じく /usr/src/sys/netinet/udp_usrreq.c で以下のように定義されています。
static int
udp_inpcb_init(void *mem, int size, int flags)
{
struct inpcb *inp;
inp = mem;
INP_LOCK_INIT(inp, "inp", "udpinp");
return (0);
}
第九引数の UMA_ZONE_NOFREE は、/usr/src/sys/vm/uma.h (http://svnweb.freebsd.org/base/release/9.1.0/sys/vm/uma.h?view=markup) で以下のように定義されています。
/*
* Definitions for uma_zcreate flags
*
* These flags share space with UMA_ZFLAGs in uma_int.h. Be careful not to
* overlap when adding new features. 0xf0000000 is in use by uma_int.h.
*/
:
:
#define UMA_ZONE_NOFREE 0x0020 /* Do not free slabs of this type! */
:
:
第十引数の IPI_HASHFIELDS_2TUPLE は、/usr/src/sys/netinet/in_pcb.h (http://svnweb.freebsd.org/base/release/9.1.0/sys/netinet/in_pcb.h?view=markup) で以下のように定義されています。
/*
* Constants for pcbinfo.ipi_hashfields.
*/
#define IPI_HASHFIELDS_NONE 0
#define IPI_HASHFIELDS_2TUPLE 1
#define IPI_HASHFIELDS_4TUPLE 2
今回はここで終了します。次回は、in_pcbinfo_init() の呼出から見ていきます。
[1] | 今までにも何度か VIMAGE [2] が出てきたと思いますが、カーネルをコンパイルする際にコンフィギュレーションファイルに「options VIMAGE」を追加すると有効になるようです。ということで、今後も #ifdef VIMAGE ~ #endif で括られている部分は解説対象から除外します。 |
[2] | VIMAGE とは FreeBSD のカーネル内で複数のネットワークスタックを動作させる機能のようです。詳しくは、Network Stack Virtualization を御覧ください。 |