FreeBSD kernel SOCKET I/F 探検

第10回 socket() システムコール (8)

2014.04.30

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

socket() システムコール、八回目です。どういう経路で呼び出されているか分からなくなってしまうので、今回からそれを記述することにします。sys_socket() -> socreate() -> udp_attach() -> soreserve() と呼び出されていて、soreserve() から sbreserve_locked() を呼び出すところです。

if (sbreserve_locked(&so->so_snd, sndcc, so, td) == 0)
        goto bad;
if (sbreserve_locked(&so->so_rcv, rcvcc, so, td) == 0)
        goto bad2;

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

u_long  sb_max = SB_MAX;
u_long sb_max_adj =
       (quad_t)SB_MAX * MCLBYTES / (MSIZE + MCLBYTES); /* adjusted sb_max */

int
sbreserve_locked(struct sockbuf *sb, u_long cc, struct socket *so,
    struct thread *td)
{
        rlim_t sbsize_limit;

        SOCKBUF_LOCK_ASSERT(sb);

        /*
         * When a thread is passed, we take into account the thread's socket
         * buffer size limit.  The caller will generally pass curthread, but
         * in the TCP input path, NULL will be passed to indicate that no
         * appropriate thread resource limits are available.  In that case,
         * we don't apply a process limit.
         */
        if (cc > sb_max_adj)
                return (0);
        if (td != NULL) {
                PROC_LOCK(td->td_proc);
                sbsize_limit = lim_cur(td->td_proc, RLIMIT_SBSIZE);
                PROC_UNLOCK(td->td_proc);
        } else
                sbsize_limit = RLIM_INFINITY;
        if (!chgsbsize(so->so_cred->cr_uidinfo, &sb->sb_hiwat, cc,
            sbsize_limit))
                return (0);
        sb->sb_mbmax = min(cc * sb_efficiency, sb_max);
        if (sb->sb_lowat > sb->sb_hiwat)
                sb->sb_lowat = sb->sb_hiwat;
        return (1);
}

sbreserve_locked() 関数の前に、変数 u_long sb_max_adj の初期化を見ていきます。

u_long  sb_max = SB_MAX;
u_long sb_max_adj =
       (quad_t)sb_max * mclbytes / (msize + mclbytes); /* adjusted sb_max */

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

#define SB_MAX          (2*1024*1024)   /* default for max chars in sockbuf */

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

#ifndef MSIZE
#define MSIZE           256             /* size of an mbuf */
#endif  /* MSIZE */

#ifndef MCLSHIFT
#define MCLSHIFT        11              /* convert bytes to mbuf clusters */
#endif  /* MCLSHIFT */

#define MCLBYTES        (1 << MCLSHIFT) /* size of an mbuf cluster */

MCLBYTES は、1 << 11 = 2^11 (2 の 11 乗) = 2048 となります。従って、

u_long  sb_max = SB_MAX;
u_long sb_max_adj =
       (quad_t)SB_MAX * MCLBYTES / (MSIZE + MCLBYTES); /* adjusted sb_max */

は、

u_long  sb_max = 2097152
u_long sb_max_adj = 2097152 * 2048 / (256 + 2048) = 1864135

