Socket与系统调用深度分析
iyuanyuan 人气:1学习一下对Socket与系统调用的分析分析
一、介绍
我们都知道高级语言的网络编程最终的实现都是调用了系统的Socket API编程接口,在操作系统提供的socket系统接口之上可以建立不同端口之间的网络连接,从而使我们可以编写各基于不同网络协议的应用程序。而用户程序一般都是运行在用户态,依靠的Socket接口也是在在用户态,我们都知道socket接口是通过系统调用机制进入内核,从而从内核的层面提高服务。本次实验主要需要分析出socketAPI函数是如何进行系统调用的。
首先我们再明确一下系统调用和socketAPI之间的关系:也就是说系统调用是使得API能获得内核支持的途径。
二、实验步骤
首先从参考了大佬的博客之后获悉,在linux系统中,与socket API有关的系统调用包括所有的与socketapi都使用的sys_socketcall系统调用和每一个socketapi都对应特定的系统调用,因此我主要通过这两类来进行分析。
1、首先使用gdb连接menu系统,这个系统是上次实验制作的简易系统
注意:这里需要先用下面的命令启动tcp server,否则会显示连接超时
执行 qemu -kernel ../linux-5.0.1/arch/x86/boot/bzImage -initrd ../rootfs.img -append nokaslr -s -S
然后新开一个终端,打开gdb,并且使用target连接上menu系统
gdb file ~/net/linux-5.0.1/vmlinux target remote:1234
连接以后就可以打断点进行调试了。
2、对特殊的系统调用进行测试:给sys_bind和sys_listen分析
接着按c即继续运行程序,此时menu系统继续启动。
根据提示,输入replyhi以后,发现gdb的终端如下所示:
说明并没有停在断点,也就是使用replyhi的时候并没有调用sys_listen和sys_bind这两个内核函数。
3、接下来对所有socketapi都使用的sys_socketcall系统调用进行测试,使用 b sys_socketcall打断点,接着按c继续执行
然后在mune系统进行测试,输入replyhi以后,发现gdb终端有反馈了,如下所示:
说明停在了SYSCALL_DEFINE2(socketcall, int, call, unsigned long __user *, args),也就是此时使用了这个内核函数
查看源码如下:
下面我们简单分析一下这个函数:发现这段c语言很大一部分都是switch语句,说明这个函数的主要作用是根据不同的输入来选择不同的操作,调用不同的内核处理函数,传入是SYS_BIND则调用__sys_bind,传入SYS_LISTEN则调用 __sys_listen等内核函数进行相应的处理。
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; }
此时我们查看一下replyhi的源码,看看究竟是如何调用的。
从下面的程序可以看到,replyhi调用其他函数的顺序InitializeService,ServiceStart,RecvMsg,SendMsg、ServiceStop。
接下来我们需要做的是找出InitializeService,ServiceStart,RecvMsg,SendMsg和ServiceStop,看看这些函数是如何使用内核处理函数的。
(1)首先是InitializeService,可以看到这个函数的主要就是调用了 PrepareSocket(IP_ADDR,PORT)和 InitServer()
所以只能继续追踪,首先看 PrepareSocket(IP_ADDR,PORT),可以看到这个函数的主要作用是分配内存空间,并调用socket函数,创建套接字
接着是InitServer()函数,从程序中看到这个函数的主要作用调用bind函数,服务器使用bind来指明熟知的端口号,然后等待连接
并进行监听
所以InitializeService函数主要作用在于建立socket,绑定socket并进行端口的监听。
(2)ServiceStart函数,显然,这个函数的作用只是调用accept,获取传入连接请求。
(3) SendMsg,顾名思义,这个函数主要就是进行信息的发送,依靠的内核函数是send函数
(4)RecvMsg 这个函数主要就是进行信息的接受,依靠的内核函数是recv函数
(5)ServiceStop,这个函数很简单,就是调用close函数进行撤销套接字,结束当前的连接。
至此,我们终于分析完了replyhi这个函数是如何依靠socketAPI以及内核服务程序进行网络通信的。
回顾总结一下:
reply函数通过调用InitializeService,ServiceStart,RecvMsg,SendMsg、ServiceStop子函数,这些函数进行的系统调用分别是socket、bind、accept、recv、send、close。然后在内核的sockct函数中,通过传入的系统调用,使用switch语句调用不同的内核处理函数,完成网络通信。
三、总结
这次的实验确实挺难的,做了很久,也只是做出了点皮毛,但是通过这个实现,进一步了解了linux内核网络通信部分的机制,对写内核的人更加钦佩,只能说,大佬太强了!!!
实验过程参考同学博客:https://www.cnblogs.com/hhssqq9999/p/12048964.html
加载全部内容