Socket与系统调用深度分析

Posted qyf2199

tags:

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

Socket与系统调用深度分析

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

一、系统调用

技术图片

 

 

 

 

Socket 调用流程:

技术图片

socket():创建套接字。

bind():指定本地地址。一个套接字用socket()创建后,它其实还没有与任何特定的本地或目的地址相关联。在很多情况下,应用程序并不关心它们使用的本地地址,这时就可以不用调用bind指定本地的地址,而由协议软件为它们选择一个。但是,在某个知名端口(Well-known Port)上操作的服务器进程必须要对系统指定本地端口。所以一旦创建了一个套接字,服务器就必须使用bind()系统调用为套接字建立一个本地地址。

connect():将套接字连接到目的地址。初始创建的套接字并未与任何外地目的地址关联。客户机可以调用connect()为套接字绑定一个永久的目的地址,将它置于已连接状态。对数据流方式的套接字,必须在传输数据前,调用connect()构造一个与目的地的TCP连接,并在不能构造连接时返回一个差错代码。如果是数据报方式,则不是必须在传输数据前调用connect。如果调用了connect(),也并不像数据流方式那样发送请求建连的报文,而是只在本地存储目的地址,以后该socket上发送的所有数据都送往这个地址,程序员就可以免去为每一次发送数据都指定目的地址的麻烦。

listen():设置等待连接状态。对于一个服务器的程序,当申请到套接字,并调用bind()与本地地址绑定后,就应该等待某个客户机的程序来要求连接。listen()就是把一个套接字设置为这种状态的函数。

accept():接受连接请求。服务器进程使用系统调用socket,bind和listen创建一个套接字,将它绑定到知名的端口,并指定连接请求的队列长度。然后,服务器调用accept进入等待状态,直到到达一个连接请求。

send()/recv()和sendto()/recvfrom():发送和接收数据 。在数据流方式中,一个连接建立以后,或者在数据报方式下,调用了connect()进行了套接字与目的地址的绑定后,就可以调用send()和reev()函数进行数据传输。

closesocket():关闭套接字。

 

内核中断简介

 

 

 技术图片

 

 

 二、实验验证

技术图片

 

 接着再重新打开一个终端,进入gdb调试阶段:

 

(gdb)file linux-3.18.6/vmlinux   // 在targe remote 之前加载符号表
(gdb)target remote:1234          // 建立 gdb 和 gdbserver 之间的连接

 

 start_kernel、sys_socketcall内核函数,设置断点跟踪:

 

 技术图片

linux系统调用表:

 

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;

}

 

glibc提供的与socket有关的系统调用函数API、系统调用号及对应的内核处理函数:

技术图片

总结一下gdb常用指令:

break——设置断点,缩写为b;

delete——删除断点,缩写c;

step——单步跟踪,进入调用函数,s;

next——单步跟踪,不进入调用函数,n;

continue——恢复执行,c;

run——启动调试,r;

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

Socket与系统调用深度分析

Socket与系统调用深度分析

Socket与系统调用深度分析

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

Socket与系统调用深度分析

Socket与系统调用深度分析