FreeBSD kernel SOCKET I/F 探検

第21回 recvfrom() システムコール (1)

2016.02.17

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

revcfrom() システムコール、一回目です。

本連載の 三回目 に掲載した server.c を再度掲載します。

server.c

#include <netinet/in.h>
#include <stdio.h>
#include <stdlib.h>
#include <strings.h>
#include <sys/types.h>
#include <sys/socket.h>

/**
* Main
*/
int
main(int argc, char *argv[])
{
     int port;
     int sd;
     int result;
     struct sockaddr_in sa;
     struct sockaddr_in from;
     socklen_t addrlen;
     ssize_t rbytes;
     ssize_t sbytes;
     char buf[1024];

     /* Is not enough argument? */
     if (argc != 2) {
             fprintf(stderr, "Usage: server PortNo\n");
             exit(1);
     }
     port = atoi(argv[1]);

     /* Create a socket */
     sd = socket(PF_INET, SOCK_DGRAM, 0);
     if (sd < 0) {
             perror("socket");
             exit(1);
     }

     /* Bind the port number */
     bzero(&sa, sizeof (sa));
#ifdef __FreeBSD__
     sa.sin_len = sizeof (sa);
#endif /* __FreeBSD__ */
     sa.sin_family = AF_INET;
     sa.sin_port = htons(port);
     result = bind(sd, (struct sockaddr *)&sa, sizeof (sa));
     if (result < 0) {
             perror("bind");
             exit(1);
     }

     while (1) {
             /* Receive data */
             addrlen = sizeof (from);
             rbytes = recvfrom(sd, buf, sizeof (buf), 0, (struct sockaddr *)&from, &addrlen);
             if (rbytes < 0) {
                     perror("recvfrom");
                     continue;
             }
             printf("Received %dbytes\n", rbytes);

             /* Send data */
             sbytes = sendto(sd, buf, rbytes, 0, (struct sockaddr *)&from, addrlen);
             if (sbytes < 0) {
                     perror("sendto");
                     continue;
             }
             if (sbytes != rbytes) {
                     fprintf(stderr, "Length of the transmitted data does not matched: Tried to send %dbytes, Transmitted %dbytes.\n", rbytes, sbytes);
             }
     }

     /* Success */
     return 0;
}

前回で bind() システムコールに該当する kernel の処理 sys_bind() の解析が終了しました。server.c を見ると、bind() システムコールの次は、while ループの中で recvfrom() システムコールを呼び出してクライアントから送信されたデータを受け取ります。

