- 必要とする知識など:
-
- 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() の呼び出しの直前までの処理で変更した変数を青、変更した値は赤で示して います。