- 必要とする知識など:
- UNIX (含む Linux) 上での C 言語と SOCKET によるプログラミング経験および TCP/IP に関する知識。
bind() システムコール、一回目です。
本連載の 三回目 に掲載した 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;
}
前回で socket() システムコールに該当する kernel の処理 sys_socket() の解析が終了しました。server.c を見ると、socket() システムコールの次は、bind() システムコールにより UDP のポート番号とソケットの対応付けを行ないます。
bind() システムコールを実行すると、kernel 内では sys_bind() が呼ばれます。sys_bind() 関数は、/usr/src/sys/kern/uipc_syscalls.c (http://svnweb.freebsd.org/base/release/9.1.0/sys/kern/uipc_syscalls.c?view=markup) で以下の様に定義されています。
sys_bind(td, uap)
struct thread *td;
struct bind_args /* {
int s;
caddr_t name;
int namelen;
} */ *uap;
{
struct sockaddr *sa;
int error;
if ((error = getsockaddr(&sa, uap->name, uap->namelen)) != 0)
return (error);
error = kern_bind(td, uap->s, sa);
free(sa, M_SONAME);
return (error);
}
getsockaddr() は同じ /usr/src/sys/kern/uipc_syscalls.c で以下のように定義されています。
int
getsockaddr(namp, uaddr, len)
struct sockaddr **namp;
caddr_t uaddr;
size_t len;
{
struct sockaddr *sa;
int error;
if (len > SOCK_MAXADDRLEN)
return (ENAMETOOLONG);
if (len < offsetof(struct sockaddr, sa_data[0]))
return (EINVAL);
sa = malloc(len, M_SONAME, M_WAITOK);
error = copyin(uaddr, sa, len);
if (error) {
free(sa, M_SONAME);
} else {
#if defined(COMPAT_OLDSOCK) && BYTE_ORDER != BIG_ENDIAN
if (sa->sa_family == 0 && sa->sa_len < AF_MAX)
sa->sa_family = sa->sa_len;
#endif
sa->sa_len = len;
*namp = sa;
}
return (error);
}
最初の二つの if (len... はエラーチェックです。詳細は割愛します。次の malloc(() も MALLOC(3) と大差ないメモリ領域の確保なので、詳細は割愛します。その次の copy() とその後の if (error) ... else ですが、GENERIC kernel カーネルのコンパイル時には COMAPT_OLDSOCK は指定されていないので、この #if defined(COMPAT_OLDSOCK)... の部分を削除すると、以下のようになります。
error = copyin(uaddr, sa, len);
if (error) {
free(sa, M_SONAME);
} else {
sa->sa_len = len;
*namp = sa;
}
copyin() は /usr/src/sys/アーキテクチャー/アーキテクチャー/support.[Ss] で定義されています。これは、各アーキテクチャー毎にアセンブリ言語で記述されています。i386 であれば /usr/src/sys/i386/i386/support.s (http://svnweb.freebsd.org/base/release/9.1.0/sys/i386/i386/support.s?view=markup) です。
この copyin() は、ユーザ空間上にあるデータをカーネル空間にコピーします。
getsockaddr() の第二引数 uaddr には、server.c の bind() の呼び出し (下記)
result = bind(sd, (struct sockaddr *)&sa, sizeof (sa));
の第二引数 &sa が渡されます。ユーザ空間とカーネル空間ではアドレスと実際のメモリの割当が (恐らく) 異なるので、それを copyin() でカーネル空間から見たユーザ空間のアドレスに変換した上で、ユーザ空間からカーネル空間にデータをコピーしているのだと推測します。
結局 getsockaddr() は、malloc() で struct sockaddr 用のメモリ領域を確保し、そこにユーザ空間の struct sockaddr (この場合は server.c の struct sockaddr_in as) をコピーし、そのポインタを第一引数の namp に代入しています。
getsockaddr() から sys_bind() に戻ると、次は kern_bind() の呼び出しです。
error = kern_bind(td, uap->s, sa);
kern_bind() は、sys_bind() の直後で以下のように定義されています。
int
kern_bind(td, fd, sa)
struct thread *td;
int fd;
struct sockaddr *sa;
{
struct socket *so;
struct file *fp;
int error;
AUDIT_ARG_FD(fd);
error = getsock_cap(td->td_proc->p_fd, fd, CAP_BIND, &fp, NULL);
if (error)
return (error);
so = fp->f_data;
#ifdef KTRACE
if (KTRPOINT(td, KTR_STRUCT))
ktrsockaddr(sa);
#endif
#ifdef MAC
error = mac_socket_check_bind(td->td_ucred, so, sa);
if (error == 0)
#endif
error = sobind(so, sa, td);
fdrop(fp, td);
return (error);
}
最初の AUDIT_ARG_FD() は、/usr/src/sys/security/audit/audit.h (http://svnweb.freebsd.org/base/release/9.1.0/sys/security/audit/audit.h?view=markup) で以下のように定義されています。
#ifdef AUDIT
...
#define AUDIT_ARG_FD(fd) do { \
if (AUDITING_TD(curthread)) \
audit_arg_fd((fd)); \
} while (0)
...
#else
...
#define AUDIT_ARG_FD(fd)
...
#endif
GENERIC kernel のコンパイル時に AUDIT は定義されていないようなので、uipc_bind() の最初の AUDIT_ARG_FD() は無処理になります。
また、#ifdef KTRACE, #ifdef MAC も無効と考えると、kern_bind() は以下のようになります。
int
kern_bind(td, fd, sa)
struct thread *td;
int fd;
struct sockaddr *sa;
{
struct socket *so;
struct file *fp;
int error;
error = getsock_cap(td->td_proc->p_fd, fd, CAP_BIND, &fp, NULL);
if (error)
return (error);
so = fp->f_data;
error = sobind(so, sa, td);
fdrop(fp, td);
return (error);
}
kern_bind() の詳細を追い掛けるところですが、今回はここで終了です。