recvfrom() システムコールを実行すると、kernel 内では sys_recvfrom() が呼ばれます。sys_recvfrom() 関数は、/usr/src/sys/kern/uipc_syscalls.c ( http://svnweb.freebsd.org/base/release/9.1.0/sys/kern/uipc_syscalls.c?view=markup) で以下の様に定義されています。

sys_recvfrom(td, uap)
      struct thread *td;
      struct recvfrom_args /* {
              int     s;
              caddr_t buf;
              size_t  len;
              int     flags;
              struct sockaddr * __restrict    from;
              socklen_t * __restrict fromlenaddr;
      } */ *uap;
{
      struct msghdr msg;
      struct iovec aiov;
      int error;

      if (uap->fromlenaddr) {
              error = copyin(uap->fromlenaddr,
                  &msg.msg_namelen, sizeof (msg.msg_namelen));
              if (error)
                      goto done2;
      } else {
              msg.msg_namelen = 0;
      }
      msg.msg_name = uap->from;
      msg.msg_iov = &aiov;
      msg.msg_iovlen = 1;
      aiov.iov_base = uap->buf;
      aiov.iov_len = uap->len;
      msg.msg_control = 0;
      msg.msg_flags = uap->flags;
      error = recvit(td, uap->s, &msg, uap->fromlenaddr);
done2:
      return(error);
}

最初の「if (uap->fromlenaddr) 」で評価している uap->fromlenaddr は server.c が呼び出している recvfrom() (下記参照)

rbytes = recvfrom(sd, buf, sizeof (buf), 0, (struct sockaddr *)&from, &addrlen);

の第六引数「&addrlen」なので、この if 文は真となり次の copyin() の処理が実行されます。

copyin() に関しては、本連載の 十三回目 でも出てきましたが、ユーザー空間上にある「uap->fromlenadd」が指し示すデータをカーネル空間の「msg.msg_namelen」にコピーしています。

次の処理は struct mshgdr msg と struct iovec aiov に必要な情報を代入しています。struct msghdr は、/usr/src/sys/sys/socket.h ( http://svnweb.freebsd.org/base/release/9.1.0/sys/sys/socket.h?view=markup) で以下のように定義されています。

/*
* Message header for recvmsg and sendmsg calls.
* Used value-result for recvmsg, value only for sendmsg.
*/
struct msghdr {
      void            *msg_name;              /* optional address */
      socklen_t        msg_namelen;           /* size of address */
      struct iovec    *msg_iov;               /* scatter/gather array */
      int              msg_iovlen;            /* # elements in msg_iov */
      void            *msg_control;           /* ancillary data, see below */
      socklen_t        msg_controllen;        /* ancillary data buffer len */
      int              msg_flags;             /* flags on received message */
};

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

struct iovec {
      void    *iov_base;      /* Base address. */
      size_t   iov_len;       /* Length. */
};

次の処理は、recvit() 関数の呼び出しです。recvit() 関数は、/usr/src/sys/uipc_syscall.c ( http://svnweb.freebsd.org/base/release/9.1.0/sys/kern/uipc_syscalls.c?view=markup) で以下の様に定義されています。

static int
recvit(td, s, mp, namelenp)
     struct thread *td;
     int s;
     struct msghdr *mp;
     void *namelenp;
{
     int error;

     error = kern_recvit(td, s, mp, UIO_USERSPACE, NULL);
     if (error)
             return (error);
     if (namelenp) {
             error = copyout(&mp->msg_namelen, namelenp, sizeof (socklen_t));
#ifdef COMPAT_OLDSOCK
             if (mp->msg_flags & MSG_COMPAT)
                     error = 0;      /* old recvfrom didn't check */
#endif
     }
     return (error);
}

最初に呼び出している kern_recvit() 関数も同じ uipc_syscall.c で以下の様に定義されています。

int
kern_recvit(td, s, mp, fromseg, controlp)
      struct thread *td;
      int s;
      struct msghdr *mp;
      enum uio_seg fromseg;
      struct mbuf **controlp;
{
      struct uio auio;
      struct iovec *iov;
      int i;
      ssize_t len;
      int error;
      struct mbuf *m, *control = 0;
      caddr_t ctlbuf;
      struct file *fp;
      struct socket *so;
      struct sockaddr *fromsa = 0;
#ifdef KTRACE
      struct uio *ktruio = NULL;
#endif

      if (controlp != NULL)
              *controlp = NULL;

      AUDIT_ARG_FD(s);
      error = getsock_cap(td->td_proc->p_fd, s, CAP_READ, &fp, NULL);
      if (error)
              return (error);
      so = fp->f_data;

#ifdef MAC
      error = mac_socket_check_receive(td->td_ucred, so);
      if (error) {
              fdrop(fp, td);
              return (error);
      }
#endif

      auio.uio_iov = mp->msg_iov;
      auio.uio_iovcnt = mp->msg_iovlen;
      auio.uio_segflg = UIO_USERSPACE;
      auio.uio_rw = UIO_READ;
      auio.uio_td = td;
      auio.uio_offset = 0;                    /* XXX */
      auio.uio_resid = 0;
      iov = mp->msg_iov;
      for (i = 0; i < mp->msg_iovlen; i++, iov++) {
              if ((auio.uio_resid += iov->iov_len) < 0) {
                      fdrop(fp, td);
                      return (EINVAL);
              }
      }

最初の「if (controlp != NULL)」は、recvit() から kern_recvit() を呼び出す際に NULLを渡しているので次に進みます。

AUDIT_ARG_FD() は本連載の 十三回目 でも出てきたので、ここでの説明は割愛します。

getsock_cap() は本連載の 十四回目 でも出てきたので、ここでの説明は割愛します。

#ifdef MAC は Mac 用の処理と思われるので割愛します

その次の処理は struct uio auio に必要な情報を代入しています。struct uio は、/usr/src/sys/sys/uio.h ( http://svnweb.freebsd.org/base/release/9.1.0/sys/sys/uio.h?view=markup) で以下の様に定義されています。

struct uio {
      struct  iovec *uio_iov;         /* scatter/gather list */
      int     uio_iovcnt;             /* length of scatter/gather list */
      off_t   uio_offset;             /* offset in target object */
      ssize_t uio_resid;              /* remaining bytes to process */
      enum    uio_seg uio_segflg;     /* address space */
      enum    uio_rw uio_rw;          /* operation */
      struct  thread *uio_td;         /* owner */
};

下記の for 文の次の if 文で auio.uio_resid に iov->iov_len を加算し 0 未満になっていないか確認していますが、mp->msg_iovlen が 1 でiov->iov_len は 1024 なので、今回の呼び出しではこの if 文は真にはなりません。

for (i = 0; i < mp->msg_iovlen; i++, iov++) {
      if ((auio.uio_resid += iov->iov_len) < 0) {
              fdrop(fp, td);
              return (EINVAL);
      }
}

#ifdef KTRACE は割愛します。次は soreceive() の呼び出しです。

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() の呼び出しの直前までのデータ構造を以下に示します。

sys_recvfrom() から kern_recvit() の soreceive() の呼び出しの直前までの処理で変更した変数を青、変更した値は赤で示して います。

著者プロフィール

asou

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

記事一覧Index