linux + x86 の環境では、socket, accept, listen などは glibc の関数で、実体は socketcall
というシステムコールの場合もある
ともあれ実体は、sys_accept, sys_socket
- setsockopt もシステムコール。
ユーザ空間の関数 recv() の実体は、glibc のsocket/bits/socket2.h で定義されている
recv 関数です。
__extern_always_inline ssize_t
recv (int __fd, void *__buf, size_t __n, int __flags)
{
if (__bos0 (__buf) != (size_t) -1)
{
if (!__builtin_constant_p (__n))
return __recv_chk (__fd, __buf, __n, __bos0 (__buf), __flags);
if (__n > __bos0 (__buf))
return __recv_chk_warn (__fd, __buf, __n, __bos0 (__buf), __flags);
}
return __recv_alias (__fd, __buf, __n, __flags);
}
socket を recv, __socket を __libc_recv と定義して、
socket.S を読み込んでいます。
これにより、socket.S 内の socket および __socket シンボルが置換され、
__libc_recv という関数が定義される。
この関数の動作は、引数に SOCKOP_recv をセットして、socketcall
システムコールを呼び出すことである。
最後に、この__libc_recv の別名として __recv を与えている。
#define socket recv
#define __socket __libc_recv
#define NARGS 4
#define NEED_CANCELLATION
#include
weak_alias (__libc_recv, __recv)
__socket() in sysdeps/unix/sysv/linux/socketcall.h
関数 (実際には recv() や send() に展開される)
#define SOCKOP_recv 10
関数の定義になる、socket.S は以下の通りである。
glibc :: sysdeps/unix/sysv/linux/i386/socket.S
/* C からは __socket() という関数に見える。 */
/* 引数は
.globl __socket
ENTRY (__socket)
#if defined NEED_CANCELLATION && defined CENABLE
SINGLE_THREAD_P
jne 1f
#endif
/* Save registers. */
movl %ebx, %edx
cfi_register (3, 2)
/* ここで、%eax に socketcall のシステムコール番号 (102) がセットされる */
movl $SYS_ify(socketcall), %eax /* System call number in %eax. */
/* Use ## so `socket' is a separate token that might be #define'd. */
/* ここに、socketcall 内で分岐するための番号が入る */
/* (socket, send, recv, accept, listen など) */
/* SOCKOP_ と socket が結合されて、 SOCKOP_recv みたいになる。*/
/* この値は sysdeps/sysv/linux/socketcall.h で定義されている。 */
movl $P(SOCKOP_,socket), %ebx /* Subcode is first arg to syscall. */
lea 4(%esp), %ecx /* Address of args is 2nd arg. */
/* Do the system call trap. */
/* ここでトラップ命令 (sysenter か 0x80) が呼ばれて、*/
/* 特権モードに入る。*/
ENTER_KERNEL
/* 特権モードから戻ってきた後の処理。*/
/* Restore registers. */
movl %edx, %ebx
cfi_restore (3)
/* %eax is < 0 if there was an error. */
cmpl $-125, %eax
jae SYSCALL_ERROR_LABEL
/* Successful; return the syscall's value. */
L(pseudo_end):
ret
システムコール変換テーブル (in arch/x86/kernel/syscall_table_32.S)
ようやくカーネル空間にたどりつきました。
トラップ命令後です。
ENTRY(sys_call_table)
.long sys_restart_syscall /* 0 - old "setup()" system call, used for restarting */
...
...
.long sys_fstatfs /* 100 */
.long sys_ioperm
.long sys_socketcall
%eax に 102 をセットしてトラップ命令を呼ぶと、sys_socketcall に飛びます。
sys_socketcall in net/socket.c
asmlinkage long sys_socketcall(int call, unsigned long __user *args)
{
unsigned long a[6];
unsigned long a0, a1;
int err;
if (call < 1 || call > SYS_PACCEPT)
return -EINVAL;
/* copy_from_user should be SMP safe. */
if (copy_from_user(a, args, nargs[call]))
return -EFAULT;
err = audit_socketcall(nargs[call] / sizeof(unsigned long), a);
if (err)
return err;
a0 = a[0];
a1 = a[1];
switch (call) {
case SYS_SOCKET:
err = sys_socket(a0, a1, a[2]);
break;
case SYS_BIND:
err = sys_bind(a0, (struct sockaddr __user *)a1, a[2]);
break;
...
case SYS_RECV:
err = sys_recv(a0, (void __user *)a1, a[2], a[3]);
break;
sys_recv in net/socket.c
/*
* Receive a datagram from a socket.
*/
asmlinkage long sys_recv(int fd, void __user *ubuf, size_t size,
unsigned flags)
{
return sys_recvfrom(fd, ubuf, size, flags, NULL, NULL);
}
sys_recvfrom() in net/socket.c
/*
* Receive a frame from the socket and optionally record the address of the
* sender. We verify the buffers are writable and if needed move the
* sender address from kernel to user space.
*/
asmlinkage long sys_recvfrom(int fd, void __user *ubuf, size_t size,
unsigned flags, struct sockaddr __user *addr,
int __user *addr_len)
{
struct socket *sock;
struct iovec iov;
struct msghdr msg;
struct sockaddr_storage address;
int err, err2;
int fput_needed;
sock = sockfd_lookup_light(fd, &err, &fput_needed);
if (!sock)
goto out;
msg.msg_control = NULL;
msg.msg_controllen = 0;
msg.msg_iovlen = 1;
msg.msg_iov = &iov;
iov.iov_len = size;
iov.iov_base = ubuf;
msg.msg_name = (struct sockaddr *)&address;
msg.msg_namelen = sizeof(address);
if (sock->file->f_flags & O_NONBLOCK)
flags |= MSG_DONTWAIT;
=> err = sock_recvmsg(sock, &msg, size, flags);
if (err >= 0 && addr != NULL) {
err2 = move_addr_to_user((struct sockaddr *)&address,
msg.msg_namelen, addr, addr_len);
if (err2 < 0)
err = err2;
}
fput_light(sock->file, fput_needed);
out:
return err;
}
net/socket.c
sock_recvmsg()
---
int sock_recvmsg(struct socket *sock, struct msghdr *msg,
size_t size, int flags)
{
struct kiocb iocb;
struct sock_iocb siocb;
int ret;
init_sync_kiocb(&iocb, NULL);
iocb.private = &siocb;
=> ret = __sock_recvmsg(&iocb, sock, msg, size, flags);
if (-EIOCBQUEUED == ret)
ret = wait_on_sync_kiocb(&iocb);
return ret;
}
---
net/socket.c
---
static inline int __sock_recvmsg(struct kiocb *iocb, struct socket *sock,
struct msghdr *msg, size_t size, int flags)
{
int err;
struct sock_iocb *si = kiocb_to_siocb(iocb);
si->sock = sock;
si->scm = NULL;
si->msg = msg;
si->size = size;
si->flags = flags;
err = security_socket_recvmsg(sock, msg, size, flags);
if (err)
return err;
=> return sock->ops->recvmsg(iocb, sock, msg, size, flags);
}
---
さまざまな recv 関数が呼べるようになっているが、
今回見ているのは ipv4 。
*** net/ipv4/af_inet.c:
recvmsg[857] .recvmsg = sock_common_recvmsg,
---
net/core/sock.c
---
int sock_common_recvmsg(struct kiocb *iocb, struct socket *sock,
struct msghdr *msg, size_t size, int flags)
{
struct sock *sk = sock->sk;
int addr_len = 0;
int err;
=> err = sk->sk_prot->recvmsg(iocb, sk, msg, size, flags & MSG_DONTWAIT,
flags & ~MSG_DONTWAIT, &addr_len);
if (err >= 0)
msg->msg_namelen = addr_len;
return err;
}
---
raw.c
---
static int raw_recvmsg(struct kiocb *iocb, struct socket *sock,
struct msghdr *msg, size_t size, int flags)
{
struct sock *sk = sock->sk;
struct sk_buff *skb;
int err = 0;
int noblock;
noblock = flags & MSG_DONTWAIT;
flags &= ~MSG_DONTWAIT;
skb = skb_recv_datagram(sk, flags, noblock, &err);
if (!skb)
return err;
if (size < skb->len)
msg->msg_flags |= MSG_TRUNC;
else
size = skb->len;
err = memcpy_toiovec(msg->msg_iov, skb->data, size);
if (err < 0) {
skb_free_datagram(sk, skb);
return err;
}
sock_recv_timestamp(msg, sk, skb);
if (msg->msg_name) {
msg->msg_namelen = sizeof(struct sockaddr_can);
memcpy(msg->msg_name, skb->cb, msg->msg_namelen);
}
skb_free_datagram(sk, skb);
return size;
}
---