zoukankan      html  css  js  c++  java
  • 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;

  • 相关阅读:
    c:forTokens标签循环输出
    jsp转long类型为date,并且格式化
    spring中@Param和mybatis中@Param使用区别(暂时还没接触)
    734. Sentence Similarity 有字典数组的相似句子
    246. Strobogrammatic Number 上下对称的数字
    720. Longest Word in Dictionary 能连续拼接出来的最长单词
    599. Minimum Index Sum of Two Lists两个餐厅列表的索引和最小
    594. Longest Harmonious Subsequence强制差距为1的最长连续
    645. Set Mismatch挑出不匹配的元素和应该真正存在的元素
    409. Longest Palindrome 最长对称串
  • 原文地址:https://www.cnblogs.com/qyf2199/p/12059346.html
Copyright © 2011-2022 走看看