Socket与系统调用深度分析

Posted wjt1996

tags:

篇首语:本文由小常识网(cha138.com)小编为大家整理,主要介绍了Socket与系统调用深度分析相关的知识,希望对你有一定的参考价值。

1.socket原理

1.1 计算机网络通信原理

在现行的tcp/ip协议中,总共分为四个层次,分别是网络接入层(对应iso标准的物理层和数据链路层),互联网络层(对应iso网络层),传输层(对应iso传输层)和应用层(对应iso应用层,表示层,会话层)。

 技术图片

 

网络接入层提供物理层面的链接,互联网络层实现数据报的转发与传递,传输层实现点到点的通信,应用层提供应用数据。在上述层次中,每一层都在下一层提供服务的基础上进行了封装,这样上层在调用其功能时,只需要知道该层次所提供的接口即可。

1.2 Socket简介

传输层实现端到端的通信,因此,每一个传输层连接有两个端点。那么,传输层连接的端点不单是主机,也不单是传输层的协议端口。传输层连接的端点叫做套接字(socket)。根据RFC793的定义:端口号拼接到IP地址就构成了套接字。所谓套接字,实际上是一个通信端点,每个套接字都有一个套接字序号,包括主机的IP地址与一个16位的主机端口号,即形如(主机IP地址:端口号)。例如,如果IP地址是210.37.145.1,而端口号是23,那么得到套接字就是(210.37.145.123)。

套接字Socket=IP地址:端口号),套接字的表示方法是点分十进制的IP地址后面写上端口号,中间用冒号或逗号隔开。每一个传输层连接唯一地被通信两端的两个端点(即两个套接字)所确定。

套接字可以看成是两个网络应用程序进行通信时,各自通信连接中的一个端点。通信时,其中的一个网络应用程序将要传输的一段信息写入它所在主机的Socket中,该Socket通过网络接口卡的传输介质将这段信息发送给另一台主机的Socket中,使这段信息能传送到其他程序中。因此,两个应用程序之间的数据传输要通过套接字来完成。

 技术图片

如上图所示,一般调用socket函数的通信流程为,服务端通过socket结构,用bind函数绑定ip地址和端口号,再通过listen监听通信,等待用户端连接请求,而用户端使用connect建立连接。连接建立后,用户端和服务端都可以使用write()和read()来进行信息的发送与接收,在传输完毕时,用户端先断开连接,服务器端接收到断开连接信号后,也断开连接。

2.内核与系统调用原理

2.1内核

内核,是一个操作系统的核心。是基于硬件的第一层软件扩充,提供操作系统的最基本的功能,是操作系统工作的基础,它负责管理系统的进程、内存、内核体系结构、设备驱动程序、文件和网络系统,决定着系统的性能和稳定性。内核将操作系统分为目态和管态,目态运行的程序受到权限限制,部分功能直接使用,在它们需要使用这些被限制的功能时,通过系统调用来实现。

2.2 系统调用

操作系统中的状态分为管态(核心态)和目态(用户态)。目态的进程运行受到权限限制,不能使用特权指令。(特权指令:一类只能在核心态下运行而不能在用户态下运行的特殊指令。不同的操作系统特权指令会有所差异,但是一般来说主要是和硬件相关的一些指令。)当目态进程需要使用特权指令时,他们通过访管指令将系统从目态转化为管态,从而获得权限执行特权指令。(访管指令:本身是一条特殊的指令,但不是特权指令,是在目态下使用的指令)访管指令能引起访管异常,通过中断机制实现功能。用户程序只在用户态下运行,有时需要访问系统核心功能,这时通过系统调用接口使用系统调用。

 技术图片

 

 

3.内核中断简介

中断是指由于接收到来自外围硬件(相对于中央处理器和内存)的异步信号或来自软件的同步信号,而进行相应的硬件/软件处理。发出这样的信号称为进行中断请求(interrupt requestIRQ)。硬件中断导致处理器通过一个上下文切换(context switch)来保存执行状态(以程序计数器和程序状态字等寄存器信息为主);软件中断则通常作为CPU指令集中的一个指令,以可编程的方式直接指示这种上下文切换,并将处理导向一段中断处理代码。

技术图片 系统调用本身就是一种中断,在Linux中,当进程使用系统调用时,执行int $0x80,产?向量为128的编程异常,内核保存现场,并从eax寄存器中读取系统调用号,传入system_call函数,执行对应系统调用。

4.Socket系统调用具体分析

lab3所提供的main.c文件中,我们可以发现和socket有关的如socket()listen()bind()以及close()等几个函数。根据查阅资料,我们得知Linux中所有和socket有关的系统调用统一使用了sys_socketcall()作为入口。于是我们在gdb中,分别对sys_socketcall,sys_bind,sys_listen设置断点跟踪,结果如图所示。(在等待配置环境的同时先用了实验楼环境来追踪)

技术图片技术图片

 技术图片

技术图片

技术图片

