FreeBSD kernel SOCKET I/F 探検

第22回 recvfrom() システムコール (2)

2016.05.18

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

revcfrom() システムコール、二回目です。sys_recvfrom()->recvit()->kern_recvit()と呼び出されて、kern_recvit()から soreceive()を呼び出すところです。

kern_recvit()の該当部分を再度掲載しておきます。

len = auio.uio_resid;
error = soreceive(so, &fromsa, &auio, (struct mbuf **)0,
    (mp->msg_control || controlp) ? &control : (struct mbuf **)0,
    &mp->msg_flags);
if (error) {
        if (auio.uio_resid != len && (error == ERESTART ||
            error == EINTR || error == EWOULDBLOCK))
                error = 0;
}

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

soreceive(struct socket *so, struct sockaddr **psa, struct uio *uio,
    struct mbuf **mp0, struct mbuf **controlp, int *flagsp)
{
        int error;

        CURVNET_SET(so->so_vnet);
        error = (so->so_proto->pr_usrreqs->pru_soreceive(so, psa, uio, mp0,
            controlp, flagsp));
        CURVNET_RESTORE();
        return (error);
}

so->so_proto->pr_userreq->pru_soreceiveは、/usr/src/sys/netinet/udp_usrreq.c (http://svnweb.freebsd.org/base/release/9.1.0/sys/netinet/udp_usrreq.c?view=markup) で以下のようにsoreceive_dgram()のポインタが格納されています。

#ifdef INET
struct pr_usrreqs udp_usrreqs = {
        .pru_abort =            udp_abort,
        .pru_attach =           udp_attach,
        .pru_bind =             udp_bind,
        .pru_connect =          udp_connect,
        .pru_control =          in_control,
        .pru_detach =           udp_detach,
        .pru_disconnect =       udp_disconnect,
        .pru_peeraddr =         in_getpeeraddr,
        .pru_send =             udp_send,
        .pru_soreceive =        soreceive_dgram,
        .pru_sosend =           sosend_dgram,
        .pru_shutdown =         udp_shutdown,
        .pru_sockaddr =         in_getsockaddr,
        .pru_sosetlabel =       in_pcbsosetlabel,
        .pru_close =            udp_close,
};
#endif /* INET */

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

/*
 * Optimized version of soreceive() for simple datagram cases from userspace.
 * Unlike in the stream case, we're able to drop a datagram if copyout()
 * fails, and because we handle datagrams atomically, we don't need to use a
 * sleep lock to prevent I/O interlacing.
 */
int
soreceive_dgram(struct socket *so, struct sockaddr **psa, struct uio *uio,
    struct mbuf **mp0, struct mbuf **controlp, int *flagsp)
{
        struct mbuf *m, *m2;
        int flags, error;
        ssize_t len;
        struct protosw *pr = so->so_proto;
        struct mbuf *nextrecord;

        if (psa != NULL)
                *psa = NULL;
        if (controlp != NULL)
                *controlp = NULL;
        if (flagsp != NULL)
                flags = *flagsp &~ MSG_EOR;
        else
                flags = 0;

        /*
         * For any complicated cases, fall back to the full
         * soreceive_generic().
         */
        if (mp0 != NULL || (flags & MSG_PEEK) || (flags & MSG_OOB))
                return (soreceive_generic(so, psa, uio, mp0, controlp,
                    flagsp));

        /*
         * Enforce restrictions on use.
         */
        KASSERT((pr->pr_flags & PR_WANTRCVD) == 0,
            ("soreceive_dgram: wantrcvd"));
        KASSERT(pr->pr_flags & PR_ATOMIC, ("soreceive_dgram: !atomic"));
        KASSERT((so->so_rcv.sb_state & SBS_RCVATMARK) == 0,
            ("soreceive_dgram: SBS_RCVATMARK"));
        KASSERT((so->so_proto->pr_flags & PR_CONNREQUIRED) == 0,
            ("soreceive_dgram: P_CONNREQUIRED"));

それではsoreceive_dgram()を最初から見ていきますが変数定義の直後の二つの if文の説明は割愛します。その次のif文ですが、

if (flagsp != NULL)
        flags = *flagsp &~ MSG_EOR;
else
        flags = 0;

flagspにはsys_recvfrom()のローカル変数struct msghdr msgのメンバ変数 msg_flagsのポインタが渡れます。このためこのif文は真になります。この msg_flagsにはrecvfrom()の第四引数int flagsが格納されますが、server.cが recvfrom()を呼び出す際に0を渡しているので結局以下の処理の結果flagsには 0が格納されます。

flags = *flagsp &~ MSG_EOR;

これに続く以下のif文ですが、ここで参照しているmp0は呼び出し元のkern_recvit() でNULLを渡しています。また、直前の処理でflagsには0が格納されています。結局以下のif文は偽となり次の処理に進みます。

/*
 * For any complicated cases, fall back to the full
 * soreceive_generic().
 */
if (mp0 != NULL || (flags & MSG_PEEK) || (flags & MSG_OOB))
        return (soreceive_generic(so, psa, uio, mp0, controlp,
            flagsp));

次の処理は以下のようにKASSERT()なので、詳細は割愛します。

/*
 * Enforce restrictions on use.
 */
KASSERT((pr->pr_flags & PR_WANTRCVD) == 0,
    ("soreceive_dgram: wantrcvd"));
KASSERT(pr->pr_flags & PR_ATOMIC, ("soreceive_dgram: !atomic"));
KASSERT((so->so_rcv.sb_state & SBS_RCVATMARK) == 0,
    ("soreceive_dgram: SBS_RCVATMARK"));
KASSERT((so->so_proto->pr_flags & PR_CONNREQUIRED) == 0,
    ("soreceive_dgram: P_CONNREQUIRED"));

次の処理を以下に掲載します。

/*
 * Loop blocking while waiting for a datagram.
 */
SOCKBUF_LOCK(&so->so_rcv);
while ((m = so->so_rcv.sb_mb) == NULL) {
        KASSERT(so->so_rcv.sb_cc == 0,
            ("soreceive_dgram: sb_mb NULL but sb_cc %u",
            so->so_rcv.sb_cc));
        if (so->so_error) {
                error = so->so_error;
                so->so_error = 0;
                SOCKBUF_UNLOCK(&so->so_rcv);
                return (error);
        }
        if (so->so_rcv.sb_state & SBS_CANTRCVMORE ||
            uio->uio_resid == 0) {
                SOCKBUF_UNLOCK(&so->so_rcv);
                return (0);
        }
        if ((so->so_state & SS_NBIO) ||
            (flags & (MSG_DONTWAIT|MSG_NBIO))) {
                SOCKBUF_UNLOCK(&so->so_rcv);
                return (EWOULDBLOCK);
        }
        SBLASTRECORDCHK(&so->so_rcv);
        SBLASTMBUFCHK(&so->so_rcv);
        error = sbwait(&so->so_rcv);
        if (error) {
                SOCKBUF_UNLOCK(&so->so_rcv);
                return (error);
        }
}
SOCKBUF_LOCK_ASSERT(&so->so_rcv);

whileループ開始直前のSOCKBUF_LOCK()とループ内でエラーでreturnする直前に実行しているSOCKBUF_UNLOCK()は、本連載の 第九回 で簡単に説明していますので、SOCKBUF_LOCK()/SOCKBUF_UNLOCK()自体の説明はここでは割愛しますが、このSOCKBUF_LOCK()はこのwhileループ以降で使用するソケットの受信バッファso->so_rcvへの操作の競合を抑止するためのロックです。

so->so_rcvは、本連載で最初に扱ったsocket()システムコールの説明で出てきましたが、struct socketのメンバ変数struct sockbuf so_rcvです。本連載の 第十二回 で使用した図から一部抜粋したものを以下に掲載します。

上記の図は色付きのまま引用していますが、これは今回扱っているrecvfrom()の呼び出しで変更等を行ったものではなく、socket()の呼び出し時の操作を示しているものです。

struct socket *soは、soreceive_dgram()関数の第一引数として渡されています。なお、struct socketは、/usr/src/sys/sys/socketvar.h (http://svnweb.freebsd.org/base/release/9.1.0/sys/sys/socketvar.h?view=markup) で定義されています。またstruct sockbufは、/usr/src/sys/sys/sockbuf.h (http://svnweb.freebsd.org/base/release/9.1.0/sys/sys/sockbuf.h?view=markup) で定義されています。これらは既に何度か掲載しているので、ここでは定義内容は記載しません。

whileループ終了直後のSOCKBUF_LOCK_ASSERT()ですが、こちらも本連載の 第十回 で簡単に説明していますので、ここでの説明は割愛します。

whileループ内の処理の説明を行います。

KASSERT()の説明は割愛して、最初の

if (so->so_error) {
        error = so->so_error;
        so->so_error = 0;
        SOCKBUF_UNLOCK(&so->so_rcv);
        return (error);
}

ですが、これは何らかのエラーが発生していないか確認しています。恐らくエラー発生時にはここに0以外の値が書き込まれているものと推測します。ちなみにその値ですが、これは恐らくman 2 introで解説されているerrnoで定義されている値が格納され、それがrecvfrom()システムコールからの戻り値としてユーザープログラム(本連載ではserver.c)に返却されているものと思われます。

ちょっと脱線:

上記の「man 2 intro」は、FreeBSDマシンにログインしてcshなどのShell 上で下記の様にmanコマンドを実行することを意図しています。

% man 2 intro

と同時に、オンラインマニュアルの第二セクションのintroに記述されていますということを伝えたいという意図もあります。

似たようなことを以前にも当連載上で記述したような気もしますが、本人も覚えていないので念のため追記しておきます。

次の

if (so->so_rcv.sb_state & SBS_CANTRCVMORE ||
    uio->uio_resid == 0) {
        SOCKBUF_UNLOCK(&so->so_rcv);
        return (0);
}

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

#define SBS_CANTRCVMORE         0x0020  /* can't receive more data from peer */

コメントから推測すると、通信相手がソケットを閉じたなどの理由でこれ以上受信できない状態を示すものかと推測しましたが、現在扱っているソケットはUDPなのでそもそも接続断など、相手がソケットを閉じたと言った事象は拾えないはずです。ということで申し訳ないのですが、UDPのソケットの場合にSBS_CANTRCVMOREが真になる状況は、現時点では把握できていません。いずれにしろ、現時点でso->so_rcv.sb_state にSB_CANTRCVMOREは設定されていないものと判断します。

次の行のuioですが、これは現在sys_recvfrom()->recvit()->kern_recvit()-> soreceive()->soreceive_dgram() と呼び出されているkern_recv()のローカル変数 struct uio auioのポインタです。

struct uio auioを再掲載しておきます。

uio->uio_residは1024なので、結局このif文は偽となります。

次に進みます。

if ((so->so_state & SS_NBIO) ||
    (flags & (MSG_DONTWAIT|MSG_NBIO))) {
        SOCKBUF_UNLOCK(&so->so_rcv);
        return (EWOULDBLOCK);
}

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

sockstate.h:#define     SS_NBIO                 0x0100  /* non-blocking ops */

コメントに「non-blocking」と書かれています。これは恐らく受信データがない場合にrecvfrom()システムコールをブロックしないことを意味しているものと推測します。現時点ではso->so_stateにSS_NBIOは設定されていないものと判断します。

次のMSG_DONTWAITとMSG_NBIOは、/usr/src/sys/sys/socket.h (http://svnweb.freebsd.org/base/release/9.1.0/sys/sys/socket.h?view=markup)

#define MSG_DONTWAIT    0x80            /* this message should be nonblocking */
#define MSG_NBIO        0x4000          /* FIONBIO mode, used by fifofs */

ここで評価しているflagsは、soreceive_dgram()の最初のほうで説明しましたが0になっています。ということで、このif文も偽となります。

結局、このwhileループの各if文は全て偽となり、次の処理に進みます。

SBLASTRECORDCHK(&so->so_rcv);
SBLASTMBUFCHK(&so->so_rcv);
error = sbwait(&so->so_rcv);
if (error) {
        SOCKBUF_UNLOCK(&so->so_rcv);
        return (error);
}

が、今回はここで終了です。

著者プロフィール

asou

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

記事一覧Index