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指向的函数,参见注释)来完成绑定操作。