学习一下对Socket与系统调用的分析分析html
1、介绍linux
咱们都知道高级语言的网络编程最终的实现都是调用了系统的Socket API编程接口,在操做系统提供的socket系统接口之上能够创建不一样端口之间的网络链接,从而使咱们能够编写各基于不一样网络协议的应用程序。而用户程序通常都是运行在用户态,依靠的Socket接口也是在在用户态,咱们都知道socket接口是经过系统调用机制进入内核,从而从内核的层面提升服务。本次实验主要须要分析出socketAPI函数是如何进行系统调用的。编程
首先咱们再明确一下系统调用和socketAPI之间的关系:也就是说系统调用是使得API能得到内核支持的途径。api
2、实验步骤服务器
首先从参考了大佬的博客以后获悉,在linux系统中,与socket API有关的系统调用包括全部的与socketapi都使用的sys_socketcall系统调用和每个socketapi都对应特定的系统调用,所以我主要经过这两类来进行分析。网络
一、首先使用gdb链接menu系统,这个系统是上次实验制做的简易系统app
注意:这里须要先用下面的命令启动tcp server,不然会显示链接超时socket
执行 qemu -kernel ../linux-5.0.1/arch/x86/boot/bzImage -initrd ../rootfs.img -append nokaslr -s -S
而后新开一个终端,打开gdb,而且使用target链接上menu系统tcp
gdb file ~/net/linux-5.0.1/vmlinux target remote:1234
链接之后就能够打断点进行调试了。函数
二、对特殊的系统调用进行测试:给sys_bind和sys_listen分析
接着按c即继续运行程序,此时menu系统继续启动。
根据提示,输入replyhi之后,发现gdb的终端以下所示:
说明并无停在断点,也就是使用replyhi的时候并无调用sys_listen和sys_bind这两个内核函数。
三、接下来对全部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语句调用不一样的内核处理函数,完成网络通讯。
3、总结
此次的实验确实挺难的,作了好久,也只是作出了点皮毛,可是经过这个实现,进一步了解了linux内核网络通讯部分的机制,对写内核的人更加钦佩,只能说,大佬太强了!!!
实验过程参考同窗博客:https://www.cnblogs.com/hhssqq9999/p/12048964.html
原文出处:https://www.cnblogs.com/iyuanyuan/p/12069677.html