Socket与系统调用深度分析

一、系统调用简述

  操作系统的主要功能是为管理硬件资源和为应用程序开发人员提供良好的环境来使应用程序具有更好的兼容性,为了达到这个目的,内核提供一系列具备预定功能的多内核函数,通过一组称为系统调用(system call)的接口呈现给用户。系统调用把应用程序的请求传给内核,调用相应的内核函数完成所需的处理,将处理结果返回给应用程序。
  现代的操作系统通常都具有多任务处理的功能,通常靠进程来实现。由于操作系统快速的在每个进程间切换执行,所以一切看起来就会像是同时的。同时这也带来了很多安全问题,例如,一个进程可以轻易的修改进程的内存空间中的数据来使另一个进程异常或达到一些目的,因此操作系统必须保证每一个进程都能安全的执行。这一问题的解决方法是在处理器中加入基址寄存器和界限寄存器。这两个寄存器中的内容用硬件限制了对储存器的存取指令所访问的储存器的地址。这样就可以在系统切换进程时写入这两个寄存器的内容到该进程被分配的地址范围,从而避免恶意软件。
  为了防止用户程序修改基址寄存器和界限寄存器中的内容来达到访问其他内存空间的目的,这两个寄存器必须通过一些特殊的指令来访问。通常,处理器设有两种模式:“用户模式”与“内核模式”,通过一个标签位来鉴别当前正处于什么模式。一些诸如修改基址寄存器内容的指令只有在内核模式中可以执行,而处于用户模式的时候硬件会直接跳过这个指令并继续执行下一个。
  同样,为了安全问题,一些I/O操作的指令都被限制在只有内核模式可以执行,因此操作系统有必要提供接口来为应用程序提供诸如读取磁盘某位置的数据的接口,这些接口就被称为系统调用。
  当操作系统接收到系统调用请求后,会让处理器进入内核模式,从而执行诸如I/O操作,修改基址寄存器内容等指令,而当处理完系统调用内容后,操作系统会让处理器返回用户模式,来执行用户代码。
二、Socket简述
 
  传输层 实现 端到端 的通信,因此,每一个传输层连接有两个端点。那么,传输层连接的端点是什么呢?不是 主机 ,不是主机的 IP地址 ,不是应用进程,也不是传输层的协议端口。传输层连接的端点叫做套接字(socket)。根据RFC793的定义: 端口号 拼接到IP地址就构成了套接字。所谓套接字,实际上是一个通信端点,每个套接字都有一个套接字序号,包括主机的IP地址与一个16位的主机端口号,即形如(主机IP地址:端口号)。例如,如果IP地址是210.37.145.1,而端口号是23,那么得到套接字就是(210.37.145.1:23)。
  总之,套接字Socket=(IP地址:端口号),套接字的表示方法是点分十进制的IP地址后面写上端口号,中间用冒号或逗号隔开。每一个传输层连接唯一地被通信两端的两个端点(即两个套接字)所确定。
  套接字可以看成是两个网络应用程序进行通信时,各自通信连接中的一个端点。通信时,其中的一个网络应用程序将要传输的一段信息写入它所在主机的Socket中,该Socket通过网络接口卡的传输介质将这段信息发送给另一台主机的Socket中,使这段信息能传送到其他程序中。因此,两个应用程序之间的数据传输要通过套接字来完成。
  在网络应用程序设计时,由于TCP/IP的核心内容被封装在操作系统中,如果应用程序要使用TCP/IP,可以通过系统提供的TCP/IP的编程接口来实现。在Windows环境下,网络应用程序编程接口称作Windows Socket。为了支持用户开发面向应用的通信程序,大部分系统都提供了一组基于TCP或者UDP的应用程序编程接口(API),该接口通常以一组函数的形式出现,也称为套接字(Socket)。
  Socket与系统调用深度分析_第1张图片

 

 三、实验过程

所有的socket系统调用的总入口是sys_socketcall(),在include/linux/Syscalls.h中定义,其中参数call是标识接口编号,args是接口参数指针,接口编号的定义在 include/uapi/linux/net.h中定义

 1 #include 
 2 #include 
 3 
 4 #define NPROTO        AF_MAX
 5 
 6 #define SYS_SOCKET    1        /* sys_socket(2)        */
 7 #define SYS_BIND    2        /* sys_bind(2)            */
 8 #define SYS_CONNECT    3        /* sys_connect(2)        */
 9 #define SYS_LISTEN    4        /* sys_listen(2)        */
10 #define SYS_ACCEPT    5        /* sys_accept(2)        */
11 #define SYS_GETSOCKNAME    6        /* sys_getsockname(2)        */
12 #define SYS_GETPEERNAME    7        /* sys_getpeername(2)        */
13 #define SYS_SOCKETPAIR    8        /* sys_socketpair(2)        */
14 #define SYS_SEND    9        /* sys_send(2)            */
15 #define SYS_RECV    10        /* sys_recv(2)            */
16 #define SYS_SENDTO    11        /* sys_sendto(2)        */
17 #define SYS_RECVFROM    12        /* sys_recvfrom(2)        */
18 #define SYS_SHUTDOWN    13        /* sys_shutdown(2)        */
19 #define SYS_SETSOCKOPT    14        /* sys_setsockopt(2)        */
20 #define SYS_GETSOCKOPT    15        /* sys_getsockopt(2)        */
21 #define SYS_SENDMSG    16        /* sys_sendmsg(2)        */
22 #define SYS_RECVMSG    17        /* sys_recvmsg(2)        */
23 #define SYS_ACCEPT4    18        /* sys_accept4(2)        */
24 #define SYS_RECVMMSG    19        /* sys_recvmmsg(2)        */
25 #define SYS_SENDMMSG    20        /* sys_sendmmsg(2)        */

启动gdb server

qemu -kernel ../linux-5.0.1/arch/x86/boot/bzImage -initrd ../rootfs.img -append  nokaslr -s -S

设置断点

gdb
file ~/LinuxKernel/linux-5.0.1/vmlinux
break sys_socketcall
target remote:1234
c

执行结果:

 

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

 

 不止一次调用了sys_socketcall

在net/socket.c中可以找到SYS_DEFINE2的定义

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;
}

可以看到实现的核心是一个switch语句,通过call参数来处理sys_socket、sys_bind等系统调用,每个case都和上面的系统调用表中的项对应。在socket中有不同的函数如Socket():创建套接字,Bind():指定本地地址,Connect():将套接字连接到目的地址等,根据传入参数的不同进行相应的处理。

 

 

 

 

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