Socket与系统调用深度分析

Socket与系统调用深度分析

  • Socket API编程接口之上可以编写基于不同网络协议的应用程序;
  • Socket接口在用户态通过系统调用机制进入内核;
  • 内核中将系统调用作为一个特殊的中断来处理,以socket相关系统调用为例进行分析。
  • socket相关系统调用的内核处理函数内部通过“多态机制”对不同的网络协议进行的封装方法;

请将Socket API编程接口、系统调用机制及内核中系统调用相关源代码、 socket相关系统调用的内核处理函数结合起来分析,并在X86 32环境下Linux5.0以上的内核中进一步跟踪验证。

为了与上次实验保持一致,本次实验使用的是上次实验的linux-5.0.1下的32位X86系统。搭建过程见上文。

一、原理简介

1、系统调用介绍

为了保证系统的安全性,linux系统设置了两种控制权限。第一种是用户权限,其只能执行不涉及系统的指令;第二种是内核权限,它可以执行系统中的所有指令,于此同时,通常32位Linux内核地址空间划分0~3G为用户空间,3~4G为内核空间。

而当普通用户需要执行系统相关的指令时,就无法直接访问系统的指令,只能通过系统调用的方式,向内核发送指令产生软中断,进入内核状态,执行系统相关的函数和指令。

2、linux内核执行系统调用过程简介

 Socket与系统调用深度分析_第1张图片

 

 

Linux系统调用示意图

(1)用户态

应用程序:是一个普通程序,例如用户编写的应用。当其涉及到内核操作时,执行系统调用程序:执行软中断,Intel架构中使用的是0X80。

系统调用程序:当进程需要进行系统调用时,必须以C语言函数的形式写一句系统调用命令。当进程执行到用户程序的系统调用命令时,实际上执行了由宏命令_syscallN()展开的函数。系统调用的参数由各通用寄存器传递。然后执行INT 0X80,以核心态进入入口地址system_call。

(2)内核态

系统调用处理程序:有两个关键的文件,Entry.s和unistd.h文件。

在unistd中为每个唯一的系统调用规定了唯一的系统调用号,当得到相应的系统调用号后,转到entry.s找到相应的内容。

在entry.s中规定的system_call就是所有系统调用的总入口,entry.s中规定了系统调用表。
    sys_call_table记录了各sys_name函数在表中的位子。有了这张表,很容易根据特定系统调用在表中的偏移量,找到对应的系统调用响应函数的入口地址。

3、系统调用初始化过程

对系统调用的初始化也即对INT 0X80的初始化。系统启动时,汇编子程序setup_idt(head.S)准备了张256项的idt 表start_kernel()(init/main.c)、trap_init()(traps.c)调用的C语言宏定义set_system_gate(0x80, &system_call)( system.h)设置0X80号软中断的服务程序为system_call。system_call(entry.S)就是所有系统调用的总入口。

 Socket与系统调用深度分析_第2张图片

 

 

 

二、网络程序系统调用

1、查看系统调用与中断的关系:

在qemu启动过程中分析system_call中系统调用和中断处理过程。

start_kernel --> trap_init --> idt_setup_traps --> entry_INT80_32

设置断点1:start_kernel是所有程序初始化的入口点,因此在init中的main进行设置start_kernel断点1。

 Socket与系统调用深度分析_第3张图片

 

 

 

 

 

 

 

 

 

 

 

 

 

 

设置断点2:系统调用实质是指令发起的软中断,trap_init中断初始化。

该程序主要定义和实现了asm.s中所引用的各个硬件异常中断处理程序。

 Socket与系统调用深度分析_第4张图片

 

设置断点3:系统调用中断点。

在5.0内核int0x80对应的中断服务例程是entry_INT80_32,而不是原来的名称system_call了

 

 

其对应的ENTRY(entry_INT80_32),即为使用0x80引入中断。

由此可以观察到,内核处理系统调用是将其视为一个特殊的中断进行处理的。

Socket与系统调用深度分析_第5张图片

Qemu程序如图所示:

Socket与系统调用深度分析_第6张图片

 

 

转到:Do_entry_INT80_32

Socket与系统调用深度分析_第7张图片

 

 

程序继续运行

Socket与系统调用深度分析_第8张图片

 

 

2、查看系统调用表

文件在arch/x86/entry/syscall_32目录下,系统调用号是102。