となります。なお、MCLBYTES, MSIZE は、後に出てくるであろう mbuf (/usr/src/sys/sys/mbuf.h (http://svnweb.freebsd.org/base/release/9.1.0/sys/sys/mbuf.h?view=markup)) 用の定義です。mbuf は、ネットワークの送受信データを保持しておく領域です。

引続き、sbreserve_locked() 関数の処理を追っていきます。

SOCKBUF_LOCK_ASSERT(sb);

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

#define SOCKBUF_LOCK_ASSERT(_sb)        mtx_assert(SOCKBUF_MTX(_sb), MA_OWNED)

mtx_assert() の呼び出しで使用している SOCKBUF_MTX() は、/usr/src/sys/sys/sockbuf.h で以下のように定義されています。

#define SOCKBUF_MTX(_sb)                (&(_sb)->sb_mtx)

mtx_assert() の詳細は割愛しますが、名称から MUTEX のロックを保持していることを確認しているものと推測します。

以上のことから SOCKBUF_LOCK_ASSERT(sb) の呼び出しは、sbreserve_locked() の第一引数 struct sockbuf *sb に対してロックを保持していることを確認しているものと推測します。

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

if (cc > sb_max_adj)
        return (0);

cc は、本関数の第二引数 u_long cc で、udp_attach() から soreserve() を呼び出す際に渡した udp_sendspace です。この変数は前回 (http://www.soum.co.jp/misc/asou/fbsd-kernel-socket/9.html) で解説していますが、デフォルト値は 9216 です。sb_max_adj (1864135) よりも小さいので、この if 文は偽となります。

if (td != NULL) {
        PROC_LOCK(td->td_proc);
        sbsize_limit = lim_cur(td->td_proc, RLIMIT_SBSIZE);
        PROC_UNLOCK(td->td_proc);
} else
        sbsize_limit = RLIM_INFINITY;

td は、本関数の第四引数 struct thread *td で、soreserve() から本関数を呼び出す際に curthred を渡しています。curthread は前回 (http://www.soum.co.jp/misc/asou/fbsd-kernel-socket/9.html) 出てきましたが、その際は

恐らく現在処理中のスレッド用の情報 (struct thread) のポインタを取得し
ているものと思われます。

と記述しました。気になったので、sys_socket() と sbreserve_locked() に printf デバッグを仕込んで確認したところ [1]、struct thread *td はいずれも NULL ではなく同位置を指していました。

ということで、if (td != NULL) は真になるので、

PROC_LOCK(td->td_proc);
sbsize_limit = lim_cur(td->td_proc, RLIMIT_SBSIZE);
PROC_UNLOCK(td->td_proc);

が実行されます。ここで使用している td->td_proc は、/usr/src/sys/sys/proc.h (http://svnweb.freebsd.org/base/release/9.1.0/sys/sys/proc.h?view=markup) で以下のように定義されています。

struct thread {
        ...
        struct proc     *td_proc;       /* (*) Associated process. */

また、PROC_LOCK()/PROC_UNLOCK() も同じく /usr/src/sys/sys/proc.h で以下のように定義されています。

#define PROC_LOCK(p)    mtx_lock(&(p)->p_mtx)
#define PROC_UNLOCK(p)  mtx_unlock(&(p)->p_mtx)

ここで参照している p_mtx も同じく /usr/src/sys/sys/proc.h で以下のように定義されています。

struct proc {
        ...
        struct mtx      p_mtx;          /* (n) Lock for this struct. */

mtx_lock()/mtx_unlock() の詳細は割愛しますが、名称から MUTEX のロック/ロック解除を行なっているものと推測します。

結局以下の処理は、

PROC_LOCK(td->td_proc);
sbsize_limit = lim_cur(td->td_proc, RLIMIT_SBSIZE);
PROC_UNLOCK(td->td_proc);

lim_cur() を実行する際に、現在のスレッド用の情報 (struct thread) が指しているプロセス情報 (struct proc) のロック/ロック解除を実行しているものと推測します。

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

/*
 * Return the current (soft) limit for a particular system resource.
 * The which parameter which specifies the index into the rlimit array
 */
rlim_t
lim_cur(struct proc *p, int which)
{
        struct rlimit rl;

        lim_rlimit(p, which, &rl);
        return (rl.rlim_cur);
}

これ以上の深追いは止めておきますが、これは恐らく ULIMIT(3) で 取得/設定 できる各プロセスの各種パラメータの上限値を取得しているものと推測します。ちなみにこの値は (bash の場合) ulimit コマンドで 取得/設定 可能です。

sbreserve_locked() から lim_cur() の第二引数 int which に渡している RLIMIT_SBSIZE は、/usr/src/sys/sys/resouce.h (http://svnweb.freebsd.org/base/release/9.1.0/sys/sys/resource.h?view=markup) で以下のように定義されています。

#define       RLIMIT_SBSIZE   9               /* maximum size of all socket buffers */

結局 sbreserve_locked() の以下の処理は、

PROC_LOCK(td->td_proc);
sbsize_limit = lim_cur(td->td_proc, RLIMIT_SBSIZE);
PROC_UNLOCK(td->td_proc);

以下の socket buffer size を取得しているものと思われます。

$ ulimit -a | grep sock
socket buffer size       (bytes, -b) unlimited

ちなみに、printf デバッグで確認したところ、sbsize_limit には 9223372036854775807 (= 2^63 - 1, long long (符号付 64bit) の最大値) が代入されていました。

次の処理は、chgsbsize() の呼び出しです。

if (!chgsbsize(so->so_cred->cr_uidinfo, &sb->sb_hiwat, cc,
    sbsize_limit))
        return (0);

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

/*
 * Change the total socket buffer size a user has used.
 */
int
chgsbsize(uip, hiwat, to, max)
        struct  uidinfo *uip;
        u_int  *hiwat;
        u_int   to;
        rlim_t  max;
{
        int diff;

        diff = to - *hiwat;
        if (diff > 0) {
                if (atomic_fetchadd_long(&uip->ui_sbsize, (long)diff) + diff > max) {
                        atomic_subtract_long(&uip->ui_sbsize, (long)diff);
                        return (0);
                }
        } else {
                atomic_add_long(&uip->ui_sbsize, (long)diff);
                if (uip->ui_sbsize < 0)
                        printf("negative sbsize for uid = %d\n", uip->ui_uid);
        }
        *hiwat = to;
        return (1);
}

詳細は割愛しますが、コメントから推測すると、ユーザ情報 (struct uidinfo) に保持しているソケットバッファの使用量を更新しているようです。また、*hiwat に to を代入しています。

次の処理です。

sb->sb_mbmax = min(cc * sb_efficiency, sb_max);
if (sb->sb_lowat > sb->sb_hiwat)
        sb->sb_lowat = sb->sb_hiwat;
return (1);

sb->sb_mbmax を更新し、必要であれば sb->sb_lowat も更新したのち、1 を返却して終了します。

一行目の min() で使用している sb_efficiency は、sbreserve_locked() と同じ /usr/src/sys/kern/uipc_sockbuf.c で以下のように定義されています。

static  u_long sb_efficiency = 8;       /* parameter for sbreserve() */

sbreserve_lockd() と chgsbsize() の呼び出しに関しては、分かりにくいので printf デバッグで各変数の値を表示してみました。

以下に、soreserve() からの sbreserve_locked() の呼び出しの結果、

sbreserve_locked(&so->so_snd, sndcc = 9216, so, td)
sbreserve_locked(&so->so_rcv, rcvcc = 42080, so, td)

各変数の値は以下のようになります。

uip->ui_sbsize = 32768 + 9216 + 42080 = 84064

so->so_snd.sb_mbmax = 9216 * 8 = 73728
so->so_snd.sb_hiwat = 9216
so->so_snd.sb_lowat = 0

so->so_rcv.sb_mbmax = 42080 * 8 = 336640
so->so_rcv.sb_hiwat = 42080
so->so_rcv.sb_lowat = 0

sbreserve_locked() から soreserve() に戻ります。

if (so->so_rcv.sb_lowat == 0)
        so->so_rcv.sb_lowat = 1;
if (so->so_snd.sb_lowat == 0)
        so->so_snd.sb_lowat = MCLBYTES;
if (so->so_snd.sb_lowat > so->so_snd.sb_hiwat)
        so->so_snd.sb_lowat = so->so_snd.sb_hiwat;
SOCKBUF_UNLOCK(&so->so_rcv);
SOCKBUF_UNLOCK(&so->so_snd);
return (0);

so->so_rvc.sb_lowat, so->so_snd.sb_lowat 共に 0 なので、それぞれ 1, MCLBYTES が代入されます。MCLBYTES は今回の 80行目あたりで 2048 であることが分かっています。2048 は so->so_snd.sb_hiwat よりも小さいので、so->so_snd.sb_lowat は 2048 になります。

この結果、先ほどの各変数値は以下の値になります。

uip->ui_sbsize = 32768 + 9216 + 42080 = 84064

so->so_snd.sb_mbmax = 9216 * 8 = 73728
so->so_snd.sb_hiwat = 9216
so->so_snd.sb_lowat = 2048

so->so_rcv.sb_mbmax = 42080 * 8 = 336640
so->so_rcv.sb_hiwat = 42080
so->so_rcv.sb_lowat = 1

SOCKBUF_UNLOCK() は、soreserve() の最初に SOCKBUF_LOCK() で獲得したロックを解除しています。

soreserve() は、0 を返却して udp_attach() に戻ります。

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

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

[1]FreeBSD のカーネルのソースコードに printf(“Debugn”) を追加すると、コンソールと /var/log/messages に “Debugn” が出力されます。

著者プロフィール

asou

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

記事一覧Index