5.3. システムコールの追加
ソケットAPIをユーザ空間から使えるように、システムコールを追加します。UDP通信に必要なsocket()・bind()・recvfrom()・sendto()の4つを実装します。
システムコール番号の追加
既存の最大番号(SYS_sync = 22)に続けて、ソケット関連のシステムコール番号を追加します。
📝 kernel/syscall.h
...
#define SYS_mkdir 20
#define SYS_close 21
#define SYS_sync 22
+#define SYS_socket 23
+#define SYS_bind 24
+#define SYS_recvfrom 25
+#define SYS_sendto 26
システムコール番号とカーネル関数の対応付け
kernel/syscall.cに、システムコールを処理するカーネル関数の宣言と、番号から関数への対応表(syscalls[])への登録を追加します。
📝 kernel/syscall.c
...
extern uint64 sys_close(void);
extern uint64 sys_sync(void);
+extern uint64 sys_socket(void);
+extern uint64 sys_bind(void);
+extern uint64 sys_recvfrom(void);
+extern uint64 sys_sendto(void);
// An array mapping syscall numbers from syscall.h
// to the function that handles the system call.
static uint64 (*syscalls[])(void) = {
...
[SYS_close] sys_close,
[SYS_sync] sys_sync,
+ [SYS_socket] sys_socket,
+ [SYS_bind] sys_bind,
+ [SYS_recvfrom] sys_recvfrom,
+ [SYS_sendto] sys_sendto,
// clang-format on
};
...
カーネル関数の実装
システムコールの実体をkernel/syssocket.cとして新規に作成します。既存のkernel/sysfile.cと同じく、ユーザ空間から渡された引数を検証してから、5.2で移植したソケット層(sock_*())を呼び出します。
ここで、2種類のディスクリプタが登場することに注意してください。sock_open()が返すのはソケット層の内部テーブル(socks[])の添字(ソケットディスクリプタ)で、これはstruct fileのsockメンバに格納します。一方、socket()がユーザ空間に返すのは、そのstruct fileに割り当てたxv6の**ファイルディスクリプタ(FD)**です。以降のbind()/recvfrom()/sendto()は、ユーザから渡されたFDをargsock()でソケットディスクリプタに解決してからソケット層を呼び出します。
ファイル冒頭では、この橋渡しを担うヘルパを用意しています。filealloc()でファイルを確保してFDを割り当てるsockfdalloc()、FDをソケットディスクリプタに変換するargsock()、struct sockaddr_inをユーザ空間とやり取りするfetchsockaddr()/storesockaddr()です。
📝 kernel/syssocket.c
//
// Socket system calls.
// Mostly argument checking, since we don't trust
// user code, and calls into net/sock.c.
//
#include "types.h"
#include "riscv.h"
#include "defs.h"
#include "param.h"
#include "spinlock.h"
#include "proc.h"
#include "fs.h"
#include "sleeplock.h"
#include "file.h"
#include "net/sock.h"
#define KBUFSIZ 2048
// Allocate a struct file and a file descriptor for the given socket
// descriptor. Closes the socket and returns -1 on failure.
static int
sockfdalloc(int sock)
{
struct file *f;
int fd;
if ((f = filealloc()) == 0) {
sock_close(sock);
return -1;
}
f->type = FD_SOCKET;
f->readable = 1;
f->writable = 1;
f->sock = sock;
if ((fd = fdalloc(f)) < 0) {
fileclose(f); // also closes the socket
return -1;
}
return fd;
}
// Fetch the nth word-sized system call argument as a socket descriptor.
static int
argsock(int n, int *psock)
{
struct file *f;
if (argfd(n, 0, &f) < 0)
return -1;
if (f->type != FD_SOCKET)
return -1;
*psock = f->sock;
return 0;
}
// Fetch a struct sockaddr_in from user space.
static int
fetchsockaddr(uint64 addr, int addrlen, struct sockaddr_in *sin)
{
struct proc *p = myproc();
if (addrlen != sizeof(*sin))
return -1;
if (copyin(p->pagetable, (char *)sin, addr, sizeof(*sin)) < 0)
return -1;
return 0;
}
// Copy out a struct sockaddr_in and its length to user space.
// Does nothing if the user passed a null pointer for either of them.
static int
storesockaddr(uint64 addr, uint64 addrlen, struct sockaddr_in *sin, int len)
{
struct proc *p = myproc();
if (addr == 0 || addrlen == 0)
return 0;
if (copyout(p->pagetable, addr, (char *)sin, len) < 0)
return -1;
if (copyout(p->pagetable, addrlen, (char *)&len, sizeof(len)) < 0)
return -1;
return 0;
}
uint64
sys_socket(void)
{
int domain, type, protocol;
int sock;
argint(0, &domain);
argint(1, &type);
argint(2, &protocol);
if ((sock = sock_open(domain, type, protocol)) < 0)
return -1;
return sockfdalloc(sock);
}
uint64
sys_bind(void)
{
int sock, addrlen;
uint64 addr;
struct sockaddr_in sin;
argaddr(1, &addr);
argint(2, &addrlen);
if (argsock(0, &sock) < 0)
return -1;
if (fetchsockaddr(addr, addrlen, &sin) < 0)
return -1;
return sock_bind(sock, (struct sockaddr *)&sin, sizeof(sin));
}
uint64
sys_recvfrom(void)
{
int sock, n, len;
uint64 buf, addr, addrlen;
char kbuf[KBUFSIZ];
struct sockaddr_in sin;
struct proc *p = myproc();
ssize_t ret;
argaddr(1, &buf);
argint(2, &n);
argaddr(3, &addr);
argaddr(4, &addrlen);
if (argsock(0, &sock) < 0)
return -1;
if (n < 0)
return -1;
if (n > KBUFSIZ)
n = KBUFSIZ;
len = sizeof(sin);
if ((ret = sock_recvfrom(sock, kbuf, n, (struct sockaddr *)&sin, &len)) < 0)
return -1;
// copy out only the bytes actually received, not the whole buffer
if (ret > 0 && copyout(p->pagetable, buf, kbuf, ret) < 0)
return -1;
if (storesockaddr(addr, addrlen, &sin, len) < 0)
return -1;
return ret;
}
uint64
sys_sendto(void)
{
int sock, n, addrlen;
uint64 buf, addr;
char kbuf[KBUFSIZ];
struct sockaddr_in sin;
struct proc *p = myproc();
argaddr(1, &buf);
argint(2, &n);
argaddr(3, &addr);
argint(4, &addrlen);
if (argsock(0, &sock) < 0)
return -1;
if (n < 0 || n > KBUFSIZ)
return -1;
if (fetchsockaddr(addr, addrlen, &sin) < 0)
return -1;
if (copyin(p->pagetable, kbuf, buf, n) < 0)
return -1;
return sock_sendto(sock, kbuf, n, (struct sockaddr *)&sin, sizeof(sin));
}
Important
sys_recvfrom()とsys_sendto()は、受信・送信データを一旦カーネル側のバッファkbuf(char kbuf[KBUFSIZ])にコピーしてからソケット層に渡します。
Note
sys_recvfrom()では「実際に受信したバイト数」だけをcopyout()します。バッファ全体をコピーすると、初期化されていないカーネルメモリがユーザ空間へ漏れてしまうためです。
Makefileの修正
syssocket.oをビルド対象に追加します。
📝 Makefile
...
$K/exec.o \
$K/sysfile.o \
+ $K/syssocket.o \
$K/kernelvec.o \
...
ユーザ空間へのシステムコールの提供
システムコールの入り口(トランポリン)を生成するuser/usys.plと、プロトタイプ宣言を持つuser/user.hにソケット関連のエントリを追加します。
📝 user/usys.pl
...
entry("uptime");
entry("sync");
+entry("socket");
+entry("bind");
+entry("recvfrom");
+entry("sendto");
📝 user/user.h
...
#define SBRK_ERROR ((char *)-1)
struct stat;
+struct sockaddr;
// system calls
int fork(void);
...
int pause(int);
int uptime(void);
int sync(void);
+int socket(int, int, int);
+int bind(int, const struct sockaddr *, int);
+int recvfrom(int, void *, int, struct sockaddr *, int *);
+int sendto(int, const void *, int, const struct sockaddr *, int);
// ulib.c
...