Socket与系统调用深度分析

1 Linux上的系统调用

系统调用是操作系统提供给用户程序访问内核的桥梁,通过系统调用,运行于用户态的用户程序能够调用到运行于内核态的系统内核提供的功能。系统调用一般是由软中断实现的,在Linux上该功能是由中断号为0x80的系统调用处理程序system_call提供。下面以Linux socket API为例,探究Linux中系统调用是如何进行的。

2 环境准备

本次实验的环境在构建调试Linux内核网络代码的环境MenuOS系统已经搭建好了,使用内核版本为Linux-5.0.1,在MenuOS下运行。

3 Replyhi & hello

Replyhi和hello命令的功能是建立一个TCP连接,在本地进行一次hi/hello通信。从相关源码中找到该程序使用的Socket API。这两个函数中调用的关于TCP连接的函数都是用宏定义的,限于篇幅,在注释中直接给出这些函数所调用的Socket API,以便了解其进行TCP通信的工作过程。

int Replyhi()
{
	char szBuf[MAX_BUF_LEN] = "\0";
	char szReplyMsg[MAX_BUF_LEN] = "hi\0";
	InitializeService();//socket bind listen
	while (1)
	{
		ServiceStart();//accept
		RecvMsg(szBuf);//recv
		SendMsg(szReplyMsg);//send
		ServiceStop();
	}
	ShutdownService();
	return 0;
}
int Hello(int argc, char *argv[])
{
	char szBuf[MAX_BUF_LEN] = "\0";
	char szMsg[MAX_BUF_LEN] = "hello\0";
	OpenRemoteService();//socket connect
	SendMsg(szMsg);//send
	RecvMsg(szBuf);//recv
	CloseRemoteService();
	return 0;
}

4 Socket的系统调用

4.1 Replyhi & hello中Socket系统调用追踪

从上面给出的源码注释中可以看到,replyhi/hello程序调用的Socket API包括socket、bind、listen、accept、recv、send、connect,依次给对应的系统调用加上断点:

(gdb) b __sys_socket
(gdb) b __sys_bind
(gdb) b __sys_listen
(gdb) b __sys_accept4
(gdb) b __sys_recvfrom
(gdb) b __sys_recvmsg
(gdb) b __sys_sendto
(gdb) b __sys_sendmsg
(gdb) b __sys_connect

断点信息如下图:

接下来,运行一次replyhi/hello程序,查看其调用情况:

运行replyhi:

从图中可以看到,replyhi分别调用了__sys_socket、__sys_bind函数。

运行hello:

从图中可以看到,hello分别调用了__sys_socket、__sys_connect、__sys_recvfrom、__sys_sendto、__sys_recvfrom、__sys_socket、__sys_socket函数。

4.2 Linux Socket系统调用的函数栈

下面以__sys_socket、__sys_bind和为例继续探究系统调用是如何进行的。

查看调用这两个函数时的函数堆栈:

