GNU是通过调用Linux的系统调用,进入内核空间,开始使用内核提供的代码来处理网络通信问题的。

    正如本章开篇介绍的,编写套接字接口的头函数是GNU的标准头文件,而这些具体的函数是在glibc 的源代码中sysdeps/unix/sysv/linux/i386/socket.S 用汇编实现的,用来从用户空间进入名为socketcall的系统调用,并传递参数,下面是相关汇编代码:
 --------------------------------------------------------------------
.globl __socket
ENTRY (__socket)
#if defined NEED_CANCELLATION && defined CENABLE
 SINGLE_THREAD_P
 jne 1f
#endif
 /* Save registers.  */
 movl %ebx, %edx
 cfi_register (3, 2)
 movl $SYS_ify(socketcall), %eax /* System call number in %eax.  */
 /* Use ## so `socket' is a separate token that might be #define'd.  */
 movl $P(SOCKOP_,socket), %ebx /* Subcode is first arg to syscall.  */
 lea 4(%esp), %ecx  /* Address of args is 2nd arg.  */
   /* Do the system call trap.  */
 ENTER_KERNEL
 /* Restore registers.  */
 movl %edx, %ebx
 cfi_restore (3)
 /* %eax is < 0 if there was an error.  */
 cmpl $-125, %eax
 jae SYSCALL_ERROR_LABEL
 /* Successful; return the syscall's value.  */
L(pseudo_end):
 ret    
           代码段 4.1 从用户空间进入内核空间的汇编代码
 -------------------------------------------------- 

    这段汇编代码是glibc用来进入Linux内核访问名为socketcall系统调用主要汇编程序,采用了Linux对x86体系常用的汇编格式,也就是AT&T格式。而socketcall对应的代码在net/socket.c文件中,而net/socket.c已经是Linux源代码的一部分了,也就是说:此时glibc已经将相关的操作传递给了内核,也就是Linux系统,代码段4.2是socketcall的源代码,位于/net/socket.c中。这就完成了用户空间到内核空间的转化。
 
----------------------------------------------------------------------------
asmlinkage long sys_socketcall(int call, unsigned long __user *args)
{
 unsigned long a[6];
 unsigned long a0,a1;
 int err;
 if(call<1||call>SYS_RECVMSG)
  return -EINVAL;
 /* 此函数要确保 SMP 安全. */
 if (copy_from_user(a, args, nargs[call]))
  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_accept(a0,(struct sockaddr __user *)a1, (int __user *)a[2]);
   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_send(a0, (void __user *)a1, a[2], a[3]);
   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_recv(a0, (void __user *)a1, a[2], a[3]);
   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 msghdr __user *) a1, a[2]);
   break;
  case SYS_RECVMSG:
   err = sys_recvmsg(a0, (struct msghdr __user *) a1, a[2]);
   break;
  default:
   err = -EINVAL;
   break;
 }
 return err;
}    
  
        代码段4.2 socketcall 源代码 [---/net/socket.c]
-------------------------------------------------------------------

    此段代码非常简明,很容易看出从用户空间到内核空间的对应关系。当用户使用标准的GNU socket编程,用socket()函数得到一个socket描述符的时候,GNU会调用Linux的socketcall系统调用中的sys_socket(a0,a1,a[2]),其原型是asmlinkage long sys_socket(int, int, int);代码
-------------------------------------------------------------------
asmlinkage  long  sys_socket(int family, int type, int protocol)
{
 int retval;
 struct socket *sock;
    /*创建一个socket*/
 retval = sock_create(family, type, protocol, &sock);
 if (retval < 0)
  goto out;
    /*将socket和套接字描述符对应*/
 retval = sock_map_fd(sock);
 if (retval < 0)
  goto out_release;
out:
 /* 出错原因可能是已经存在此描述符*/
 return retval;
out_release:
 sock_release(sock);
 return retval;
}

   代码段4.3 socket()对应的系统调用sys_socket源代码 [---/net/socket.c]
 -------------------------------------------------- ---------------------
    从代码4.3中可以看出用户空间申请一个socket,通过系统调用提交到内核,Linux为此而做的事情:
1. 用sock_create() 创建一个socket,得到socket描述符。
2. 用sock_map_fd() 将此socket和得到的socket描述符关联。
    用户空间会得到这个描述符,而Linux此时也可以轻松管理socket了。根据这样的思路,对照Linux源代码,可以继续学习其他的用户空间函数如何对应到系统内核的源代码上去,而进入Linux的内核空间。
    但是要继续深入的理解Linux内核对网络通信的处理,那必须从整体上开始对Linux网络部分进行分析和理解。
Logo

瓜分20万奖金 获得内推名额 丰厚实物奖励 易参与易上手

更多推荐