FreeBSD kernel SOCKET I/F 探検

第20回 bind() システムコール (8)

2016.01.14

必要とする知識など:
  • 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()システムコールの処理で確保した領域を緑、変更した変数を青、変更した値は赤で示して います。

著者プロフィール

asou

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

記事一覧Index