FreeBSD kernel SOCKET I/F 探検

第14回 bind() システムコール (2)

2014.10.22 (2015.05.28更新)

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

bind() システムコール、二回目です。sys_bind() ->kern_bind() -> getsock_cap() と呼び出したところです。

getsock_cap() も sys_bind() と同じ uipc_syscalls.c (http://svnweb.freebsd.org/base/release/9.1.0/sys/kern/uipc_syscalls.c?view=markup) で以下のように定義されています。

/*
 * Convert a user file descriptor to a kernel file entry and check that, if
 * it is a capability, the right rights are present. A reference on the file
 * entry is held upon returning.
 */
static int
getsock_cap(struct filedesc *fdp, int fd, cap_rights_t rights,
    struct file **fpp, u_int *fflagp)
{
        struct file *fp;
#ifdef CAPABILITIES
        struct file *fp_fromcap;
        int error;
#endif

        fp = NULL;
        if ((fdp == NULL) || ((fp = fget_unlocked(fdp, fd)) == NULL))
                return (EBADF);
#ifdef CAPABILITIES
        /*
         * If the file descriptor is for a capability, test rights and use
         * the file descriptor referenced by the capability.
         */
        error = cap_funwrap(fp, rights, &fp_fromcap);
        if (error) {
                fdrop(fp, curthread);
                return (error);
        }
        if (fp != fp_fromcap) {
                fhold(fp_fromcap);
                fdrop(fp, curthread);
                fp = fp_fromcap;
        }
#endif /* CAPABILITIES */
        if (fp->f_type != DTYPE_SOCKET) {
                fdrop(fp, curthread);
                return (ENOTSOCK);
        }
        if (fflagp != NULL)
                *fflagp = fp->f_flag;
        *fpp = fp;
        return (0);
}

GENERIC kernel のコンパイル時に CAPABILITIES は指定されていないので、不要な部分を削除すると以下のようになります。

/*
 * Convert a user file descriptor to a kernel file entry and check that, if
 * it is a capability, the right rights are present. A reference on the file
 * entry is held upon returning.
 */
static int
getsock_cap(struct filedesc *fdp, int fd, cap_rights_t rights,
    struct file **fpp, u_int *fflagp)
{
        struct file *fp;

        fp = NULL;
        if ((fdp == NULL) || ((fp = fget_unlocked(fdp, fd)) == NULL))
                return (EBADF);
        if (fp->f_type != DTYPE_SOCKET) {
                fdrop(fp, curthread);
                return (ENOTSOCK);
        }
        if (fflagp != NULL)
                *fflagp = fp->f_flag;
        *fpp = fp;
        return (0);
}

データ構造があった方が分かりやすいと思うので、前々回掲載した図をここに載せておきます。

それでは、getsock_cap() の処理を見ていきます。

if ((fdp == NULL) || ((fp = fget_unlocked(fdp, fd)) == NULL))

fdp には kern_bind() の第一引数 struct thread *td の td->td_proc->p_fd が渡されています。これは NULL ではないので fget_unlocked() を呼びます。

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

struct file *
fget_unlocked(struct filedesc *fdp, int fd)
{
        struct file *fp;
        u_int count;

        if (fd < 0 || fd >= fdp->fd_nfiles)
                return (NULL);
        /*
         * Fetch the descriptor locklessly.  We avoid fdrop() races by
         * never raising a refcount above 0.  To accomplish this we have
         * to use a cmpset loop rather than an atomic_add.  The descriptor
         * must be re-verified once we acquire a reference to be certain
         * that the identity is still correct and we did not lose a race
         * due to preemption.
         */
        for (;;) {
                fp = fdp->fd_ofiles[fd];
                if (fp == NULL)
                        break;
                count = fp->f_count;
                if (count == 0)
                        continue;
                /*
                 * Use an acquire barrier to prevent caching of fd_ofiles
                 * so it is refreshed for verification.
                 */
                if (atomic_cmpset_acq_int(&fp->f_count, count, count + 1) != 1)
                        continue;
                if (fp == fdp->fd_ofiles[fd])
                        break;
                fdrop(fp, curthread);
        }

        return (fp);
}

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

if (fd < 0 || fd >= fdp->fd_nfiles)
        return (NULL);

fd には bind(int s, ...) システムコールの第一引数が渡されます。bind()システムコールを呼び出しているプログラムに誤りがなければ、この if 文は偽になります。

次はまとめて見ていきます。fdp は上図の右上の struct filedesc を指しています。fdp->fd_ofiles[fd] は上図左下の struct file を指しています。

for (;;) {
        fp = fdp->fd_ofiles[fd];
        if (fp == NULL)
                break;
        count = fp->f_count;
        if (count == 0)
                continue;

fp->f_count の値は現在 2 なので、次に進みます。

if (atomic_cmpset_acq_int(&fp->f_count, count, count + 1) != 1)
        continue;

atomic_cmpset_acq_int() は、/usr/src/sys/アーキテクチャー/include/atomic.h で定義されています。i386 であれば /usr/src/sys/i386/include/atomic.h (http://svnweb.freebsd.org/base/release/9.1.0/sys/i386/include/atomic.h?view=markup) です。

詳細は割愛しますが、atomic_cmpset_acq_int() は、第一引数 fp->f_count と第二引数 (cout) を比較し、この値が等しければ第一引数で指定されたポインタの内容を第三引数 (count + 1) に書き換えるようです。

次の処理に進みます。

if (fp == fdp->fd_ofiles[fd])
        break;

for 文の最初で以下のように

fp = fdp->fd_ofiles[fd];

fdp->fd_ofiles[fd] を fp に代入しているので、取り敢えず上記の if 文の条件は真であると考えます。もしかすると fdp->fd_ofiles[fd] が他のプロセス等から書き換えられることがあるのかも知れません。

break で for ループから抜けて fp を返却して fget_unlocked() から getsock_cap() に戻ります。以下、引続き getsock_cap() に戻ったところです。

if (fp->f_type != DTYPE_SOCKET) {
        fdrop(fp, curthread);
        return (ENOTSOCK);
}
if (fflagp != NULL)
        *fflagp = fp->f_flag;
*fpp = fp;
return (0);

fp->f_type は DTYPE_SOCKET なので、次の if (fflagp != NULL) に進みます。fflagp には NULL が渡されているので、*fpp に fp を代入した後、0 を返却して kern_bind() に戻ります。

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

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

著者プロフィール

asou

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

記事一覧Index