网络编程常用接口的内核实现

bind()系统调用是给套接字分配一个本地协议地址,对于网际协议,协议地址是32位IPv4地址或128位IPv6地址与16位的TCP或UDP端口号的组合。如果没有通过bind()来指定本地的协议地址,在和远端通信时,内核会随机给套接字分配一个IP地址和端口号。bind()系统调用通常是在网络程序的服务器端调用,而且是必须的。如果TCP服务器不这么做,让内核来选择临时端口号而不是捆绑众所周知的端口,客户端如何发起与服务器的连接?

一、sys_bind()

bind()系统调用对应的内核实现是sys_bind(),其源码及分析如下:

/*
 * Bind a name to a socket. Nothing much to do here since it's
 * the protocol's responsibility to handle the local address.
 *
 * We move the socket address to kernel space before we call
 * the protocol layer (having also checked the address is ok).
 */

SYSCALL_DEFINE3(bind, int, fd, struct sockaddr __user *, umyaddr, int, addrlen)
{
 struct socket *sock;
 struct sockaddr_storage address;
 int err, fput_needed;

/*
  * 以fd为索引从当前进程的文件描述符表中
  * 找到对应的file实例,然后从file实例的private_data中
  * 获取socket实例。
  */
 sock = sockfd_lookup_light(fd, &err, &fput_needed);
 if (sock) {
  /*
  * 将用户空间的地址拷贝到内核空间的缓冲区中。
  */
  err = move_addr_to_kernel(umyaddr, addrlen, (struct sockaddr *)&address);
  if (err >= 0) {
   /*
    * SELinux相关,不需要关心。
    */
   err = security_socket_bind(sock,
        (struct sockaddr *)&address,
        addrlen);
   /*
    * 如果是TCP套接字,sock->ops指向的是inet_stream_ops,
    * sock->ops是在inet_create()函数中初始化,所以bind接口
    * 调用的是inet_bind()函数。
    */
   if (!err)
    err = sock->ops->bind(sock,
            (struct sockaddr *)
            &address, addrlen);
  }
  fput_light(sock->file, fput_needed);
 }
 return err;
}

sys_bind()的代码流程如下图所示:

网络编程常用接口的内核实现

sys_bind()首先调用sockfd_lookup_light()查找套接字对应的socket实例,如果没有找到,则返回EBADF错误。在进行绑定操作之前,要先将用户传入的本地协议地址从用户空间拷贝到内核缓冲区中,在拷贝过程中会检查用户传入的地址是否正确。如果指定的长度参数小于0或者大于sockaddr_storage的大小,则返回EINVAL错误;如果在调用copy_from_user()执行拷贝操作过程中出现错误,则返回EFAULT错误。在上述的准备工作都完成后,调用inet_bind()函数(即sock->ops->bind指向的函数,参见注释)来完成绑定操作。

内容版权声明:除非注明,否则皆为本站原创文章。

转载注明出处:http://www.heiqu.com/d6f9d4ad7e72a86cf03158099b535f05.html