- 必要とする知識など:
- UNIX (含む Linux) 上での C 言語と SOCKET によるプログラミング経験および TCP/IP に関する知識。
bind() システムコール、八回目です。sys_bind() ->kern_bind() -> sobind() -> udp_bind() -> in_pcbbind() -> in_pcbinshash() -> in_pcbhash_internal() を呼び出したところです。
本連載の 十八回目 の途中から 十九回目 でグローバル変数 struct inpcb udbinfo の初期化を行なう udp_init() の処理を見てきました。今回は 十八回目 の最初の方で取り上げた in_pcbinshash_internal() の説明に戻ります。
ローカル変数 struct inpcbinfo *pcbinfo の初期化の次の処理から見ていきます。
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 */
これらの INP_WLOCK_ASSERT(), INP_HASH_WLOCK_ASSERT(), KASSERT() は、今までにもたびたび出現してきたアサーションなので割愛します。今後も、アサーションに関しては特に触れることなく割愛していきます。また、今回は IPv4 に着目しているので、#ifdef INET6~#endif この様な IPv6 固有の部分も割愛していきます。
ということで、ローカル変数 u_int32_t hashkey_faddr への代入に進みます。
hashkey_faddr = inp->inp_faddr.s_addr;
inp_faddr は、/usr/src/sys/netinet/in_pcb.h (http://svnweb.freebsd.org/base/release/9.1.0/sys/netinet/in_pcb.h?view=markup) で以下のように定義されています。
#define inp_faddr inp_inc.inc_faddr
#define inc_faddr inc_ie.ie_faddr
#define ie_faddr ie_dependfaddr.ie46_foreign.ia46_addr4
このマクロ定義をたどっていくと、inp_faddr は以下の構造体から構成されていることが分かります。ちなみに、faddr の f は foreign の f です。
struct in_conninfo {
u_int8_t inc_flags;
u_int8_t inc_len;
u_int16_t inc_fibnum; /* XXX was pad, 16 bits is plenty */
/* protocol dependent part */
struct in_endpoints inc_ie;
};
struct in_endpoints {
u_int16_t ie_fport; /* foreign port */
u_int16_t ie_lport; /* local port */
/* protocol dependent part, local and foreign addr */
union {
/* foreign host table entry */
struct in_addr_4in6 ie46_foreign;
struct in6_addr ie6_foreign;
} ie_dependfaddr;
union {
/* local host table entry */
struct in_addr_4in6 ie46_local;
struct in6_addr ie6_local;
} ie_dependladdr;
};
struct in_addr_4in6 {
u_int32_t ia46_pad32[3];
struct in_addr ia46_addr4;
};
struct in_addr {
in_addr_t s_addr;
};
typedef uint32_t in_addr_t;
したがって inp->inp_faddr.s_addr を展開すると以下の様になります。
inp->inp_inc.inc_ie.ie_dependfaddr.ie46_foreign.ia46_addr4.s_addr
以上のことから以下の処理は、
hashkey_faddr = inp->inp_faddr.s_addr;
リモート側のアドレスの struct in_addr (struct in_addr_4in6 の struct in_addr ia46_addr4) の s_addr を hashkey_faddr に代入しています。なお、struct inpcb の inp_faddr は 十一回目 で解説した通り、bzero() にて初期化したのち、まだ inp_faddr には何も代入していないので、0 のままになっています。このため、hashkey_faddr には 0 が代入されます。
次の処理に進みます。
pcbhash = &pcbinfo->ipi_hashbase[INP_PCBHASH(hashkey_faddr,
inp->inp_lport, inp->inp_fport, pcbinfo->ipi_hashmask)];
INP_PCBHASH() は、/usr/src/sys/netinet/in_pcb.h (http://svnweb.freebsd.org/base/release/9.1.0/sys/netinet/in_pcb.h?view=markup) で以下のように定義されています。
#define INP_PCBHASH(faddr, lport, fport, mask) \
(((faddr) ^ ((faddr) >> 16) ^ ntohs((lport) ^ (fport))) & (mask))
マクロ名などから推測すると、何らかの情報をハッシュテーブルに格納する際のハッシュ値を算出しているものと思われます。
INP_PCBHASH() には hashkey_faddr (== 0) と、当関数の第一引数struct inpcb *inp の inp_lport, inp_fport と pcbinfo->ipi_hashmask (ローカル変数 struct inpcbinfo *pcbinfo には inp->inp_pcbinfo に格納されているポインタが代入されている。) が渡されます。
inp_lport は 十七回目 で説明した通り 8000 が格納されています。inp_fport は前述の inp_faddr と同様にまだ何も代入されていないので、0 のままになっています。pcbinfo->ipi_hashmask ですが、struct inpcbinfo udpinfo は、十九回目 で扱っていますが、ipi_hashmask には0x7fが格納されています。なお、inp_lport は 8000 と書きましたが、server.c で htons(8000) した値なので、実際には 8000 == 0x1f40, htons(0x1f40) == 0x401f == 16415 になります。
INP_PCBHASH() に各値を割り当てると、
INP_PCBHASH(0, 0x401f, 0, 0x7f)
となり、マクロを展開すると、
(((0) ^ ((0) >> 16) ^ ntohs((0x401f) ^ (0))) & (0x7f))
これは以下となり、
(((0) ^ (0) ^ 0x1f40) & (0x7f))
これはさらに以下となります。
((0x1f40) & (0x7f)) == 0x40 == 64
結局、以下の処理は、
pcbhash = &pcbinfo->ipi_hashbase[INP_PCBHASH(hashkey_faddr,
inp->inp_lport, inp->inp_fport, pcbinfo->ipi_hashmask)];
次のように展開されます。
pcbhash = &pcbinfo->ipi_hashbase[64];
次の処理に進みます。
pcbporthash = &pcbinfo->ipi_porthashbase[
INP_PCBPORTHASH(inp->inp_lport, pcbinfo->ipi_porthashmask)];
INP_PCBPORTHASH() も、/usr/src/sys/netinet/in_pcb.h (http://svnweb.freebsd.org/base/release/9.1.0/sys/netinet/in_pcb.h?view=markup) で以下のように定義されています。
#define INP_PCBPORTHASH(lport, mask) \
(ntohs((lport)) & (mask))
こちらも INP_PCBHASH() と同様に、何らかの情報をハッシュテーブルに格納する際のハッシュ値を算出しているものと思われます。
portinfo->ipi_porthashmask も 十九回目 で扱っていますが、0x7f が格納されています。NP_PCBPORTHASH() に各値を割り当てると、
INP_PCBPORTHASH(0x401f, 0x7f)
となり、マクロを展開すると、
(ntohs((0x401f)) & (0x7f))
これは以下となります。
((0x1f40) & (0x7f)) == 0x40 == 64
結局、以下の処理は、
pcbporthash = &pcbinfo->ipi_porthashbase[
INP_PCBPORTHASH(inp->inp_lport, pcbinfo->ipi_porthashmask)];
次のように展開されます。
pcbporthash = &pcbinfo->ipi_porthashbase[64];
次の処理に進みます。
/*
* Go through port list and look for a head for this lport.
*/
LIST_FOREACH(phd, pcbporthash, phd_hash) {
if (phd->phd_port == inp->inp_lport)
break;
}
LIST_FOREACH() は、/usr/src/sys/sys/queue.h (http://svnweb.freebsd.org/base/release/9.1.0/sys/sys/queue.h?view=markup) で以下のように定義されています。
#define LIST_FOREACH(var, head, field) \
for ((var) = LIST_FIRST((head)); \
(var); \
(var) = LIST_NEXT((var), field))
#define LIST_FIRST(head) ((head)->lh_first)
#define LIST_NEXT(elm, field) ((elm)->field.le_next)
LIST_FOREACH() に渡している各引数ですが、phd は当関数のローカル変数で、struct inpcbport *phd と定義されています。pcbporthash も当関数のローカル変数で、struct inpcbporthead *pcbporthash と定義されています。これには、先ほど算出したハッシュ値から求めた pcbinfo->ipi_porthashbash[64] へのポインタが代入されています。。これらの変数をLIST_FOREACH() に適用すると、以下のようになります。
for (phd = pcbporthash->lh_first; phd; phd = phd->phd_hash.le_next)
struct inpcbporthead は、/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(inpcbporthead, inpcbport);
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(inpcbporthead, inpcbport) に適用すると、以下のように展開されます。
struct inpcbporthead {
struct inpcbport *lh_first;
};
inpcbport は、/usr/src/sys/netinet/in_pcb.h (http://svnweb.freebsd.org/base/release/9.1.0/sys/netinet/in_pcb.h?view=markup) で以下のように定義されています。
struct inpcbport {
LIST_ENTRY(inpcbport) phd_hash;
struct inpcbhead phd_pcblist;
u_short phd_port;
};
struct inpcbhed は、/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);
LIST_HEAD() は、前述の struct inpcbporthead で説明済みです。結局 LIST_HEAD(inpcbhead, inpcb) は、以下の ように展開されます。
struct inpcbhead {
struct inpcb *lh_first;
};
LIST_ENTRY() は、/usr/src/sys/sys/queue.h で (http://svnweb.freebsd.org/base/release/9.1.0/sys/sys/queue.h?view=markup) 以下のように定義されています。
#define LIST_ENTRY(type) \
struct { \
struct type *le_next; /* next element */ \
struct type **le_prev; /* address of previous next element */ \
}
struct inpcbport の LIST_ENTRY() を展開すると、以下のようになります。
struct inpcbport {
struct {
struct inpcbport *le_next;
struct inpcbport **le_prev;
} phd_hash;
struct inpcbhead phd_pcblist;
u_short phd_port;
};
元の LIST_FOREACH() に戻りますが、第三引数で指定している phd_hash は、上記の struct inpcbport の定義で出現しています。
/*
* Go through port list and look for a head for this lport.
*/
LIST_FOREACH(phd, pcbporthash, phd_hash) {
if (phd->phd_port == inp->inp_lport)
break;
}
結局この LIST_FOREACH() の処理は、以下のように展開され、
for (phd = pcbporthash->lh_first; phd; phd = phd->phd_hash.le_next) {
if (phd->phd_port == inp->inp_lport)
break;
}
pcbporthash で管理されている struct inpcbport のリストから inp->inp_lport と同じポート番号 8000番を使用している定義を探しています。なお、ポート番号 8000番は使用されていないので、phd が NULL になった時点でこのループは終了します。
次の処理に進みます。
/*
* If none exists, malloc one and tack it on.
*/
if (phd == NULL) {
phd = malloc(sizeof(struct inpcbport), M_PCB, M_NOWAIT);
if (phd == NULL) {
return (ENOBUFS); /* XXX */
}
phd->phd_port = inp->inp_lport;
LIST_INIT(&phd->phd_pcblist);
LIST_INSERT_HEAD(pcbporthash, phd, phd_hash);
}
前述の通り if (phd == NULL) は真として次の処理に進みます。
malloc() で struct inpcbport 用の領域を確保し phd->phd_port に inp->inp_lport を代入します。
LIST_INIT() と LIST_INSERT_HEAD() は、/usr/src/sys/sys/queue.h で (http://svnweb.freebsd.org/base/release/9.1.0/sys/sys/queue.h?view=markup) 以下のように定義されています。
#define LIST_INIT(head) do { \
LIST_FIRST((head)) = NULL; \
} while (0)
#define LIST_INSERT_HEAD(head, elm, field) do { \
QMD_LIST_CHECK_HEAD((head), field); \
if ((LIST_NEXT((elm), field) = LIST_FIRST((head))) != NULL) \
LIST_FIRST((head))->field.le_prev = &LIST_NEXT((elm), field);\
LIST_FIRST((head)) = (elm); \
(elm)->field.le_prev = &LIST_FIRST((head)); \
} while (0)
#define LIST_FIRST(head) ((head)->lh_first)
#define LIST_NEXT(elm, field) ((elm)->field.le_next)
#if (defined(_KERNEL) && defined(INVARIANTS))
<略>
#else
#define QMD_LIST_CHECK_HEAD(head, field)
#define QMD_LIST_CHECK_NEXT(elm, field)
#define QMD_LIST_CHECK_PREV(elm, field)
#endif /* (_KERNEL && INVARIANTS) */
defined(INVARIANTS) が真の場合には条件が満たされていると panic() を実行するコードが定義されています。ここでは長くなるので省略しました。
それぞれのマクロを展開すると、以下のようになります。
do {
(&phd->phd_pcblist)->lh_first = NULL;
} while (0);
do {
if ((phd->phd_hash.le_next = pcbporthash->lh_first) != NULL)
pcbporthash->lh_first->phd_hash.le_prev = &phd->phd_hash.le_next;
pcbporthash->lh_first = phd;
phd->phd_hash.le_prev = &pcbporthash->lh_first;
} while (0);
各処理は見たままなので、特に解説はしません。なお、この処理を実行する直前の pcbporthash->lh_first は NULL とみなして、以下の図は記述しています。
次の処理に進みます。
inp->inp_phd = phd;
LIST_INSERT_HEAD(&phd->phd_pcblist, inp, inp_portlist);
LIST_INSERT_HEAD(pcbhash, inp, inp_hash);
inp->inp_flags |= INP_INHASHLIST;
二つの LIST_INSERT_HEAD() を展開すると、以下のようになります。
do {
if ((inp->inp_portlist.le_next = (&phd->phd_pcblist)->lh_first) != NULL)
(&phd->phd_pcblist)->lh_first->inp_portlist.le_prev = &inp->inp_portlist.le_next;
(&phd->phd_pcblist)->lh_first = inp;
inp->inp_portlist.le_prev = &((&phd->phd_pcblist)->lh_first);
} while (0);
do {
if ((inp->inp_hash.le_next = pcbhash->lh_first) != NULL)
pcbhash->lh_first->inp_hash.le_prev = &inp->inp_hash.le_next;
pcbhash->lh_first = inp;
inp->inp_hash.le_prev = &pcbhash->lh_first;
} while (0);
in_pcbinshash_internal()の最後の処理です。
#ifdef PCBGROUP
if (do_pcbgroup_update)
in_pcbgroup_update(inp);
#endif
return (0);
}
#ifdef PCBGROUP ~ #endif で括られていますが、コンパイル時に PCBGROUP は定義されていないようなのでこの部分は割愛します。最後に 0 を返却して in_pcbinshash() に戻ります。
int
in_pcbinshash(struct inpcb *inp)
{
return (in_pcbinshash_internal(inp, 1));
}
in_pcbinshash() は in_pcbinshash_internal() からの戻り値をそのまま返却して in_pcbbind() に戻ります。以下は、in_pcbbind() の in_pcbinshash() の呼び出し部分です。
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);
}
最初の if (in_pcbinshash(inp) != 0) { ですが、戻り値は 0 なので、この if 文は偽となり次の if (anonport) に進みます。ここで評価する anonport は、少し前の処理で以下のように設定されます。
anonport = inp->inp_lport == 0 && (nam == NULL ||
((struct sockaddr_in *)nam)->sin_port == 0);
inp->inp_lport は 0 です。nam は sys_bind() が呼び出した getsockaddr() で確保した領域のポインタが渡されるので NULL ではありません。((struct sockaddr_in *)nam)->sin_port には server.c が bind() システムコールを呼び出した際に渡す以下の sa.sin_port の値が格納されています。
sa.sin_port = htons(port);
result = bind(sd, (struct sockaddr *)&sa, sizeof (sa));
以上のことから、anonport には偽が格納されているので、if (anonport) この if 文は偽となるので、inp->inp_flags |= INP_ANONPORT; は実行されません。
in_pcbbind() は最後に 0 を返却して udp_bind() に戻ります。以下は、udp_bind() の in_pcbbind() の呼び出し部分です。
error = in_pcbbind(inp, nam, td->td_ucred);
INP_HASH_WUNLOCK(&V_udbinfo);
INP_WUNLOCK(inp);
return (error);
}
udp_bind() は in_pcbbind() の戻り値をそのまま返却して sobind() に戻ります。以下は、sobind() の udp_bind() の呼び出し部分です。
error = (*so->so_proto->pr_usrreqs->pru_bind)(so, nam, td);
CURVNET_RESTORE();
return error;
}
ここでも udp_bind() からの戻り値をそのまま返却して kern_bind() に戻ります。以下は、kern_bind() の sobind() の呼び出し部分です。
error = sobind(so, sa, td);
fdrop(fp, td);
return (error);
}
kern_bind() は sobind() から戻ると fdrop() を呼び出します。
fdrop() は /usr/src/sys/sys/file.h (http://svnweb.freebsd.org/base/release/9.1.0/sys/sys/file.h?view=markup) で以下のように定義されています。
#define fdrop(fp, td) \
(refcount_release(&(fp)->f_count) ? _fdrop((fp), (td)) : _fnoop())
まずは、refcount_release() の呼び出しです。refcount_release() は、fdop() と同じく /usr/src/sys/file.h (http://svnweb.freebsd.org/base/release/9.1.0/sys/sys/file.h?view=markup) で以下のように定義されています。
static __inline int
refcount_release(volatile u_int *count)
{
u_int old;
/* XXX: Should this have a rel membar? */
old = atomic_fetchadd_int(count, -1);
KASSERT(old > 0, ("negative refcount %p", count));
return (old == 1);
}
atomic_fetchadd_int() は、/usr/src/sys/アーキテクチャー/include/atomic.h で定義されています。i386 であれば /usr/src/sys/i386/include/atomic.h (http://svnweb.freebsd.org/base/release/9.1.0/sys/i386/include/atomic.h?view=markup) です。
詳細は割愛しますが、atomic_fetchadd_int() は、第一引数のポインタが示す int の値に第二引数を加算し、加算する前の値を返却します。fp->count は現在 3 になっているので、
return (old == 1);
これは偽を返却します。
refcount_release() が偽を返すので、fdrop() は _fnoop() を呼び出します。_fnoop() は fdrop() と同じく、/usr/src/sys/sys/file.h (http://svnweb.freebsd.org/base/release/9.1.0/sys/sys/file.h?view=markup) で以下のように定義されています。
static __inline int
_fnoop(void)
{
return (0);
}
_fnoop() は 0 を返却して kern_bind() に戻ります。kern_bind() は sobind() の戻り値をそのまま返却してsys_bind() に戻ります。
以下は、sys_bind() の kern_bind() の呼び出し部分です。
error = kern_bind(td, uap->s, sa);
free(sa, M_SONAME);
return (error);
}
free() で getsockaddr() を呼び出した際に確保した領域を解放したのちに、error を返却して終了します。
以上でbind()システムコールの処理は終了です。
bind()システムコールで操作したデータ構造を以下に示します。
bind()システムコールの処理で確保した領域を緑、変更した変数を青、変更した値は赤で示して います。