Socket与系统调用深度分析

Socket与系统调用深度分析

〇,linux系统调用

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

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

  Socket与系统调用深度分析_第1张图片

 

   正如上图所示,运行在用户态的函数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 /* 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\n", \ __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设置断点,并跟踪,结果如下:

Socket与系统调用深度分析_第2张图片

 

Socket与系统调用深度分析_第3张图片

  上图的结果在两点上不符合我们的预期或者说值得探究:一,对与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与系统调用深度分析_第4张图片

Socket与系统调用深度分析_第5张图片

Socket与系统调用深度分析_第6张图片

  结果证明我们此时的猜想并没有错误,之前的问题也得到解决:涉及到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)\n", 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\n");
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。通过这样的方法,实现了对于不同的协议的封装。

你可能感兴趣的:(Socket与系统调用深度分析)