当创建了一个Socket 套接字后,对于服务器来说,接下来的工作,就是调用 bind(2)为服务器指明本地址、协议端口号,常常可以看到这样的代码:strut sockaddr_in sin;  sin.sin_family = AF_INET; sin.sin_addr.s_addr = xxx; sin.sin_port = xxx; bind(sock, (struct sockaddr *)&sin, sizeof(sin)); 从这个系统调用中可以知道当进行 SYS_BIND 操作的时候: 1. 对于 AF_INET 协议簇来讲,其地址格式是 strut sockaddr_in,而对于 socket 来讲,strut sockaddr结构表示的地址格式实现了更高层次的抽像,因为每种协议长簇的地址不一定是相同的,所以系统调用的第三个参数得指明该协议簇的地址格式的长度2. 进行 bind(2)系统调用时,除了地址长度外,还得向内核提供:sock 描述符、协议簇名称、本地地址、端口这些参数sys_bind的实现操作 SYS_BIND  是由 sys_bind()实现的1335 asmlinkage long sys_bind(int fd, struct sockaddr __user *umyaddr, int addrlen)1336 {1337     struct socket *sock;1338     char address[MAX_SOCK_ADDR];1339     int err, fput_needed;1340 1341     if((sock = sockfd_lookup_light(fd, &err, &fput_needed))!=NULL)1342     {1343         if((err=move_addr_to_kernel(umyaddr,addrlen,address))>=0) {1344             err = security_socket_bind(sock, (struct sockaddr *)address, addrlen);1345             if (!err)1346                 err = sock->ops->bind(sock,1347                     (struct sockaddr *)address, addrlen);1348         }1349         fput_light(sock->file, fput_needed);1350     }           1351     return err;1352 }在socket 的创建中已经反复分析了 socket 与文件系统的关系,现在已知socket的描述符号,要找出与之相关的socket结构应该是件容易的事情 490 static struct socket *sockfd_lookup_light(int fd, int *err, int *fput_needed)                                            491 { 492     struct file *file; 493     struct socket *sock; 494  495     *err = -EBADF; 496     file = fget_light(fd, fput_needed); 497     if (file) { 498         sock = sock_from_file(file, err); 499         if (sock) 500             return sock; 501         fput_light(file, *fput_needed); 502     } 503     return NULL; 504 }fget 从当前进程的 files 指针中,根据 sock 对应的描述符号,找到已打开的文件 file,再根据文件的目录项中的 inode,利用inode 与 sock 被封装在同一个结构中的事实,调用宏 SOCKET_I找到待查的 sock 结构。最后做一个小小的判断,因为正常情况下,sock 的 file 指针是 回指与之相关的 file.接下来的工作是把用户态的地址拷贝至内核中来 228 int move_addr_to_kernel(void __user *uaddr, int  229 { 230     if( 231         return -EINVAL; 232     if( 233         return 0; 234     if(copy_from_user(kaddr,uaddr, 235         return -EFAULT; 236     return audit_sockaddr( 237 }bind(2)第三个参数必须存在的原因之一,copy_from_user 必须知道拷贝的字节长度因为 sock 的 ops 函数指针集,在创建之初,就指向了对应的协议类型,例如如果类型是SOCK_STREAM,那么它就指向 inetsw_array[0].ops。也就是 inet_stream_ops: struct proto_ops inet_stream_ops = {         .family = PF_INET,         .bind = inet_bind,         …… }; sys_bind()在做完了一个通用的 socket bind 应该做的事情,包括查找对应 sock 结构,拷贝地址。就调用对应协议族的对应协议类型的 bind 函数,也就是 inet_bind.inet_bind说 bind(2)的最重要的作用就是为套接字绑定地址和端口,那么要分析inet_bind()之前,要搞清楚的一件事情就是,这个绑定是绑定到哪儿?或者说是绑定到内核的哪个数据结构的哪个成员变量上面?有三个地方是可以考虑的:socket 结构,包括 sock 和 sk,inet结构,以及 protoname_sock 结构。绑定在 socket 结构上是可行的,这样可以实现最高层面上的抽像,但是因为每一类协议簇 socket 的地址及端口表现形式差异很大,这样就得引入专门的转换处理功能。绑定在 protoname_sock 也是可行的,但是却是最笨拙的,因为例如 tcp 和 udp,它们的地址及端口表现形式是一样的,这样就浪费了空间加大了代码处理量。所以inet 做为一个协议类型的抽像是最理想的地方了,再来回顾一下它的定义108 struct inet_sock {         114     /* Socket demultiplex comparisons on incoming packets. */                                                            115     __u32           daddr; 116     __u32           rcv_saddr;117     __u16           dport; 118     __u16           num;   119     __u32           saddr; 去掉了其它成员保留了与地址及端口相关的成员变量,从注释中可以清楚地了解它们的作用。所以我们说的 bind(2)之绑定主要就是对这几个成员变量赋值的过程了. 397 int inet_bind(struct socket *sock, struct sockaddr *uaddr, int addr_len) 398 {            /*  获取地址参数 */  399     struct sockaddr_in *addr = (struct sockaddr_in *)uaddr;            /*  获取 sock 对应的 sk */  400     struct sock *sk = sock->sk;            /*  获取 sk 对应的inet */  401     struct inet_sock *inet = inet_sk(sk);            /*  这个临时变量用来保存用户态传递下来的端口参数 */  402     unsigned short snum; 403     int chk_addr_ret; 404     int err; 405             /*  如果协议簇对应的协议自身还有bind函数调用之,例如 SOCK_RAW 就还有一个raw_bind */  406     /* If the socket has its own bind function then use it. (RAW) */ 407     if (sk->sk_prot->bind) { 408         err = sk->sk_prot->bind(sk, uaddr, addr_len); 409         goto out; 410     } 411     err = -EINVAL;            /*  校验地址长度 */  412     if (addr_len  413         goto out; 414     /*  判断地址类型:广播?多播?单播? */  415     chk_addr_ret = inet_addr_type(addr->sin_addr.s_addr); 416             /*  ipv4 有一个 ip_nonlocal_bind标志,表示是否绑定非本地址 IP地址,可以通过               *  cat /proc/sys/net/ipv4/ip_nonlocal_bind查看到。             *  它用来解决某些服务绑定动态 IP地址的情况。作者在注释中已有详细说明.             *  这里判断,用来确认如果没有开启“绑定非本地址 IP”,地址值及类型是正确的  417     /* Not specified by any standard per-se, however it breaks too 418      * many applications when removed.  It is unfortunate since 419      * allowing applications to make a non-local bind solves 420      * several problems with systems using dynamic addressing. 421      * (ie. your servers still start up even if your ISDN link 422      *  is temporarily down) 423      */ 424     err = -EADDRNOTAVAIL; 425     if (!sysctl_ip_nonlocal_bind && 426         !inet->freebind && 427         addr->sin_addr.s_addr != INADDR_ANY && 428         chk_addr_ret != RTN_LOCAL && 429         chk_addr_ret != RTN_MULTICAST && 430         chk_addr_ret != RTN_BROADCAST) 431         goto out;            /* 获取协议端口号 */  433     snum = ntohs(addr->sin_port); 434     err = -EACCES;            /*  校验当前进程有没有使用低于 1024 端口的能力  */  435     if (snum && snum  436         goto out; 437  438     /*      We keep a pair of addresses. rcv_saddr is the one 439      *      used by hash lookups, and saddr is used for transmit. 440      * 441      *      In the BSD API these are the same except where it 442      *      would be illegal to use them (multicast/broadcast) in 443      *      which case the sending device address is used. 444      */ 445     lock_sock(sk); 446  447     /* Check these errors (active socket, double bind). */ 448     err = -EINVAL;            /*  检查socket是否已经被绑定过了: 用了两个检查项, 一个是 sk 状态, 另一个是是否已经绑定过端口了                   当然地址本来就可以为0,所以不能做为检查项  */  449     if (sk->sk_state != TCP_CLOSE || inet->num) 450         goto out_release_sock; 451     /*  绑定inet的接收地址(地址服务绑定地址)和来源地址为用户态指定地址 */  452     inet->rcv_saddr = inet->saddr = addr->sin_addr.s_addr;            /*  若地址类型为广播或多播,则将地址置 0,表示直接使用网络设备 */  453     if (chk_addr_ret == RTN_MULTICAST || chk_addr_ret == RTN_BROADCAST) 454         inet->saddr = 0;  /* Use device */  455             /*  调用协议的 get_port 函数,确认是否可绑定端口.             *  若可以, 则绑定在 inet->num 之上, 注意这里虽然没有把inet传过去,但是第一个参数sk             *  它本身和 inet是可以互相转化的 */  456     /* Make sure we are allowed to bind here. */ 457     if (sk->sk_prot->get_port(sk, snum)) { 458         inet->saddr = inet->rcv_saddr = 0; 459         err = -EADDRINUSE; 460         goto out_release_sock; 461     } 462      /*  如果端口和地址可以绑定,置标志位 */  463     if (inet->rcv_saddr) 464         sk->sk_userlocks |= SOCK_BINDADDR_LOCK; 465     if (snum) 466         sk->sk_userlocks |= SOCK_BINDPORT_LOCK;            /* inet的 sport(来源端口)成员也置为绑定端口 */  467     inet->sport = htons(inet->num); 468     inet->daddr = 0; 469     inet->dport = 0;    470     sk_dst_reset(sk); 471     err = 0; 472 out_release_sock: 473     release_sock(sk); 474 out: 475     return err; 476 }上述分析中忽略的第一个细节是capable()函数调用,它是 Linux 安全模块(LSM)的一部份简单地讲其用来对权限做出检查检查是否有权对指定的资源进行操作。这里它的参数是CAP_NET_BIND_SERVICE表示的含义是:  /* Allows binding to TCP/UDP sockets below 1024 */ /* Allows binding to ATM VCIs below 32 */ #define CAP_NET_BIND_SERVICE 10[/code]  另一个就是协议的端口绑定,调用了协议的get_port函数,如果是SOCK_STREAM的TCP协议,那么它就是tcp_v4_get_port()函数.协议端口的绑定 要分析这个函数还是得先绕一些基本的东东,这里涉及到内核中提供hash链表的操作的API。可以参考其它相关资料。 http://www.ibm.com/developerworks/cn/linux/kernel/l-chain/index.html这里讲了链表的实现,顺道提了一个 hash 链表,觉得写得还不错,收藏一下。  对于 TCP已注册的端口,是采用一个 hash 表来维护的。hash 桶用 struct tcp_bind_hashbucket 结构来表示: struct tcp_bind_hashbucket {         spinlock_t                lock;         struct hlist_head        chain; }; hash 表中的每一个 hash节点,用 struct tcp_bind_hashbucket 结构来表示: struct tcp_bind_bucket {         unsigned short           port;                        /*  节点中绑定的端口 */         signed short               fastreuse;         struct hlist_node        node;         struct hlist_head        owners; }; tcp_hashinfo 的 hash 表信息,都集中封装在结构 tcp_hashinfo 当中,而维护已注册端口只是它其中一部份:  extern struct tcp_hashinfo {         ……         /* Ok, let's try this, I give up, we do need a local binding          * TCP hash as well as the others for fast bind/connect.          */         struct tcp_bind_hashbucket *__tcp_bhash;          int __tcp_bhash_size;         …… } tcp_hashinfo;  #define tcp_bhash        (tcp_hashinfo.__tcp_bhash) #define tcp_bhash_size        (tcp_hashinfo.__tcp_bhash_size) 其使用的 hash 函数是 tcp_bhashfn: /* These are AF independent. */ static __inline__ int tcp_bhashfn(__u16 lport) {         return (lport & (tcp_bhash_size - 1)); } 这样,如果要取得某个端口对应的 hash 链的首部hash 桶节点的话,可以使用: struct tcp_bind_hashbucket *head; head = &tcp_bhash[tcp_bhashfn(snum)]; 如果要新绑定一个端口就是先创建一个 struct tcp_bind_hashbucket 结构的 hash 节点,然后把它插入到对应的 hash 链中去: struct tcp_bind_bucket *tb; tb = tcp_bucket_create(head, snum);  struct tcp_bind_bucket *tcp_bucket_create(struct tcp_bind_hashbucket *head,                                           unsigned short snum) {         struct tcp_bind_bucket *tb = kmem_cache_alloc(tcp_bucket_cachep,                                                       SLAB_ATOMIC);         if (tb) {                 tb->port = snum;                 tb->fastreuse = 0;                 INIT_HLIST_HEAD(&tb->owners);                 hlist_add_head(&tb->node, &head->chain);         }         return tb; }另外sk 中还维护了一个类似的 hash 链表,同时需要调用 tcp_bind_hash()函数把 hash 节点插入进去: struct sock {         struct sock_common        __sk_common; #define sk_bind_node              __sk_common.skc_bind_node         …… } /* @skc_bind_node: bind hash linkage for various protocol lookup tables */ struct sock_common {         struct hlist_node        skc_bind_node;         …… }         if (!tcp_sk(sk)->bind_hash)         tcp_bind_hash(sk, tb, snum);          void tcp_bind_hash(struct sock *sk, struct tcp_bind_bucket *tb,                    unsigned short snum) {         inet_sk(sk)->num = snum;         sk_add_bind_node(sk, &tb->owners);         tcp_sk(sk)->bind_hash = tb; } 这里就顺道绑定了 inet 的 num成员变量,并置协议的 bind_hash 指针为当前分配的 hash 节点。而sk_add_bind_node 函数,就是一个插入 hash 表节点的过程:  static __inline__ void sk_add_bind_node(struct sock *sk,                                         struct hlist_head *list) {         hlist_add_head(&sk->sk_bind_node, list); } 如果要遍历 hash 表的话,例如在插入之前,先判断端口是否已经在 hash表当中了。就可以调用:  #define tb_for_each(tb, node, head) hlist_for_each_entry(tb, node, head, node)  struct tcp_bind_hashbucket *head; struct tcp_bind_bucket *tb;  head = &tcp_bhash[tcp_bhashfn(snum)]; spin_lock(&head->lock); tb_for_each(tb, node, &head->chain)         if (tb->port == snum)                 found,do_something;               有了这些基础知识,再来看 tcp_v4_get_port()的实现,就要容易得多了:  static int tcp_v4_get_port(struct sock *sk, unsigned short snum) {         struct tcp_bind_hashbucket *head;         struct hlist_node *node;         struct tcp_bind_bucket *tb;         int ret;          local_bh_disable();                  /*  如果端口值为 0,意味着让系统从本地可用端口用选择一个,并置 snum为分配的值 */         if (!snum) {                 int low = sysctl_local_port_range[0];                 int high = sysctl_local_port_range[1];                 int remaining = (high - low) + 1;                 int rover;                  spin_lock(&tcp_portalloc_lock);                 if (tcp_port_rover                         rover = low;                 else                         rover = tcp_port_rover;                 do {                         rover++;                         if (rover > high)                                 rover = low;                          head = &tcp_bhash[tcp_bhashfn(rover)];                         spin_lock(&head->lock);                         tb_for_each(tb, node, &head->chain)                                 if (tb->port == rover)                                         goto next;                         break;                 next:                         spin_unlock(&head->lock);                 } while (--remaining > 0);                 tcp_port_rover = rover;                 spin_unlock(&tcp_portalloc_lock);                  /* Exhausted local port range during search? */                 ret = 1;                 if (remaining                         goto fail;                  /* OK, here is the one we will use.  HEAD is                  * non-NULL and we hold it's mutex.                  */                 snum = rover;         } else {                 /*  否则,就在 hash 表中,查找端口是否已经存在 */                 head = &tcp_bhash[tcp_bhashfn(snum)];                 spin_lock(&head->lock);                 tb_for_each(tb, node, &head->chain)                         if (tb->port == snum)                                 goto tb_found;         }         tb = NULL;         goto tb_not_found; tb_found:         /*  稍后有对应的代码:         *  第一次分配 tb 后,会调用 tcp_bind_hash加入至相应的 sk,这里先做一个判断,         *  来确定这一步工作是否进行过 */         if (!hlist_empty(&tb->owners)) {         /* socket的SO_REUSEADDR选项,用来确定是否允许本地地址重用,例如同时启动多个服务器、多个套接字         * 绑定至同一端口等等,sk_reuse 成员对应其值,因为如果一个绑定的 hash节点已经存在,而且不允许重用的话,         * 那么则表示因冲突导致出错,调用 tcp_bind_conflict 来处理之 */                 if (sk->sk_reuse > 1)                         goto success;                 if (tb->fastreuse > 0 &&                     sk->sk_reuse && sk->sk_state != TCP_LISTEN) {                         goto success;                 } else {                         ret = 1;                         if (tcp_bind_conflict(sk, tb))                                 goto fail_unlock;                 }         } tb_not_found:         /*  如果不存在,则分配 hash节点,绑定端口 */         ret = 1;         if (!tb && (tb = tcp_bucket_create(head, snum)) == NULL)                 goto fail_unlock;         if (hlist_empty(&tb->owners)) {                 if (sk->sk_reuse && sk->sk_state != TCP_LISTEN)                         tb->fastreuse = 1;                 else                         tb->fastreuse = 0;         } else if (tb->fastreuse && (!sk->sk_reuse || sk->sk_state == TCP_LISTEN))                 tb->fastreuse = 0; success:         if (!tcp_sk(sk)->bind_hash)                 tcp_bind_hash(sk, tb, snum);         BUG_TRAP(tcp_sk(sk)->bind_hash == tb);         ret = 0;  fail_unlock:         spin_unlock(&head->lock); fail:         local_bh_enable();         return ret; } 到这里,可以为这部份下一个小结了,所谓绑定,就是: 1. 设置内核中 inet 相关变量成员的值,以待后用; 2. 协议中,如TCP协议,记录绑定的协议端口的信息,采用 hash 链表存储,sk 中也同时维护了这么一个链表。    两者的区别应该是前者给协议用, 后者给socket 用。
01-04 04:17