FreeBSD kernel SOCKET I/F 探検

第11回 socket() システムコール (9)

2014.06.11

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

socket() システムコール、九回目です。sys_socket() -> socreate() -> udp_attach() -> soreserve() と呼び出して、soreserve() から udp_attach() に戻ったところです。

error = soreserve(so, udp_sendspace, udp_recvspace);
if (error)
        return (error);
INP_INFO_WLOCK(&V_udbinfo);
error = in_pcballoc(so, &V_udbinfo);
if (error) {
        INP_INFO_WUNLOCK(&V_udbinfo);
        return (error);
}

soreserve() から戻ると、INP_INFO_WLOCK() を実行しています。

INP_INFO_WLOCK() は、/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_INFO_WLOCK(ipi)     rw_wlock(&(ipi)->ipi_lock)

rw_wlock() の詳細に関しては割愛しますが、V_udbinfo への書き込み用のロックを獲得しているものと推測します。

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

#define V_udbinfo               VNET(udbinfo)

VNET() は、/usr/src/sys/net/vnet.h (http://svnweb.freebsd.org/base/release/9.1.0/sys/net/vnet.h?view=markup) で定義されています。VNET() の詳細は割愛しますが INP_INFO_WLOCK( & V_udbinfo); は、以下と同義です。

INP_INFO_WLOCK((udbinfo));

INP_INFO_WLOCK() は、udbinfo への書き込み用のロックを獲得しているものと理解して先に進みます。

error = in_pcballoc(so, &V_udbinfo);
if (error) {
        INP_INFO_WUNLOCK(&V_udbinfo);
        return (error);
}

in_pcballoc(so, &V_udbinfo); は、以下と同義です。

error = in_pcballoc(so, &(udbinfo));

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

VNET_DEFINE(struct inpcbinfo, udbinfo);

VNET_DEFINE() も VNET() と同じ /usr/src/sys/net/vnet.h で定義されています。詳細は割愛しますが、上記の VNET_DEFINE(...) は、以下と同義です。

struct inpcbinfo udbinfo;

struct inpcbinfo も、/usr/src/sys/netinet/in_pcb.h で以下のように定義されています。

struct inpcbinfo {
        ...
        struct inpcbhead        *ipi_listhead;          /* (g) */

struct inpcbhead は、同じく /usr/src/sys/netinet/in_pcb.h で以下のように定義されています。

LIST_HEAD(inpcbhead, inpcb);

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

/*
 * List declarations.
 */
#define LIST_HEAD(name, type)                                           \
struct name {                                                           \
        struct type *lh_first;  /* first element */                     \
}

これに従って上記の LIST_HEAD(inpcbhead, inpcb); を展開したものを以下に記載します。

struct inpcbhead {
        struct inpcb *lh_first; /* first element */
};

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

int
in_pcballoc(struct socket *so, struct inpcbinfo *pcbinfo)
{
        struct inpcb *inp;
        int error;

        INP_INFO_WLOCK_ASSERT(pcbinfo);
        error = 0;
        inp = uma_zalloc(pcbinfo->ipi_zone, M_NOWAIT);
        if (inp == NULL)
                return (ENOBUFS);
        bzero(inp, inp_zero_size);
        inp->inp_pcbinfo = pcbinfo;
        inp->inp_socket = so;
        inp->inp_cred = crhold(so->so_cred);
        inp->inp_inc.inc_fibnum = so->so_fibnum;
#ifdef MAC
        error = mac_inpcb_init(inp, M_NOWAIT);
        if (error != 0)
                goto out;
        mac_inpcb_create(so, inp);
#endif
#ifdef IPSEC
        error = ipsec_init_policy(so, &inp->inp_sp);
        if (error != 0) {
#ifdef MAC
                mac_inpcb_destroy(inp);
#endif
                goto out;
        }
#endif /*IPSEC*/
#ifdef INET6
        if (INP_SOCKAF(so) == AF_INET6) {
                inp->inp_vflag |= INP_IPV6PROTO;
                if (V_ip6_v6only)
                        inp->inp_flags |= IN6P_IPV6_V6ONLY;
        }
#endif
        LIST_INSERT_HEAD(pcbinfo->ipi_listhead, inp, inp_list);
        pcbinfo->ipi_count++;
        so->so_pcb = (caddr_t)inp;
#ifdef INET6
        if (V_ip6_auto_flowlabel)
                inp->inp_flags |= IN6P_AUTOFLOWLABEL;
#endif
        INP_WLOCK(inp);
        inp->inp_gencnt = ++pcbinfo->ipi_gencnt;
        refcount_init(&inp->inp_refcount, 1);   /* Reference from inpcbinfo */
#if defined(IPSEC) || defined(MAC)
out:
        if (error != 0) {
                crfree(inp->inp_cred);
                uma_zfree(pcbinfo->ipi_zone, inp);
        }
#endif
        return (error);
}

#ifdef がたくさん書かれています。以下の理由により、不要な #ifdef を削除したものを掲載します。

  • IPSEC (Security Architecture for Internet Protocol) は本連載では扱わない。
  • MAC は Mac 用の定義と思われる。
  • IPv6 は本連載では扱わない。
int
in_pcballoc(struct socket *so, struct inpcbinfo *pcbinfo)
{
        struct inpcb *inp;
        int error;

        INP_INFO_WLOCK_ASSERT(pcbinfo);
        error = 0;
        inp = uma_zalloc(pcbinfo->ipi_zone, M_NOWAIT);
        if (inp == NULL)
                return (ENOBUFS);
        bzero(inp, inp_zero_size);
        inp->inp_pcbinfo = pcbinfo;
        inp->inp_socket = so;
        inp->inp_cred = crhold(so->so_cred);
        inp->inp_inc.inc_fibnum = so->so_fibnum;
        LIST_INSERT_HEAD(pcbinfo->ipi_listhead, inp, inp_list);
        pcbinfo->ipi_count++;
        so->so_pcb = (caddr_t)inp;
        INP_WLOCK(inp);
        inp->inp_gencnt = ++pcbinfo->ipi_gencnt;
        refcount_init(&inp->inp_refcount, 1);   /* Reference from inpcbinfo */
        return (error);
}

in_pcballoc() の処理を見ていきます。

INP_INFO_WLOCK_ASSERT(pcbinfo);

INP_INFO_WLOCK_ASSERT() は、INP_INFO_WLOCK() と同様に /usr/src/sys/netinet/in_pcb.h 以下のように定義されています。

#define INP_INFO_WLOCK_ASSERT(ipi)      rw_assert(&(ipi)->ipi_lock, RA_WLOCKED)

詳細は割愛しますが、上記の INP_INFO_WLOCK_ASSERT(pcbinfo) は、pcbinfo に対して書き込みロックを保持していることを確認しています。

error = 0;
inp = uma_zalloc(pcbinfo->ipi_zone, M_NOWAIT);
if (inp == NULL)
        return (ENOBUFS);
bzero(inp, inp_zero_size);

uma_zalloc() は本連載の 四回目 でも登場しましたが、MALLOC(3) と同様にメモリ領域を割り当てる関数です。

bzero() の第二引数として渡している inp_zero_size は、/usr/src/sys/netinet/in_pcb.h で以下のように定義されています。

struct inpcb {
        ...
        struct  inpcbport *inp_phd;     /* (i/p) head of this list */
#define inp_zero_size offsetof(struct inpcb, inp_gencnt)
        inp_gen_t       inp_gencnt;     /* (c) generation count */
        struct llentry  *inp_lle;       /* cached L2 information */
        struct rtentry  *inp_rt;        /* cached L3 information */
        struct rwlock   inp_lock;
};

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

#define offsetof(type, field)   __offsetof(type, field)

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

#if __GNUC_PREREQ__(4, 1)
#define __offsetof(type, field)  __builtin_offsetof(type, field)
#else
#ifndef __cplusplus
#define __offsetof(type, field) \
        ((__size_t)(__uintptr_t)((const volatile void *)&((type *)0)->field))
#else
#define __offsetof(type, field)                                 \
  (__offsetof__ (reinterpret_cast <__size_t>                    \
                 (&reinterpret_cast <const volatile char &>     \
                  (static_cast<type *> (0)->field))))
#endif
#endif
#define __rangeof(type, start, end) \
        (__offsetof(type, end) - __offsetof(type, start))

__GNUC_PREREQ__() は、/usr/src/sys/sys/cdefs.h (http://svnweb.freebsd.org/base/release/9.1.0/sys/sys/cdefs.h?view=markup) で定義されています。#if __GNUC_PREREQ__(4, 1) は、使用しているコンパイラが GCC で且つそのバージョンが 4.1 以上の場合に真になります。最近の FreeBSD では、コンパイラを GCC から Clang に乗り換えているので、ここでは GCC を使用していないものとみなし、

#define __offsetof(type, field) \
        ((__size_t)(__uintptr_t)((const volatile void *)&((type *)0)->field))

この定義を適用しているものとみなします。これを踏まえて以下

#define inp_zero_size offsetof(struct inpcb, inp_gencnt)

を展開し一部を省略すると、

((void *)&(struct inpcb *)0)->inp_gencnt

となり struct inpcb のメンバ変数 inp_gencnt へのオフセット [1] となります。

bzero(inp, inp_zero_size);

この bzero() は、uma_azlloc() で確保した struct inpcb用の領域の先頭からメンバ変数 inp_gencnt の直前までを ZERO クリアしています。なぜそのようにしているのか理由は分かりませんが、inp_gencnt およびそれ以降のメンバ変数をこの直後に初期化する為、bzero() での ZERO クリアを省いているのかも知れません。

次の処理を見ていきます。

inp->inp_pcbinfo = pcbinfo;
inp->inp_socket = so;
inp->inp_cred = crhold(so->so_cred);
inp->inp_inc.inc_fibnum = so->so_fibnum;

これらの代入処理は自明なので説明は割愛します。crhold() は本連載の 四回目 で説明しているので、詳細はそちらを御覧ください。

LIST_INSERT_HEAD(pcbinfo->ipi_listhead, inp, inp_list);

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_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)

また、LIST_INSERT_HEAD() で使用している QMD_LIST_CHECK_HEAD() は、同じく /usr/src/sys/sys/queue.h で以下のように定義されています。

#if (defined(_KERNEL) && defined(INVARIANTS))
#define QMD_LIST_CHECK_HEAD(head, field) do {                           \
        if (LIST_FIRST((head)) != NULL &&                               \
            LIST_FIRST((head))->field.le_prev !=                        \
             &LIST_FIRST((head)))                                       \
                panic("Bad list head %p first->prev != head", (head));  \
} while (0)
#else
#define QMD_LIST_CHECK_HEAD(head, field)
#endif /* (_KERNEL && INVARIANTS) */

#if (defined(...) により定義が異なりますが、ここでは説明の簡略化の為、後者 (#else~#endif で括られている部分) の空の定義が有効になっているものとみなします。

LIST_INSERT_HEAD() で使用している LIST_FIRST(), LIST_NEXT() も同様に、/usr/src/sys/sys/queue.h で以下のように定義されています。

#define LIST_FIRST(head)        ((head)->lh_first)
#define LIST_NEXT(elm, field)   ((elm)->field.le_next)

これらを踏まえて

LIST_INSERT_HEAD(pcbinfo->ipi_listhead, inp, inp_list);

を展開したものを以下に記載します。

do {
        if ((inp->inp_list.le_next = pcbinfo->ipi_listhead->lh_first) != ((void*)0))
                pcbinfo->ipi_listhead->lh_first->inp_list.le_prev = &inp->inp_list.le_next;
        pcbinfo->ipi_listhead->lh_first = inp;
        inp->inp_list.le_prev = &pcbinfo->ipi_listhead->lh_first;
} while (0);

要するに、LIST_INSERT_HEAD() は、新たに確保した struct inpcb 用の領域を struct inpcbinfo udbinfo で管理している PCB のリストに追加しています。

LIST_INSERT_HEAD(pcbinfo->ipi_listhead, inp, inp_list);

ここで使用している inp_list は、/usr/include/sys/neitinet/in_pcb.h で以下のように定義されています。

struct inpcb {
        LIST_ENTRY(inpcb) inp_hash;     /* (i/p) hash list */
        LIST_ENTRY(inpcb) inp_pcbgrouphash;     /* (g/i) hash list */
        LIST_ENTRY(inpcb) inp_list;     /* (i/p) list for all PCBs for proto */
        ...

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 inpub の LIST_ENTRY(inpcb) inp_list; に適用したものを以下に記載します。

struct inpcb {
        LIST_ENTRY(inpcb) inp_hash;     /* (i/p) hash list */
        LIST_ENTRY(inpcb) inp_pcbgrouphash;     /* (g/i) hash list */
        struct {                         /* (i/p) list for all PCBs for proto */                                        \
                struct inpcb *le_next;   /* next element */                      \
                struct inpcb **le_prev;  /* address of previous next element */  \
        } inp_list;
        ...

LIST_INSERT_HEAD() の処理を以下に示します。

今回の処理で確保した領域を緑、変更した変数を青、変更した値は赤で示しています。First はリストが空の状態。Second は空のリストに一件の PCB を追加した状態。Third はリストに二件目の PCB を追加した状態を表します。二件目以降を追加する際には、新たに追加したものがリストの先頭に位置します。

上図を簡略化したものを以下に示します。

Third の図が上と下にあります。上の図は簡略化する前と同じ構造ですが、下の図は struct inpcb をリストの並び順に書き換えたものです。

要するに LIST_INSERT_HEAD() は、双方向のリストの管理を行なうマクロで、挿入対象の要素をリストの先頭に追加します。

なお、inpcb および PCB は、/usr/src/sys/netinet/in_pcb.h の最初の方のコメントにあるように、

/*
 * struct inpcb is the common protocol control block structure used in most
 * IP transport protocols.

IP 用の Protocol Control Block (PCB) という意味です。

次の処理に進みます。

pcbinfo->ipi_count++;
so->so_pcb = (caddr_t)inp;
INP_WLOCK(inp);
inp->inp_gencnt = ++pcbinfo->ipi_gencnt;

二行目のキャストで使用している caddr_t は、/usr/src/sys/sys/types.h (http://svnweb.freebsd.org/base/release/9.1.0/sys/sys/types.h?view=markup) で以下のように定義されています。

typedef char * caddr_t; /* core address */

コメントに書かれている core とは、コア・メモリのコアでしょうか?代入先の so->so_pcb は、/usr/src/sys/sys/sockvar.h (http://svnweb.freebsd.org/base/release/9.1.0/sys/sys/socketvar.h?view=markup) 以下のように void* と定義されているので、キャストの必要はなさそうですが、caddr_t にキャストしている理由は分かりません。

void *so_pcb; /* protocol control block */

三行目の INP_WLOCK() は、/usr/src/sys/netinet/in_pcb.h で以下のように定義されています。

#define INP_WLOCK(inp)          rw_wlock(&(inp)->inp_lock)

詳細は割愛しますが、struct inpcb *inp に対する書き込み用のロックを獲得しているものと推測します。

refcount_init(&inp->inp_refcount, 1);   /* Reference from inpcbinfo */
return (error);

refcount_init() は、本連載の 四回目 で解説しています。

最後に error (値は 0) を返却して、in_pcballoc() から udp_attach() に戻ります。

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

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

[1]struct foo *pointer; &pointer->member; で構造体のメンバへのポインタとなりますが、ここではポインタに ゼロ番地 を指定しているので、メンバへのオフセットとなります。

著者プロフィール

asou

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

記事一覧Index