可以发现,sys_socketcall总是在调用一个名为syscall_define2的函数,在这里查阅资料可知,syscall_defineX()为Linux中系统调用的统一接口,X为参数数量,这里syscall_define2表示系统调用参数有两个,第一个为系统调用名,第二个为类型+参数名。syscall_define2socketcall...)为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]);

        break;

    case SYS_GETPEERNAME:

        err =

            __sys_getpeername(a0, (struct sockaddr __user *)a1,

                      (int __user *)a[2]);

        break;

    case SYS_SOCKETPAIR:

        err = __sys_socketpair(a0, a1, a[2], (int __user *)a[3]);

        break;

    case SYS_SEND:

        err = __sys_sendto(a0, (void __user *)a1, a[2], a[3],

                   NULL, 0);

        break;

    case SYS_SENDTO:

        err = __sys_sendto(a0, (void __user *)a1, a[2], a[3],

                   (struct sockaddr __user *)a[4], a[5]);

        break;

    case SYS_RECV:

        err = __sys_recvfrom(a0, (void __user *)a1, a[2], a[3],

                     NULL, NULL);

        break;

    case SYS_RECVFROM:

        err = __sys_recvfrom(a0, (void __user *)a1, a[2], a[3],

                     (struct sockaddr __user *)a[4],

                     (int __user *)a[5]);

        break;

    case SYS_SHUTDOWN:

        err = __sys_shutdown(a0, a1);

        break;

    case SYS_SETSOCKOPT:

        err = __sys_setsockopt(a0, a1, a[2], (char __user *)a[3],

                       a[4]);

        break;

    case SYS_GETSOCKOPT:

        err =

            __sys_getsockopt(a0, a1, a[2], (char __user *)a[3],

                     (int __user *)a[4]);

        break;

    case SYS_SENDMSG:

        err = __sys_sendmsg(a0, (struct user_msghdr __user *)a1,

                    a[2], true);

        break;

    case SYS_SENDMMSG:

        err = __sys_sendmmsg(a0, (struct mmsghdr __user *)a1, a[2],

                     a[3], true);

        break;

    case SYS_RECVMSG:

        err = __sys_recvmsg(a0, (struct user_msghdr __user *)a1,

                    a[2], true);

        break;

    case SYS_RECVMMSG:

        if (IS_ENABLED(CONFIG_64BIT) || !IS_ENABLED(CONFIG_64BIT_TIME))

            err = __sys_recvmmsg(a0, (struct mmsghdr __user *)a1,

                         a[2], a[3],

                         (struct __kernel_timespec __user *)a[4],

                         NULL);

        else

            err = __sys_recvmmsg(a0, (struct mmsghdr __user *)a1,

                         a[2], a[3], NULL,

                         (struct old_timespec32 __user *)a[4]);

        break;

    case SYS_ACCEPT4:

        err = __sys_accept4(a0, (struct sockaddr __user *)a1,

                    (int __user *)a[2], a[3]);

        break;

    default:

        err = -EINVAL;

        break;

    }

    return err;

}

 

从而我们可以得知,Linux系统真正调用的函数实际是__sys_bind而非sys_bind,因此在追踪sys_bind时没有反馈。

Linux系统对__sys_bind__sys_listen进行追踪,结果如下:

 技术图片

 

 

继续分析调用结果,通过对照socketcall源码与gdb追踪结果,可以发现,系统调用顺序为:

socket

socket

socket

socket

connect

socket

bind

listen

accpet

socket

connect

send_to

send

accpet

由此我们知道,在实验三中进程实现helloreplyhi的过程中,系统调用的顺序为,socket->connect->bind->listen->accept->send_to->send

由此,我们弄清了实验三中实现hihello的网络通信中,socket的系统调用过程。

 

在关于对不同协议的多态处理中,我在net/socket.c中找到了sock_create的实现,

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;

 

if (family < 0 || family >= NPROTO)

return -EAFNOSUPPORT;

if (type < 0 || type >= SOCK_MAX)

return -EINVAL;

 

if (family == PF_INET && type == SOCK_PACKET) {

pr_info_once("%s uses obsolete (PF_INET,SOCK_PACKET)
",

     current->comm);

family = PF_PACKET;

}

 

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

if (err)

return err;

sock = sock_alloc();

if (!sock) {

net_warn_ratelimited("socket: no more sockets
");

return -ENFILE; /* Not exactly a match, but its the

   closest posix thing */

}

 

sock->type = type;

 

#ifdef CONFIG_MODULES

if (rcu_access_pointer(net_families[family]) == NULL)

request_module("net-pf-%d", family);

#endif

 

rcu_read_lock();

pf = rcu_dereference(net_families[family]);

err = -EAFNOSUPPORT;

if (!pf)

goto out_release;

 

if (!try_module_get(pf->owner))

goto out_release;

 

 

rcu_read_unlock();

 

err = pf->create(net, sock, protocol, kern);

if (err < 0)

goto out_module_put;

if (!try_module_get(sock->ops->owner))

goto out_module_busy;

 

module_put(pf->owner);

err = security_socket_post_create(sock, family, type, protocol, kern);

if (err)

goto out_sock_release;

*res = sock;

 

return 0;

 

out_module_busy:

err = -EAFNOSUPPORT;

out_module_put:

sock->ops = NULL;

module_put(pf->owner);

out_sock_release:

sock_release(sock);

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);

 

阅读源码可知,关于多态实现的主要方式,是通过pf->create从而调用了inet_create()函数,而inet_create()函数一方面通过inetsw[]数组获取对应协议类型的接口操作集信息,另一方面创建struct sock类型的变量,最后是对创建的sock进行初始化。

以上是关于Socket与系统调用深度分析的主要内容,如果未能解决你的问题,请参考以下文章

Socket与系统调用深度分析

Socket与系统调用深度分析

Socket与系统调用深度分析

Socket系统调用Socket与系统调用深度分析

Socket与系统调用深度分析

Socket与系统调用深度分析