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

  • 相关阅读:
    Jquery使用live导致执行的内容会重复执行
    网上找的些tomact配置
    DatePicker 注意点 1.不用v-model 用:value 2.配合on-change进行回调 3.初始值 当天的用 (new Date()).toLocaleDateString().replace(///g, '-')
    Springboot框架 idea 工具 解决代码修改需要重新启动的方式
    合同签订
    ORACLE链接错误解决方案
    oracle中的split
    axis2和springboot冲突问题
    ActiveMQ中文乱码问题
    AXIS2的接口调用常见以及不常见问题
  • 原文地址:https://www.cnblogs.com/qyf2199/p/12059346.html
Copyright © 2020-2023  润新知