Socket与系统调用深度分析_第9张图片

 

 

在sys_socketcall处添加断点,运行replyhi程序,找到其socket源码位置是位于net/socket位置。

Socket与系统调用深度分析_第10张图片

 

 部分代码:

SYSCALL_DEFINE2(socketcall, int, call, unsigned long __user *, args)
{
	unsigned long a[AUDITSC_ARGS];
	unsigned long a0, a1;
	int err;
	unsigned int len;

	if (call < 1 || call > SYS_SENDMMSG)
		return -EINVAL;
	call = array_index_nospec(call, SYS_SENDMMSG + 1);

	len = nargs[call];
	if (len > sizeof(a))
		return -EINVAL;

	/* copy_from_user should be SMP safe. */
	if (copy_from_user(a, args, len))
		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_CONNECT:
		err = __sys_connect(a0, (struct sockaddr __user *)a1, a[2]);
		break;
	case SYS_LISTEN:
		err = __sys_listen(a0, a1);
		break;
	case SYS_ACCEPT:
		err = __sys_accept4(a0, (struct sockaddr __user *)a1,
				    (int __user *)a[2], 0);
		break;
	case SYS_GETSOCKNAME:
		err =
		    __sys_getsockname(a0, (struct sockaddr __user *)a1,
				      (int __user *)a[2]);
…
}

 

研究其代码,发现了其中有很多常用的socket相关的代码,比如最常见的bind、accept、listen等,客户端采用TCP协议时的监听函数LISTEN,绑定函数BIND,接收客户端发来的消息ACCEPT等。

 

追踪replyhi程序调用过程,输入s单步执行查看程序调用函数:

发现继续调用了create函数。

Socket与系统调用深度分析_第11张图片

 

 

3、重载功能的实现

以create_socket为例,socket是通过参数的不同值跳到不同的方法,实现重载功能的。

Socket与系统调用深度分析_第12张图片

 

 

int __sock_create(struct net *net, int family, int type, int protocol,
			 struct socket **res, int kern)
{
	int err;
	struct socket *sock;
	const struct net_proto_family *pf;

	/*
	 *      Check protocol is in range
	 */
	if (family < 0 || family >= NPROTO)
		return -EAFNOSUPPORT;
	if (type < 0 || type >= SOCK_MAX)
		return -EINVAL;

	/* Compatibility.

	   This uglymoron is moved from INET layer to here to avoid
	   deadlock in module load.
	 */
	if (family == PF_INET && type == SOCK_PACKET) {
		pr_info_once("%s uses obsolete (PF_INET,SOCK_PACKET)\n",
			     current->comm);
		family = PF_PACKET;
	}

	err = security_socket_create(family, type, protocol, kern);
	if (err)
		return err;

………

out_release:
	rcu_read_unlock();
	goto out_sock_release;
}
EXPORT_SYMBOL(__sock_create);

int sock_create(int family, int type, int protocol, struct socket **res)
{
	return __sock_create(current->nsproxy->net_ns, family, type, protocol, res, 0);
}
EXPORT_SYMBOL(sock_create);

int sock_create_kern(struct net *net, int family, int type, int protocol, struct socket **res)
{
	return __sock_create(net, family, type, protocol, res, 1);
}
EXPORT_SYMBOL(sock_create_kern);

 调用具体的处理函数

int __sock_create(struct net *net, int family, int type, int protocol,
			 struct socket **res, int kern)
{
	int err;
	struct socket *sock;
	const struct net_proto_family *pf;

	/*
	 *      Check protocol is in range
	 */
	if (family < 0 || family >= NPROTO)
		return -EAFNOSUPPORT;
	if (type < 0 || type >= SOCK_MAX)
		return -EINVAL;

	/* Compatibility.

	   This uglymoron is moved from INET layer to here to avoid
	   deadlock in module load.
	 */
	if (family == PF_INET && type == SOCK_PACKET) {
		pr_info_once("%s uses obsolete (PF_INET,SOCK_PACKET)\n",
			     current->comm);
		family = PF_PACKET;
	}

	err = security_socket_create(family, type, protocol, kern);
	if (err)
		return err;

	/*

 

当退出断点时,qemu程序继续执行。

 

 

 至此,socket中系统调用完成。

 

你可能感兴趣的:(Socket与系统调用深度分析)