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内核执行系统调用过程简介

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)就是所有系统调用的总入口。

二、网络程序系统调用
1、查看系统调用与中断的关系:
在qemu启动过程中分析system_call中系统调用和中断处理过程。
start_kernel --> trap_init --> idt_setup_traps --> entry_INT80_32
设置断点1:start_kernel是所有程序初始化的入口点,因此在init中的main进行设置start_kernel断点1。

设置断点2:系统调用实质是指令发起的软中断,trap_init中断初始化。
该程序主要定义和实现了asm.s中所引用的各个硬件异常中断处理程序。

设置断点3:系统调用中断点。
在5.0内核int0x80对应的中断服务例程是entry_INT80_32,而不是原来的名称system_call了


其对应的ENTRY(entry_INT80_32),即为使用0x80引入中断。
由此可以观察到,内核处理系统调用是将其视为一个特殊的中断进行处理的。

Qemu程序如图所示:

转到:Do_entry_INT80_32

程序继续运行

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

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

部分代码:
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函数。

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

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中系统调用完成。

浙公网安备 33010602011771号