zoukankan      html  css  js  c++  java
  • Socket与系统调用深度分析

    Socket与系统调用深度分析

    〇,linux系统调用

      本次实验的主要内容是从socket接口入手,通过跟踪相关函数在内核中的运行过程了解socket相关的系统调用是如何工作的。那么首当其冲需要解决的问题就是,什么是系统调用?socket等程序接口又是如何使用系统调用来实现自身功能的呢?

      但凡学过操作系统的人都不难理解,操作系统分为用户态和内核态,应用程序一般工作在用户态,而操作系统则通过系统调用为工作在其上的进程提供服务:

      

       正如上图所示,运行在用户态的函数xyz()通过中断进入内核态,再通过系统调用处理函数调用相关的服务历程。

    一,分析用户函数

      要分析socket接口与系统调用之间的关系,首先就要找出socket究竟调用了哪些系统调用。根据上一小节的内容,我们可以从对应的函数入手,找出相应的系统调用。

      打开lab3中的main.c文件,在main函数中找到如下代码:

        MenuConfig("replyhi", "Reply hi TCP Service", StartReplyhi);
        MenuConfig("hello", "Hello TCP Client", Hello);

      按照函数调用的关系依次查找,如StartReplyhi()到Replyhi再到syswrapper.h中,找到如下代码:

    #include<arpa/inet.h> /* internet socket */

    /**/

    #define
    PrepareSocket(addr,port) int sockfd = -1; struct sockaddr_in serveraddr; struct sockaddr_in clientaddr; socklen_t addr_len = sizeof(struct sockaddr); serveraddr.sin_family = AF_INET; serveraddr.sin_port = htons(port); serveraddr.sin_addr.s_addr = inet_addr(addr); memset(&serveraddr.sin_zero, 0, 8); sockfd = socket(PF_INET,SOCK_STREAM,0); #define InitServer() int ret = bind( sockfd, (struct sockaddr *)&serveraddr, sizeof(struct sockaddr)); if(ret == -1) { fprintf(stderr,"Bind Error,%s:%d ", __FILE__,__LINE__); close(sockfd); return -1; } listen(sockfd,MAX_CONNECT_QUEUE);

    /**/
    #define InitializeService()                            
            PrepareSocket(IP_ADDR,PORT);                   
            InitServer();

      至此我们已经找到了如socket(),listen(),bind()以及close()等几个函数,通过结合linux系统调用表(https://github.com/mengning/linux/blob/master/arch/x86/entry/syscalls/syscall_32.tbl)和其他网上资料,我们可以发现Linux 内核为所有与 socket 有关的操作提供了一个统一的系统调用入口,但是在用户程序界面上则通过 C 语言程序库 c.lib 提供诸多库函数,看起来好像都是独立的系统调用一样。内核中为 socket 设置的总入口为 sys_socketcall(),其代码在 net/socket.c 中,而该函数实际上则调用的是SYSCALL_DEFINE2

     1 /*与socket相关的系统调用总入口。 */
     2 /*
     3 *函数的第一个参数 call 即为具体的操作码,而参数 args 为指向一个数组的指针,可以根据具体操作码的不同,确定从用户空间复制参数的数量;
     4 */
     5 asmlinkage long sys_socketcall(int call, unsigned long __user *args)
     6 {
     7     unsigned long a[6];
     8     unsigned long a0,a1;
     9     int err;
    10 
    11     if(call<1||call>SYS_RECVMSG)
    12         return -EINVAL;
    13 
    14     /* copy_from_user should be SMP safe. */
    15     if (copy_from_user(a, args, nargs[call]))
    16         return -EFAULT;
    17 
    18     a0=a[0];
    19     a1=a[1];
    20 
    21     switch(call) 
    22     {
    23         case SYS_SOCKET:
    24             err = sys_socket(a0,a1,a[2]);
    25             break;
    26         case SYS_BIND:
    27             err = sys_bind(a0,(struct sockaddr __user *)a1, a[2]);
    28             break;
    29         case SYS_CONNECT:
    30             err = sys_connect(a0, (struct sockaddr __user *)a1, a[2]);
    31             break;
    32         case SYS_LISTEN:
    33             err = sys_listen(a0,a1);
    34             break;
    35         case SYS_ACCEPT:
    36             err = sys_accept(a0,(struct sockaddr __user *)a1, (int __user *)a[2]);
    37             break;
    38         case SYS_GETSOCKNAME:
    39             err = sys_getsockname(a0,(struct sockaddr __user *)a1, (int __user *)a[2]);
    40             break;
    41         case SYS_GETPEERNAME:
    42             err = sys_getpeername(a0, (struct sockaddr __user *)a1, (int __user *)a[2]);
    43             break;
    44         case SYS_SOCKETPAIR:
    45             err = sys_socketpair(a0,a1, a[2], (int __user *)a[3]);
    46             break;
    47         case SYS_SEND:
    48             err = sys_send(a0, (void __user *)a1, a[2], a[3]);
    49             break;
    50         case SYS_SENDTO:
    51             err = sys_sendto(a0,(void __user *)a1, a[2], a[3],
    52                      (struct sockaddr __user *)a[4], a[5]);
    53             break;
    54         case SYS_RECV:
    55             err = sys_recv(a0, (void __user *)a1, a[2], a[3]);
    56             break;
    57         case SYS_RECVFROM:
    58             err = sys_recvfrom(a0, (void __user *)a1, a[2], a[3],
    59                        (struct sockaddr __user *)a[4], (int __user *)a[5]);
    60             break;
    61         case SYS_SHUTDOWN:
    62             err = sys_shutdown(a0,a1);
    63             break;
    64         case SYS_SETSOCKOPT:
    65             err = sys_setsockopt(a0, a1, a[2], (char __user *)a[3], a[4]);
    66             break;
    67         case SYS_GETSOCKOPT:
    68             err = sys_getsockopt(a0, a1, a[2], (char __user *)a[3], (int __user *)a[4]);
    69             break;
    70         case SYS_SENDMSG:
    71             err = sys_sendmsg(a0, (struct msghdr __user *) a1, a[2]);
    72             break;
    73         case SYS_RECVMSG:
    74             err = sys_recvmsg(a0, (struct msghdr __user *) a1, a[2]);
    75             break;
    76         default:
    77             err = -EINVAL;
    78             break;
    79     }
    80     return err;

      通过解读上述代码并对照相关的系统调用标,我们似乎已经找到了相关代码的系统调用入口,接下来我们可以通过gdb来验证我们的猜想。

    二,跟踪系统调用

      在gdb中,对sys_socketcall,sys_bind,sys_listen设置断点,并跟踪,结果如下:

      上图的结果在两点上不符合我们的预期或者说值得探究:一,对与sys_bind和sys_listen的跟踪没有反馈;二,sys_socketcall在启动qemu是即被调用,且每次的调用次数(即call的值)又代表什么。

      针对第一个问题,我们注意到每次跟踪sys_socketcall时,都会定位到这样一个函数即SYSCALL_DEFINE2()

    通过查阅资料可知,实际上对sys_socketcall的调用是通过该函数实现的,而该函数的代码如下:

      1 SYSCALL_DEFINE2(socketcall, int, call, unsigned long __user *, args)
      2 {
      3     unsigned long a[AUDITSC_ARGS];
      4     unsigned long a0, a1;
      5     int err;
      6     unsigned int len;
      7 
      8     if (call < 1 || call > SYS_SENDMMSG)
      9         return -EINVAL;
     10     call = array_index_nospec(call, SYS_SENDMMSG + 1);
     11 
     12     len = nargs[call];
     13     if (len > sizeof(a))
     14         return -EINVAL;
     15 
     16     /* copy_from_user should be SMP safe. */
     17     if (copy_from_user(a, args, len))
     18         return -EFAULT;
     19 
     20     err = audit_socketcall(nargs[call] / sizeof(unsigned long), a);
     21     if (err)
     22         return err;
     23 
     24     a0 = a[0];
     25     a1 = a[1];
     26 
     27     switch (call) {
     28     case SYS_SOCKET:
     29         err = __sys_socket(a0, a1, a[2]);
     30         break;
     31     case SYS_BIND:
     32         err = __sys_bind(a0, (struct sockaddr __user *)a1, a[2]);
     33         break;
     34     case SYS_CONNECT:
     35         err = __sys_connect(a0, (struct sockaddr __user *)a1, a[2]);
     36         break;
     37     case SYS_LISTEN:
     38         err = __sys_listen(a0, a1);
     39         break;
     40     case SYS_ACCEPT:
     41         err = __sys_accept4(a0, (struct sockaddr __user *)a1,
     42                     (int __user *)a[2], 0);
     43         break;
     44     case SYS_GETSOCKNAME:
     45         err =
     46             __sys_getsockname(a0, (struct sockaddr __user *)a1,
     47                       (int __user *)a[2]);
     48         break;
     49     case SYS_GETPEERNAME:
     50         err =
     51             __sys_getpeername(a0, (struct sockaddr __user *)a1,
     52                       (int __user *)a[2]);
     53         break;
     54     case SYS_SOCKETPAIR:
     55         err = __sys_socketpair(a0, a1, a[2], (int __user *)a[3]);
     56         break;
     57     case SYS_SEND:
     58         err = __sys_sendto(a0, (void __user *)a1, a[2], a[3],
     59                    NULL, 0);
     60         break;
     61     case SYS_SENDTO:
     62         err = __sys_sendto(a0, (void __user *)a1, a[2], a[3],
     63                    (struct sockaddr __user *)a[4], a[5]);
     64         break;
     65     case SYS_RECV:
     66         err = __sys_recvfrom(a0, (void __user *)a1, a[2], a[3],
     67                      NULL, NULL);
     68         break;
     69     case SYS_RECVFROM:
     70         err = __sys_recvfrom(a0, (void __user *)a1, a[2], a[3],
     71                      (struct sockaddr __user *)a[4],
     72                      (int __user *)a[5]);
     73         break;
     74     case SYS_SHUTDOWN:
     75         err = __sys_shutdown(a0, a1);
     76         break;
     77     case SYS_SETSOCKOPT:
     78         err = __sys_setsockopt(a0, a1, a[2], (char __user *)a[3],
     79                        a[4]);
     80         break;
     81     case SYS_GETSOCKOPT:
     82         err =
     83             __sys_getsockopt(a0, a1, a[2], (char __user *)a[3],
     84                      (int __user *)a[4]);
     85         break;
     86     case SYS_SENDMSG:
     87         err = __sys_sendmsg(a0, (struct user_msghdr __user *)a1,
     88                     a[2], true);
     89         break;
     90     case SYS_SENDMMSG:
     91         err = __sys_sendmmsg(a0, (struct mmsghdr __user *)a1, a[2],
     92                      a[3], true);
     93         break;
     94     case SYS_RECVMSG:
     95         err = __sys_recvmsg(a0, (struct user_msghdr __user *)a1,
     96                     a[2], true);
     97         break;
     98     case SYS_RECVMMSG:
     99         if (IS_ENABLED(CONFIG_64BIT) || !IS_ENABLED(CONFIG_64BIT_TIME))
    100             err = __sys_recvmmsg(a0, (struct mmsghdr __user *)a1,
    101                          a[2], a[3],
    102                          (struct __kernel_timespec __user *)a[4],
    103                          NULL);
    104         else
    105             err = __sys_recvmmsg(a0, (struct mmsghdr __user *)a1,
    106                          a[2], a[3], NULL,
    107                          (struct old_timespec32 __user *)a[4]);
    108         break;
    109     case SYS_ACCEPT4:
    110         err = __sys_accept4(a0, (struct sockaddr __user *)a1,
    111                     (int __user *)a[2], a[3]);
    112         break;
    113     default:
    114         err = -EINVAL;
    115         break;
    116     }
    117     return err;
    118 }

      其代码逻辑大致与sys_socket相同,即通过switch分支进入不同的分支处理,而在各个分支处理下,我们不难发现,实际上,系统中真正调用的是形如__sys_listen这样的函数,对于这样的发现,我们同样通过gdb跟踪进行验证。

      我们首先将涉及到的系统调用都打上断点,接着重复之前的步骤加以跟踪,结果如下:

      结果证明我们此时的猜想并没有错误,之前的问题也得到解决:涉及到socket的系统调用都使用统一的入口sys_socketcall,再通过SYSCALL_DEFINE2进入不同的分支,调用不同的系统调用,如在本次实验中,就包括sys_socket,sys_bind,sys_listen,sys_connect,sys_accept4等等。

    三,函数分析与对不同协议的封装

      socket接口可以实现对与不同协议的封装,具体这一机制是如何实现的,我们可以通过根据对上文中提到的内核函数进行分析略知一二:

     1 asmlinkage long sys_socket(int family, int type, int protocol)
     2 {
     3     int retval;
     4     struct socket *sock;
     5 
     6     /* 根据协议族、套口类型、传输层协议创建套口 */
     7     retval = sock_create(family, type, protocol, &sock);
     8     if (retval < 0)
     9         goto out;
    10 
    11     /* 为创建的套接口分配一个文件描述符并进行绑定 */
    12     retval = sock_map_fd(sock);
    13     if (retval < 0)
    14         goto out_release;    /* 根据是否成功帮顶选择是否释放  */
    15 
    16 out:
    17     return retval;
    18 
    19 out_release:
    20     sock_release(sock);
    21     return retval;
    22 }

      根据上述代码,我们接着找到sock_creat:

     1 /**
     2  * 创建一个套接口
     3  *  family:     套接口协议族
     4  *  type:       套接口类型
     5  *  protocol:       传输层协议
     6  *  res:            输出参数,创建成功的套接口指针
     7  *  kern:       由内核还是应用程序创建。
     8  */
     9 static int __sock_create(int family, int type, int protocol, struct socket **res, int kern)
    10 {
    11     int err;
    12     struct socket *sock;
    13 
    14     if (family < 0 || family >= NPROTO)/* 参数合法性检测 */
    15         return -EAFNOSUPPORT;
    16     if (type < 0 || type >= SOCK_MAX)
    17         return -EINVAL;
    18     /**
    19      * IPV4协议族的SOCK_PACKET类型套接口已经不被支持
    20      * 为兼容旧程序,转换为PF_PACKET 
    21 */
    22     if (family == PF_INET && type == SOCK_PACKET) {
    23         static int warned; 
    24         if (!warned) {
    25             warned = 1;
    26             printk(KERN_INFO "%s uses obsolete (PF_INET,SOCK_PACKET)
    ", current->comm);
    27         }
    28         family = PF_PACKET;
    29     }
    30 
    31     /* 由安全模块对创建过程进行审计 */
    32     err = security_socket_create(family, type, protocol, kern);
    33     if (err)
    34         return err;
    35 
    36 #if defined(CONFIG_KMOD)
    37     if (net_families[family]==NULL)/* 相应的协议族在内核中尚不存在,加载模块以支持该协议族 */
    38     {
    39         request_module("net-pf-%d",family);
    40     }
    41 #endif
    42 
    43     /* 等待,直到锁被释放 */
    44     net_family_read_lock();
    45     if (net_families[family] == NULL) {/* 如果协议族仍然不存在,说明不支持此协议族 */
    46         err = -EAFNOSUPPORT;
    47         goto out;
    48     }
    49 
    50 
    51     if (!(sock = sock_alloc())) {/* 分配与inode关联的套接口 */
    52         printk(KERN_WARNING "socket: no more sockets
    ");
    53         err = -ENFILE;      
    54         goto out;
    55     }
    56 
    57     sock->type  = type;/* 设置套接口类型。 */
    58 
    59     err = -EAFNOSUPPORT;
    60     if (!try_module_get(net_families[family]->owner))/* 增加对协议族模块的引用,如果失败则退出 */
    61         goto out_release;
    62 
    63     /* 调用协议族的创建方法,对IPV4来说,调用的是inet_create */
    64     if ((err = net_families[family]->create(sock, protocol)) < 0)
    65         goto out_module_put;
    66     if (!try_module_get(sock->ops->owner)) {/* 增加传输层模块的引用计数 */
    67         sock->ops = NULL;
    68         goto out_module_put;
    69     }
    70     /* 增加了传输层模块的引用计数后,可以释放协议族的模块引用计数 */
    71     module_put(net_families[family]->owner);
    72     *res = sock;
    73     /* 通知安全模块,对创建过程进行检查。 */
    74     security_socket_post_create(sock, family, type, protocol, kern);
    75 
    76 out:
    77     net_family_read_unlock();
    78     return err;
    79 out_module_put:
    80     module_put(net_families[family]->owner);
    81 out_release:
    82     sock_release(sock);
    83     goto out;
    84 }

      高亮部分显示,在sock_creat中调用协议族的创建方法创建一个套接口,对IPV4来说,就是调用inet_create。通过这样的方法,实现了对于不同的协议的封装。

  • 相关阅读:
    Git-常用命令大全
    winform- 发现一个博客做用户自定义控件文章非常好的
    winform- 实现画圆角矩形
    webpack 0-1 配置 以及 (性能优化)
    前端学习资料网址收集整理
    直线参数方程的研究
    高中数学向量点乘的坐标证明的一点疑惑。
    微信小程序下载视频或者照片或者文件到本地相册中
    Webpack配置区分开发环境和生产环境
    gitHub 搜索技巧
  • 原文地址:https://www.cnblogs.com/imerliu/p/12060563.html
Copyright © 2011-2022 走看看