FreeBSD kernel SOCKET I/F 探検

第4回 socket() システムコール (2)

2013.10.17 (2014.01.16更新)

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

前回 の「ちょっと脱線2」に記述した PF_INET に関してですが、ちょっと補足をすると SOCKET(2) には以下のように記述されています。

The domain argument specifies a communications domain within which
communication will take place; this selects the protocol family
which should be used.  These families are defined in the include
file <sys/socket.h>.  The currently understood formats are:

    PF_LOCAL    Host-internal protocols, formerly called PF_UNIX,
    PF_UNIX     Host-internal protocols, deprecated, use PF_LOCAL,
    PF_INET     Internet version 4 protocols,

さて、socket() システムコール二回目です。/usr/src/sys/kern/uipc_syscalls.c (http://svnweb.freebsd.org/base/release/9.1.0/sys/kern/uipc_syscalls.c?view=markup) sys_socket() から falloc() の呼び出しです。

error = falloc(td, &fp, &fd, 0);

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

int
falloc(struct thread *td, struct file **resultfp, int *resultfd, int flags)
{
        struct file *fp;
        int error, fd;

        error = falloc_noinstall(td, &fp);
        if (error)
                return (error);         /* no reference held on error */

        error = finstall(td, fp, &fd, flags);
        if (error) {
                fdrop(fp, td);          /* one reference (fp only) */
                return (error);
        }

第一引数は sys_socket() と同様に struct thread *td です。第二引数の struct file **resultfp はオープン中のファイルに関する情報を管理するもので、falloc() で新たに領域を確保しそれを呼び出し元に返却します。第三引数 int *resultdf は、falloc() で割り当てたファイルディスクリプタを呼び出し元に返却します。ファイルディスクリプタは前回 struct filedesc *fdp で登場していますが、こちらは int です。struct filedesc は、プロセスがオープンしている全てのファイルディスクリプタを管理しています。int *resultfd は、struct filedesc のメンバ変数 strict file **fd_ofiles の添字になります (例: fd_ofiles[ *resultfd]) 第二引数の strict file **resultfp とローカル変数 struct file *fp の struct file は、/usr/src/sys/sys/file.h (http://svnweb.freebsd.org/base/release/9.1.0/sys/sys/file.h?view=markup) で定義されています。

falloc() 関数の処理を見ていきます。

error = falloc_noinstall(td, &fp);

fallc_noinstall() は、同じ kern_descrip.c 内で以下のように定義されています。

int
falloc_noinstall(struct thread *td, struct file **resultfp)
{
        struct file *fp;
        int maxuserfiles = maxfiles - (maxfiles / 20);
        static struct timeval lastfail;
        static int curfail;

        KASSERT(resultfp != NULL, ("%s: resultfp == NULL", __func__));

第一引数は falloc() と同様に struct thread *td で、第二引数も同様に struct file **resultfp です。

falloc_noinstall() の最初の処理は、

KASSERT(resultfp != NULL, ...)

です。これは、resultft が NULL でないことを確認するアサートで、ASSERT(3) と同等の処理をしていると理解してください。今後も KASSERT() が出てくると思いますが、説明は割愛します。

次の処理は、

if ((openfiles >= maxuserfiles &&
    priv_check(td, PRIV_MAXFILES) != 0) ||
    openfiles >= maxfiles) {
        if (ppsratecheck(&lastfail, &curfail, 1)) {
                printf("kern.maxfiles limit exceeded by uid %i, "
                    "please see tuning(7).\n", td->td_ucred->cr_ruid);
        }
        return (ENFILE);
}

この処理は、システムで許されているオープン可能なファイル数 [1] が越えていないことを確認しています。上限を越えてしまう場合には、ENFILE [2] を返して終了します。

openfiles は、kern_descrip.c で以下のように定義されています。openfiles は、システム全体でオープンしているのファイル数を保持しています。

volatile int openfiles;                 /* actual number of open files */

maxuserfiles は、falloc_noinstall() の最初の方で以下のように定義と初期化を行なっています。

int maxuserfiles = maxfiles - (maxfiles / 20);

maxfiles は、kern_descrip.c で以下のように定義されています。

SYSCTL_INT(_kern, KERN_MAXFILES, maxfiles, CTLFLAG_RW,
    &maxfiles, 0, "Maximum number of files");

maxfiles は、システム全体でオープン可能なファイル数を表しています。SYSCTL(8) コマンドで変更可能です。

ppsratecheck() は、/usr/src/sys/kern/kern_time.c (http://svnweb.freebsd.org/base/release/9.1.0/sys/kern/kern_time.c?view=markup) で定義されています。ppsratecheck() の前に以下のコメントがあります。コメントから察するに、DoS 攻撃などの回避用ではないかと推測します。

/*
 * ppsratecheck(): packets (or events) per second limitation.

オープン可能なファイル数の上限に達していなければ、

  • システム全体のオープン中のファイル数 (openfiles) のインクリメント
  • 新しい struct file 用領域の確保および初期化

を行なうので、これらを順番に説明します。

atomic_add_int(&opefiles, 1);

atomic_add_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) です。この処理は、openfiles に 1 を加算しています。openfiles += 1 とほぼ同義です。しかし、他のプロセス (CPU [3] ) が openfiles を更新する可能性があります。そのため、++openfiles とは書かずに、atomic_add_int() [4] を使用しています。

ちょっと脱線1

ちなみに、atomic_add_int() の部分を objdump -d kern_descrip.o [5] コマンドで逆アセンブルしてみると、

18c0:       f0 83 05 00 00 00 00    lock addl $0x1,0x0(%rip)

このように lock [6] addl 命令に展開されます。

次は struct file 用領域の確保と初期化です。

fp = uma_zalloc(file_zone, M_WAITOK | M_ZERO);
refcount_init(&fp->f_count, 1);
fp->f_cred = crhold(td->td_ucred);
fp->f_ops = &badfileops;
fp->f_data = NULL;
fp->f_vnode = NULL;

uma_zalloc() で sruct file 用の領域を確保し、次に初期化を行ないます。uma_zalloc() の詳細は割愛します。MALLOC(3) と似た機能と理解してください。f_count の初期化には refcount_init() を使用しています。f_cred には struct thread が保持している認証情報へのポインタをコピーします。f_ops, f_data, f_vnode に関しては見たままなので、説明は割愛します。

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

static __inline void
refcount_init(volatile u_int *count, u_int value)
{
        *count = value;
}

static __inline void
refcount_acquire(volatile u_int *count)
{
        atomic_add_acq_int(count, 1);
}

この後で出てくる refcount_acquire() も一緒に掲載しておきます。refcount_acquire() を見ると atomic_add_acq_int() を呼び出しています。f_count は struct fileを参照しているプロセス数でしょうか?UNIX では同一ファイルディスクリプタを複数プロセスで共有できるので、この f_count によりファイルディスクリプタを参照しているプロセス数を管理しているものと思われます。プロセスで共有している情報は複数のプロセスから同時に更新される可能性があります。このためatomic_add_acq_int() を使用してアクセスが競合した場合に問題が起きないようにしています。refcount_init() は atomic_xxxx() を使用していないので、refcount_init() を呼ばずに fp->f_count = 1; と書けば良さそうですが、「fp->f_count のアクセスには refcount_xxxx() を使用する」というポリシーにしたのではないかと推測しています。

atomic_add_acq_int() は atomic_add_int() と同様に、/usr/src/sys/ */アーキテクチャー/include/atomic.h で定義されています。やっていることは atomic_add_int() と同じと考えて差し支えありません。

crhold() は、/usr/src/sys/kern/kern_prot.c (http://svnweb.freebsd.org/base/release/9.1.0/sys/kern/kern_prot.c?view=markup) で以下のように定義されています。struct ucred は、/usr/src/sys/sys/ucred.h (http://svnweb.freebsd.org/base/release/9.1.0/sys/sys/ucred.h?view=markup) で定義されています。refcount_acquire() を使用して、struct ucred のメンバ変数 cr_ref に 1 を加算しています。

struct ucred *
crhold(struct ucred *cr)
{

        refcount_acquire(&cr->cr_ref);
        return (cr);
}

最後に、resultfp に今確保した struct file * を代入し、0 (正常終了)を返却して終了します。

*resultfp = fp;
return (0);

falloc_noinstall() 終了時のデータ構造を以下に示します。

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

[1]ソケットなのになぜファイル数なのか?UNIX では、各種の資源をファイルと同等に扱うという文化があります (例: /dev/tty)。このため、ソケットもファイルと同等に扱っています。
[2]SOCKET(2), INTRO(2) にも記載されていますが、「Too many open files in system.」を表すエラー番号です。システムコールを呼び出した際にエラーが発生した際に、int errno に返されます。
[3]「プロセッサ」とも言いますが、「プロセス」と紛らわしいので、本連載では「CPU」と表記します。
[4]atomic とは辞書によると「原子の、極小の」といった意味ですが、プログラミングの世界では「不可分操作」という意味で使用します。参照 Wikipedia: http://ja.wikipedia.org/wiki/%E4%B8%8D%E5%8F%AF%E5%88%86%E6%93%8D%E4%BD%9C
[5]kern_descrip.o は、kernel を構築すると /usr/obj/usr/src/sys/MYKERNEL ディレクトリ以下に生成されます。MYKERNEL は、コンフィグレーションファイル名なので、環境により異なります。kernel の構築方法はハンドブックの「9.5. カスタムカーネルの構築とインストール」 (http://www.jp.freebsd.org/www.FreeBSD.org/doc/ja_JP.eucJP/books/handbook/kernelconfig-building.html) などを参照してください。
[6]lock 命令を実行すると、その直後の 1命令の実行が終了するまで、他の CPU は動作を中断します。詳しくは、「IA-32 インテル (R) アーキテクチャ・ソフトウェア・デベロッパーズ・マニュアル」 (http://www.intel.co.jp/content/dam/www/public/ijkk/jp/ja/documents/developer/IA32_Arh_Dev_Man_Vol2A_i.pdf) (http://www.intel.com/content/dam/www/public/us/en/documents/manuals/64-ia-32-architectures-software-developer-instruction-set-reference-manual-325383.pdf) を参照してください。

著者プロフィール

asou

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

記事一覧Index