Breakpoint 12, __sys_socket (family=2, type=1, protocol=0) at net/socket.c:1327
1327	{
(gdb) bt
#0  __sys_socket (family=2, type=1, protocol=0) at net/socket.c:1327
#1  0xffffffff8180bd52 in __do_compat_sys_socketcall (args=<optimized out>, call=<optimized out>) at net/compat.c:863
#2  __se_compat_sys_socketcall (args=<optimized out>, call=<optimized out>) at net/compat.c:838
#3  __ia32_compat_sys_socketcall (regs=<optimized out>) at net/compat.c:838
#4  0xffffffff8100261d in do_syscall_32_irqs_on (regs=<optimized out>) at arch/x86/entry/common.c:326
#5  do_fast_syscall_32 (regs=0x2 <irq_stack_union+2>) at arch/x86/entry/common.c:397
#6  0xffffffff81c013d1 in entry_SYSCALL_compat () at arch/x86/entry/entry_64_compat.S:257

Breakpoint 3, __sys_bind (fd=4, umyaddr=0xffed075c, addrlen=16) at net/socket.c:1469
1469	{
(gdb) bt
#0  __sys_bind (fd=4, umyaddr=0xffed075c, addrlen=16) at net/socket.c:1469
#1  0xffffffff8180bd60 in __do_compat_sys_socketcall (args=<optimized out>, call=<optimized out>) at net/compat.c:866
#2  __se_compat_sys_socketcall (args=<optimized out>, call=<optimized out>) at net/compat.c:838
#3  __ia32_compat_sys_socketcall (regs=<optimized out>) at net/compat.c:838
#4  0xffffffff8100261d in do_syscall_32_irqs_on (regs=<optimized out>) at arch/x86/entry/common.c:326
#5  do_fast_syscall_32 (regs=0x4 <irq_stack_union+4>) at arch/x86/entry/common.c:397
#6  0xffffffff81c013d1 in entry_SYSCALL_compat () at arch/x86/entry/entry_64_compat.S:257
#7  0x0000000000000000 in ?? ()

可以看到__sys_socket、__sys_bind都是被ia32_compat_sys_socketcall、do_syscall_32_irqs_on、do_fast_syscall_32 、entry_SYSCALL_compat 所调用,而再之上的函数gdb就无法读取到了。于是可以知道,Linux的Socket系统调用的函数或函数指针栈即为ia32_compat_sys_socketcall、do_syscall_32_irqs_on、do_fast_syscall_32 、entry_SYSCALL_compat。用户程序从函数指针entry_SYSCALL_compat找到系统调用函数入口后遍依次进入后面的函数直到调用到所期望的系统调用。

4.3 Linux Socket系统调用函数源码

继续以__sys_socket、__sys_bind为例,查看其源码。

int __sys_socket(int family, int type, int protocol)
{
	int retval;
	struct socket *sock;
	int flags;

	/* Check the SOCK_* constants for consistency.  */
	BUILD_BUG_ON(SOCK_CLOEXEC != O_CLOEXEC);
	BUILD_BUG_ON((SOCK_MAX | SOCK_TYPE_MASK) != SOCK_TYPE_MASK);
	BUILD_BUG_ON(SOCK_CLOEXEC & SOCK_TYPE_MASK);
	BUILD_BUG_ON(SOCK_NONBLOCK & SOCK_TYPE_MASK);

	flags = type & ~SOCK_TYPE_MASK;
	if (flags & ~(SOCK_CLOEXEC | SOCK_NONBLOCK))
		return -EINVAL;
	type &= SOCK_TYPE_MASK;

	if (SOCK_NONBLOCK != O_NONBLOCK && (flags & SOCK_NONBLOCK))
		flags = (flags & ~SOCK_NONBLOCK) | O_NONBLOCK;

	retval = sock_create(family, type, protocol, &sock);
	if (retval < 0)
		return retval;

	return sock_map_fd(sock, flags & (O_CLOEXEC | O_NONBLOCK));
}

int __sys_bind(int fd, struct sockaddr __user *umyaddr, int addrlen)
{
	struct socket *sock;
	struct sockaddr_storage address;
	int err, fput_needed;

	sock = sockfd_lookup_light(fd, &err, &fput_needed);
	if (sock) {
		err = move_addr_to_kernel(umyaddr, addrlen, &address);
		if (!err) {
			err = security_socket_bind(sock,
						   (struct sockaddr *)&address,
						   addrlen);
			if (!err)
				err = sock->ops->bind(sock,
						      (struct sockaddr *)
						      &address, addrlen);
		}
		fput_light(sock->file, fput_needed);
	}
	return err;
}

从源码中可以看出,这两个函数调用的核心函数是sock_create、sock_map_fd、security_socket_bind。

参考资料

https://www.cnblogs.com/wang_yb/archive/2012/09/17/2688263.html
https://github.com/mengning/net

posted @ 2019-12-18 21:55  Litosty  阅读(321)  评论(0编辑  收藏  举报