Socket与系统调用深度分析

系统调用

在一开始,应用程序是可以直接控制硬件的,这就需要程序员有很高的编程能力,否则一旦程序出了问题,会将整个系统Crash。

在现在的操作系统中,用户程序运行在用户态,而要进行诸如Socket磁盘I/O这样的一些操作,这需要切换到内核态,再进行进行相应的操作,而这一过程则是系统调用system call。有了操作系统分离了内核和用户态,应用程序就无法直接进行硬件资源的访问,需要经过系统调用来进行。

每次的系统调用,都会从用户态转换到内核态,运行完任务后,回到用户态。这中间的过程需要上下文切换(保存寄存器信息),也就是切换状态是需要消耗资源和时间的。

Socket

Socket是程序实现端到端通信的地址,互联网中每台设备有自己的IP地址,但每台设备上运行着许多程序。同时不同的程序都可能有通信的需求,这就需要一个套接字(Socket)来区分不同的程序。

一个套接字由IP地址和端口号组成。

IP address: port

实验

开启上次实验编译好的MenuOS系统

上次实验编译了一个带调试功能,且带有TCP服务器和客户端的MenuOS系统

进入LinuxKernel目录,启动虚拟机。

jett@ubuntu:~$ cd LinuxKernel
jett@ubuntu:~/LinuxKernel$ qemu-system-i386 -kernel linux-5.4.2/arch/x86/boot/bzImage -initrd rootfs.img -append "root=/dev/sda init=/init nokaslr" -s -S

进入调试

这时候虚拟机进入停止在一个黑屏界面,等待gdb的接入和下一步指令。

新开一个终端窗口,进入gdb调试。

接着分别

  • 导入符号表
  • 连接调试服务器
  • 设置断点
jett@ubuntu:~/LinuxKernel$ gdb
(gdb) file ~/LinuxKernel/linux-5.4.2/vmlinux
Reading symbols from ~/LinuxKernel/linux-5.4.2/vmlinux...done.
(gdb) target remote:1234
Remote debugging using :1234
0x0000fff0 in ?? ()
(gdb) break start_kernel
Breakpoint 1 at 0xc1db5885: file init/main.c, line 576.

然后输入c让系统继续执行,执行到断点start_kernel ()则说明成功。

(gdb) c
Continuing.

Breakpoint 1, start_kernel () at init/main.c:576
576 {

添加新断点sys_bind, sys_listen, sys_socketcall

(gdb) break sys_bind 
Breakpoint 2 at 0xc179beb0: file net/socket.c, line 1656.
(gdb) break sys_listen 
Breakpoint 3 at 0xc179bf60: file net/socket.c, line 1688.
(gdb) break sys_socketcall 
Breakpoint 4 at 0xc179ce00: file net/socket.c, line 2818.

c让系统继续执行

(gdb) c
Continuing.

Breakpoint 4, __se_sys_socketcall (call=1, args=-1075909056) at net/socket.c:2818
2818    SYSCALL_DEFINE2(socketcall, int, call, unsigned long __user *, args)

系统进入sys_socketcall断点,查看虚拟机窗口,可以看到虚拟机正在启动本地换回接口

再次继续执行,可以看到总共进了三次sys_socketcall,另外两次分别是启动以太网口和建立连接,这个在上周就已经发现了。

接着成功进入系统,可以查看之前编译进系统的TCP服务端和客户端

在MenuOS中输入replyhi启动TCP服务端

可以在gdb中发现再次进入sys_socketcall断点

Breakpoint 4, __se_sys_socketcall (call=1, args=-1075910240) at net/socket.c:2818
2818    SYSCALL_DEFINE2(socketcall, int, call, unsigned long __user *, args)

可以看到,断点停在__se_sys_socketcall中,位置是net/socket.c的2818行,找到函数如下:

2818 SYSCALL_DEFINE2(socketcall, int, call, unsigned long __user *, args)
2819 {
2820         unsigned long a[AUDITSC_ARGS];
2821         unsigned long a0, a1;
2822         int err;
2823         unsigned int len;
2824 
2825         if (call < 1 || call > SYS_SENDMMSG)
2826                 return -EINVAL;
2827         call = array_index_nospec(call, SYS_SENDMMSG + 1);
2828 
2829         len = nargs[call];
2830         if (len > sizeof(a))
2831                 return -EINVAL;
2832 
2833         /* copy_from_user should be SMP safe. */
2834         if (copy_from_user(a, args, len))
2835                 return -EFAULT;
2836 
2837         err = audit_socketcall(nargs[call] / sizeof(unsigned long), a);
2838         if (err)
2839                 return err;
2840 
2841         a0 = a[0];
2842         a1 = a[1];
2843 
2844         switch (call) {
2845         case SYS_SOCKET:
2846                 err = __sys_socket(a0, a1, a[2]);
2847                 break;
2848         case SYS_BIND:
2849                 err = __sys_bind(a0, (struct sockaddr __user *)a1, a[2]);
2850                 break;
2851         case SYS_CONNECT:
2852                 err = __sys_connect(a0, (struct sockaddr __user *)a1, a[2]);
2853                 break;
2854         case SYS_LISTEN:
2855                 err = __sys_listen(a0, a1);
2856                 break;
2857         case SYS_ACCEPT:
2858                 err = __sys_accept4(a0, (struct sockaddr __user *)a1,
2859                                     (int __user *)a[2], 0);
2860                 break;
2861         ...
2930         default:
2931                 err = -EINVAL;
2932                 break;
2933         }
2934         return err;
2935 }

函数内部主体是一个switch语句,根据我们的call参数来进行选择,通过gdb我们可以看到,这时(call=1, args=-1075910240),具体要根据1是哪个case来追踪调用。

而这些case的定义并不在socket.c中。我们可以在/LinuxKernel/linux-5.4.2/include/linux下找到socket.h文件

#define SYS_SOCKET    1        /* sys_socket(2)        */
#define SYS_BIND    2        /* sys_bind(2)            */
#define SYS_CONNECT    3        /* sys_connect(2)        */
#define SYS_LISTEN    4        /* sys_listen(2)        */
#define SYS_ACCEPT    5        /* sys_accept(2)        */
#define SYS_GETSOCKNAME    6        /* sys_getsockname(2)        */
#define SYS_GETPEERNAME    7        /* sys_getpeername(2)        */
#define SYS_SOCKETPAIR    8        /* sys_socketpair(2)        */
#define SYS_SEND    9        /* sys_send(2)            */
#define SYS_RECV    10        /* sys_recv(2)            */
#define SYS_SENDTO    11        /* sys_sendto(2)        */
#define SYS_RECVFROM    12        /* sys_recvfrom(2)        */
#define SYS_SHUTDOWN    13        /* sys_shutdown(2)        */
#define SYS_SETSOCKOPT    14        /* sys_setsockopt(2)        */
#define SYS_GETSOCKOPT    15        /* sys_getsockopt(2)        */
#define SYS_SENDMSG    16        /* sys_sendmsg(2)        */
#define SYS_RECVMSG    17        /* sys_recvmsg(2)        */

所以实质则是进入了__sys_socket(a0, a1, a[2]);函数内。

12-13 16:04