- 必要とする知識など:
- 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() の処理に戻ります。