FreeBSD kernel SOCKET I/F 探検

第19回 bind() システムコール (7)

2015.10.07

必要とする知識など:
  • UNIX (含む Linux) 上での C 言語と SOCKET によるプログラミング経験および TCP/IP に関する知識。

bind() システムコール、七回目です。sys_bind() ->kern_bind() -> sobind() -> udp_bind() -> in_pcbbind() -> in_pcbinshash() ->in_pcbinshash_internal() を呼び出したところで、in_pcbinshash_internal() で使用している struct inpcbinfo udbinfo 初期化の処理を調べている途中で udp_init() から in_pcbinfo_init() を呼び出すところです。

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);
}

いくつか処理を飛ばして、以下の処理から解説します。

LIST_INIT(pcbinfo->ipi_listhead);

LIST_INIT() は、/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_FIRST(head)        ((head)->lh_first)

結局

LIST_INIT(pcbinfo->ipi_listhead);

この処理は、以下のように展開されます。

do {
      (((pcbinfo->ipi_listhead))->lh_first) = ((void *)0);
} while (0);

次の hashinit() ですが、

pcbinfo->ipi_hashbase = hashinit(hash_nelements, M_PCB,
    &pcbinfo->ipi_hashmask);
pcbinfo->ipi_porthashbase = hashinit(porthash_nelements, M_PCB,
    &pcbinfo->ipi_porthashmask);

