bind()系統調用是給套接字分配一個本地協議地址,對於網際協議,協議地址是32位IPv4地址或128位IPv6地址與16位的TCP或UDP端口號的組合。如果沒有通過bind()來指定本地的協議地址,在和遠端通信時,內核會隨機給套接字分配一個IP地址和端口號。bind()系統調用通常是在網絡程序的服務器端調用,而且是必須的。如果TCP服務器不這麼做,讓內核來選擇臨時端口號而不是捆綁眾所周知的端口,客戶端如何發起與服務器的連接? 一、sys_bind() bind()系統調用對應的內核實現是sys_bind(),其源碼及分析如下: [cpp] /* * 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實例。 */ www.2cto.com 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指向的函數,參見注釋)來完成綁定操作。 二、inet_bind() inet_bind()比較簡單,不做過多的分析,注釋的已經很清楚了。代碼及注釋如下所示: [cpp] int inet_bind(struct socket *sock, struct sockaddr *uaddr, int addr_len) { struct sockaddr_in *addr = (struct sockaddr_in *)uaddr; struct sock *sk = sock->sk; struct inet_sock *inet = inet_sk(sk); unsigned short snum; int chk_addr_ret; int err; /* If the socket has its own bind function then use it. (RAW) */ /* * 如果是TCP套接字,sk->sk_prot指向的是tcp_prot,在 * inet_create()中調用的sk_alloc()函數中初始化。由於 * tcp_prot中沒有設置bind接口,因此判斷條件不成立。 */ if (sk->sk_prot->bind) { err = sk->sk_prot->bind(sk, uaddr, addr_len); goto out; } err = -EINVAL; if (addr_len < sizeof(struct sockaddr_in)) goto out; /* * 判斷傳入的地址類型。 */ chk_addr_ret = inet_addr_type(sock_net(sk), addr->sin_addr.s_addr); /* Not specified by any standard per-se, however it breaks too * many applications when removed. It is unfortunate since * allowing applications to make a non-local bind solves * several problems with systems using dynamic addressing. * (ie. your servers still start up even if your ISDN link * is temporarily down) */ err = -EADDRNOTAVAIL; /* * 如果系統不支持綁定本地地址,或者 * 傳入的地址類型有誤,則返回EADDRNOTAVAIL * 錯誤。 */ if (!sysctl_ip_nonlocal_bind && !(inet->freebind || inet->transparent) && addr->sin_addr.s_addr != htonl(INADDR_ANY) && chk_addr_ret != RTN_LOCAL && chk_addr_ret != RTN_MULTICAST && chk_addr_ret != RTN_BROADCAST) goto out; snum = ntohs(addr->sin_port); err = -EACCES; /* * 如果綁定的端口號小於1024(保留端口號),但是 * 當前用戶沒有CAP_NET_BIND_SERVICE權限,則返回EACCESS錯誤。 */ if (snum && snum < PROT_SOCK && !capable(CAP_NET_BIND_SERVICE)) goto out; /* We keep a pair of addresses. rcv_saddr is the one * used by hash lookups, and saddr is used for transmit. * * In the BSD API these are the same except where it * would be illegal to use them (multicast/broadcast) in * which case the sending device address is used. */ lock_sock(sk); /* Check these errors (active socket, double bind). */ err = -EINVAL; /* * 如果套接字狀態不是TCP_CLOSE(套接字的初始狀態,參見 * sock_init_data()函數),或者已經綁定過,則返回EINVAL錯誤。 */ if (sk->sk_state != TCP_CLOSE || inet->num) goto out_release_sock; inet->rcv_saddr = inet->saddr = addr->sin_addr.s_addr; if (chk_addr_ret == RTN_MULTICAST || chk_addr_ret == RTN_BROADCAST) inet->saddr = 0; /* Use device */ /* Make sure we are allowed to bind here. */ /* * 這裡實際調用的是inet_csk_get_port()函數。 * 檢查要綁定的端口號是否已經使用,如果已經使用, * 則檢查是否允許復用。如果檢查失敗,則返回 * EADDRINUSE錯誤。 */ if (sk->sk_prot->get_port(sk, snum)) { inet->saddr = inet->rcv_saddr = 0; err = -EADDRINUSE; goto out_release_sock; } /* * rcv_saddr存儲的是已綁定的本地地址,接收數據時使用。 * 如果已綁定的地址不為0,則設置SOCK_BINDADDR_LOCK標志, * 表示已綁定本地地址。 */ if (inet->rcv_saddr) sk->sk_userlocks |= SOCK_BINDADDR_LOCK; /* * 如果綁定的端口號不為0,則設置SOCK_BINDPORT_LOCK標志, * 表示已綁定本地端口號。 */ if (snum) sk->sk_userlocks |= SOCK_BINDPORT_LOCK; inet->sport = htons(inet->num); inet->daddr = 0; inet->dport = 0; /* * 重新初始化目的路由緩存項,如果之前已設置,則 * 調用dst_release()釋放老的路由緩存項。 */ sk_dst_reset(sk); err = 0; out_release_sock: release_sock(sk); out: return err; }