FreeBSD kernel SOCKET I/F 探検 (18)

bind() システムコール (6)

必要とする知識など:
  • 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 を御覧ください。
記事執筆者: asou
記事公開日:2015年05月28日