ICode9

精准搜索请尝试: 精确搜索
首页 > 其他分享> 文章详细

Socket与系统调用深度分析

2019-12-19 18:00:53  阅读:238  来源: 互联网

标签:__ break 调用 Socket err a1 a0 user 深度


什么是系统调用?

  计算机系统的各种硬件资源是有限的,在现代多任务操作系统上同时运行的多个进程都需要访问这些资源,为了更好的管理这些资源进程是不允许直接操作的,所有对这些资源的访问都必须有操作系统控制。也就是说操作系统是使用这些资源的唯一入口,而这个入口就是操作系统提供的系统调用(System Call)。在linux中系统调用是用户空间访问内核的唯一手段,除异常和陷入外,他们是内核唯一的合法入口。

  一般情况下应用程序通过应用编程接口API,而不是直接通过系统调用来编程。在Unix世界,最流行的API是基于POSIX标准的。

  操作系统一般是通过中断从用户态切换到内核态。中断就是一个硬件或软件请求,要求CPU暂停当前的工作,去处理更重要的事情。比如,在x86机器上可以通过int指令进行软件中断,而在磁盘完成读写操作后会向CPU发起硬件中断。

  中断有两个重要的属性,中断号和中断处理程序。中断号用来标识不同的中断,不同的中断具有不同的中断处理程序。在操作系统内核中维护着一个中断向量表(Interrupt Vector Table),这个数组存储了所有中断处理程序的地址,而中断号就是相应中断在中断向量表中的偏移量。

  一般地,系统调用都是通过软件中断实现的,x86系统上的软件中断由int $0x80指令产生,而128号异常处理程序就是系统调用处理程序system_call(),它与硬件体系有关,在entry.S中用汇编写。接下来就来看一下Linux下系统调用具体的实现过程。

什么是socket?

套接字(socket)是一个抽象层,应用程序可以通过它发送或接收数据,可对其进行像对文件一样的打开、读写和关闭等操作。套接字允许应用程序将I/O插入到网络中,并与网络中的其他应用程序进行通信。网络套接字是IP地址与端口的组合。

  套接字可以看成是两个网络应用程序进行通信时,各自通信连接中的一个端点。通信时,其中的一个网络应用程序将要传输的一段信息写入它所在主机的Socket中,该Socket通过网络接口卡的传输介质将这段信息发送给另一台主机的Socket中,使这段信息能传送到其他程序中。因此,两个应用程序之间的数据传输要通过套接字来完成。工作流程如下图所示:

  • Socket():创建套接字。
  • Bind():指定本地地址。在某个知名端口(Well-known Port)上操作的服务器进程必须要对系统指定本地端口,所以一旦创建了一个套接字,服务器就必须使用bind()系统调用为套接字建立一个本地地址。
  • Connect():将套接字连接到目的地址。客户机可以调用connect()为套接字绑定一个永久的目的地址,将它置于已连接状态。对数据流方式的套接字,必须在传输数据前,调用connect()构造一个与目的地的TCP连接,并在不能构造连接时返回一个差错代码。
  • Listen():设置等待连接状态。对于一个服务器的程序,当申请到套接字,并调用bind()与本地地址绑定后,就应该等待某个客户机的程序来要求连接。listen()就是把一个套接字设置为这种状态的函数。
  • Accept():接受连接请求。服务器进程使用系统调用socket,bind和listen创建一个套接字,将它绑定到知名的端口,并指定连接请求的队列长度。然后,服务器调用Accept进入等待状态,直到到达一个连接请求。
  • Send()/Receive():发送和接收数据 。在数据流方式中,一个连接建立以后,或者在数据报方式下,调用了Connect()进行了套接字与目的地址的绑定后,就可以调用Send()和Receive()函数进行数据传输。
  • Close():关闭套接字。

实验过程

启动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.c文件,SYSCALL_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;
}

可以看出,函数有一个参数call,在函数内部会根据call的不同值来确定返回不同的处理方式,再socket中有不同的函数如Socket():创建套接字,Bind():指定本地地址,Connect():将套接字连接到目的地址等,根据传入参数的不同进行相应的处理。

标签:__,break,调用,Socket,err,a1,a0,user,深度
来源: https://www.cnblogs.com/sylg12138/p/12069232.html

本站声明: 1. iCode9 技术分享网(下文简称本站)提供的所有内容,仅供技术学习、探讨和分享;
2. 关于本站的所有留言、评论、转载及引用,纯属内容发起人的个人观点,与本站观点和立场无关;
3. 关于本站的所有言论和文字,纯属内容发起人的个人观点,与本站观点和立场无关;
4. 本站文章均是网友提供,不完全保证技术分享内容的完整性、准确性、时效性、风险性和版权归属;如您发现该文章侵犯了您的权益,可联系我们第一时间进行删除;
5. 本站为非盈利性的个人网站,所有内容不会用来进行牟利,也不会利用任何形式的广告来间接获益,纯粹是为了广大技术爱好者提供技术内容和技术思想的分享性交流网站。

专注分享技术,共同学习,共同进步。侵权联系[81616952@qq.com]

Copyright (C)ICode9.com, All Rights Reserved.

ICode9版权所有