hashinit() は、/usr/src/sys/kern/subr_hash.c で (http://svnweb.freebsd.org/base/release/9.1.0/sys/kern/subr_hash.c?view=markup) 以下のように定義されています。

/*
 * Allocate and initialize a hash table with default flag: may sleep.
 */
void *
hashinit(int elements, struct malloc_type *type, u_long *hashmask)
{

        return (hashinit_flags(elements, type, hashmask, HASH_WAITOK));

}

ここで呼び出している hashinit_flags() も同じく /usr/src/sys/kern/subr_hash.c で (http://svnweb.freebsd.org/base/release/9.1.0/sys/kern/subr_hash.c?view=markup) 以下のように定義されています。

/*
 * General routine to allocate a hash table with control of memory flags.
 */
void *
hashinit_flags(int elements, struct malloc_type *type, u_long *hashmask,
    int flags)
{
        long hashsize;
        LIST_HEAD(generic, generic) *hashtbl;
        int i;

        KASSERT(elements > 0, ("%s: bad elements", __func__));
        /* Exactly one of HASH_WAITOK and HASH_NOWAIT must be set. */
        KASSERT((flags & HASH_WAITOK) ^ (flags & HASH_NOWAIT),
            ("Bad flags (0x%x) passed to hashinit_flags", flags));

        for (hashsize = 1; hashsize <= elements; hashsize <<= 1)
                continue;
        hashsize >>= 1;

        if (flags & HASH_NOWAIT)
                hashtbl = malloc((u_long)hashsize * sizeof(*hashtbl),
                    type, M_NOWAIT);
        else
                hashtbl = malloc((u_long)hashsize * sizeof(*hashtbl),
                    type, M_WAITOK);

        if (hashtbl != NULL) {
                for (i = 0; i < hashsize; i++)
                        LIST_INIT(&hashtbl[i]);
                *hashmask = hashsize - 1;
        }
        return (hashtbl);
}

それでは、hashinit_flags() の処理を順番に見ていきます。最初の KASSERT() は飛ばして次の for ループですが、

for (hashsize = 1; hashsize <= elements; hashsize <<= 1)
        continue;
hashsize >>= 1;

hashsize が elements 以下の間 hashsize を 1bit ずつ左にシフトしていき、elements よりも大きくなったところで for ループを抜け出して hashsize を右に 1bit シフトしています。

今回は、elements == 128 == 0x80 なので、hashsize が 0x100 になったところでループを抜けて右に 1bit シフトするので、hashsize は 0x80 == 128 になります。

次の処理ですが、flags には HASH_WAITOK を指定されているので、else の malloc(..., M_WAITOK) が呼び出されます。

if (flags & HASH_NOWAIT)
        hashtbl = malloc((u_long)hashsize * sizeof(*hashtbl),
            type, M_NOWAIT);
else
        hashtbl = malloc((u_long)hashsize * sizeof(*hashtbl),
            type, M_WAITOK);

ちなみに、HASH_WAITOK は、/usr/src/sys/sys/systm.h (http://svnweb.freebsd.org/base/release/9.1.0/sys/sys/systm.h?view=markup) で以下のように定義されています。

#define HASH_NOWAIT     0x00000001
#define HASH_WAITOK     0x00000002

malloc() は、/usr/src/sys/kern/kern_malloc.c (http://svnweb.freebsd.org/base/release/9.1.0/sys/kern/kern_malloc.c?view=markup) で以下のように定義されています。

/*
 *      malloc:
 *
 *      Allocate a block of memory.
 *
 *      If M_NOWAIT is set, this routine will not block and return NULL if
 *      the allocation fails.
 */
void *
malloc(unsigned long size, struct malloc_type *mtp, int flags)
{

malloc() に関しては、MALLOC(3) ライブラリ関数とほぼ同義であるとみなしてください。malloc() に渡している type は取り敢えず気にしないことにします。また M_NOWAIT/M_WAITOK は、空きメモリが不足している場合に待たずに即座に戻ってくるか、空きメモリが確保できるまで待ち続けるかの違いです。

hashinit() が malloc() を呼び出す際の第一引数の計算で使用している hashtbl は、hashinit() の最初のところで以下のように定義しています。

LIST_HEAD(generic, generic) *hashtbl;

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 */                     \
}

従って、上記の hashtbl の定義は以下のように展開されます。

struct generic { struct generic *lh_first; } *hashtbl;

結局 sizeof(*hashtbl) は、「struct generic の大きさ == sizeof(struct generic *)」なので、32bit 環境では 4、64bit 環境では 8 になると思いますが、本連載では 32bit 環境とみなして 4 とします。

hashtbl = malloc((u_long)hashsize * sizeof(*hashtbl),
    type, M_WAITOK);

以上のことから、上記の malloc() の呼出は、

hashtbl = malloc(128 * 4, type, M_WAITOK);

となります。

本関数の最後の処理です。

if (hashtbl != NULL) {
        for (i = 0; i < hashsize; i++)
                LIST_INIT(&hashtbl[i]);
        *hashmask = hashsize - 1;
}

ハッシュテーブル用の領域が確保できたならば、LIST_INIT() を使用してハッシュテーブルを初期化しています。

LIST_INIT() は、/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_FIRST(head)        ((head)->lh_first)

結局、上記の for ループは以下のように展開され、

for (i = 0; i < hashsize; i++)
        do {
                (((&hashtbl[i]))->lh_first) = ((void *)0);
        } while (0);

今確保したハッシュテーブル用の領域全てに対して NULL で初期化しています。

最後に

        *hashmask = hashsize - 1;
}
return (hashtbl);

*hashmask に hashsize (128) - 1 を代入し、確保したハッシュテーブルへのポインタを返却して hashinit_flags() は終了し、in_pcbinfo_init() に戻ります。

hashinit() から戻った直後は #ifdef PCBGROUP ~ #endif で括られていますが、コンパイル時に PCBGROUP は定義されていないようなのでこの部分は割愛し、uma_zcreate() の呼出を見ていきます。

#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);
}

uma_zcreate() は、/usr/src/sys/vm/uma_core.c (http://svnweb.freebsd.org/base/release/9.1.0/sys/vm/uma_core.c?view=markup) で以下のように定義されています。

/* See uma.h */
uma_zone_t
uma_zcreate(char *name, size_t size, uma_ctor ctor, uma_dtor dtor,
                uma_init uminit, uma_fini fini, int align, u_int32_t flags)

{
        struct uma_zctor_args args;

        /* This stuff is essential for the zone ctor */
        args.name = name;
        args.size = size;
        args.ctor = ctor;
        args.dtor = dtor;
        args.uminit = uminit;
        args.fini = fini;
        args.align = align;
        args.flags = flags;
        args.keg = NULL;

        return (zone_alloc_item(zones, &args, M_WAITOK));
}

「See uma.h」と書かれています。/usr/src/sys/vm/uma.h (http://svnweb.freebsd.org/base/release/9.1.0/sys/vm/uma.h?view=markup) の該当部分を以下に掲載します。

/*
 * Create a new uma zone
 *
 * Arguments:
     :
     :
 * Returns:
 *      A pointer to a structure which is intended to be opaque to users of
 *      the interface.  The value may be null if the wait flag is not set.
 */
uma_zone_t uma_zcreate(char *name, size_t size, uma_ctor ctor, uma_dtor dtor,
                        uma_init uminit, uma_fini fini, int align,
                        u_int32_t flags);

uma_zone_t は、同じく /usr/src/sys/vm/uma.h (http://svnweb.freebsd.org/base/release/9.1.0/sys/vm/uma.h?view=markup) で以下のように定義されています。

struct uma_zone;
/* Opaque type used as a handle to the zone */
typedef struct uma_zone * uma_zone_t;

なお、uma.h の最初の方に、以下のコメントが記述されています。

/*
 * uma.h - External definitions for the Universal Memory Allocator
 *
 */

万能メモリアロケータと言うことでしょうか?とりあえず UMA に関しては深く追求しないことにし、uma_zcreate() は第二引数 size_t size に渡す sizeof(struct inpcb) の大きさの領域を確保するものとみなします。

in_pcb_info_init() の最後は uma_zone_set_max() の呼び出しです。uma_zone_set_max() は、uma_zcreate() と同様に /usr/src/sys/vm/uma_core.c (http://svnweb.freebsd.org/base/release/9.1.0/sys/vm/uma_core.c?view=markup) で以下のように定義されています。

/* See uma.h */
int
uma_zone_set_max(uma_zone_t zone, int nitems)
{
        uma_keg_t keg;

        ZONE_LOCK(zone);
        keg = zone_first_keg(zone);
        keg->uk_maxpages = (nitems / keg->uk_ipers) * keg->uk_ppera;
        if (keg->uk_maxpages * keg->uk_ipers < nitems)
                keg->uk_maxpages += keg->uk_ppera;
        nitems = keg->uk_maxpages * keg->uk_ipers;
        ZONE_UNLOCK(zone);

        return (nitems);
}

uma_zone_set_max() の詳細に関しては割愛します。

uma_zone_set_max() から戻ると in_pcbinfo_init() の処理は終了するので、udp_init() に戻ります。udp_init() を以下に記載しておきます。

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);
}

in_pcbinfo_init() から udp_init() に戻ると、uma_zcreate() と uma_zone_set_max() を呼び出しています。これらの関数は先ほど in_pcbinfo_init() で出てきたので、詳細は割愛します。

なお、uma_zcreate() の戻り値を格納する udpcb_zone は、/usr/src/sys/netinet/udp_usrreq.c (http://svnweb.freebsd.org/base/release/9.1.0/sys/netinet/udp_usrreq.c?view=markup) で以下のように定義されています。

static VNET_DEFINE(uma_zone_t, udpcb_zone);
#define V_udpcb_zone                    VNET(udpcb_zone)

この udpcb_zone の定義は以下のように展開されます。

static uma_zone_t udpcb_zone;

uma_zone_t は、/usr/src/sys/vm/uma.h (http://svnweb.freebsd.org/base/release/9.1.0/sys/vm/uma.h?view=markup) で以下のように定義されています。

struct uma_zone;
/* Opaque type used as a handle to the zone */
typedef struct uma_zone * uma_zone_t;

udp_init() の最後の EVENTHANDLER_REGISTER() は、/usr/src/sys/sys/eventhandler.h (http://svnweb.freebsd.org/base/release/9.1.0/sys/sys/eventhandler.h?view=markup) で以下のように定義されています。

#define EVENTHANDLER_REGISTER(name, func, arg, priority)                \
        eventhandler_register(NULL, #name, func, arg, priority)

ここで使用している eventhandler_register() は、/usr/src/sys/kern/subr_eventhandler.c (http://svnweb.freebsd.org/base/release/9.1.0/sys/kern/subr_eventhandler.c?view=markup) で以下のように定義されています。

eventhandler_tag
eventhandler_register(struct eventhandler_list *list, const char *name,
                      void *func, void *arg, int priority)
{
...
以下略

eventhandler_register() の詳細は割愛します。

ここまでのデータ構造を以下に示します。

今回の処理で確保した領域を緑、変更した変数を青、変更した値は赤で示しています。

前回 十八回目 の途中から udp_init() の処理を見てきましたが、次回は前回 十八回目 の途中まで見てきた in_pcbhash_internal() の処理に戻ります。

著者プロフィール

asou

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

記事一覧Index