tcp connection setup的實現(一)

bind的實現: 



先來介紹幾個地址結構. 

struct sockaddr 其實相當於一個基類的地址結構,其他的結構都能夠直接轉到sockaddr.舉個例子比如當sa_family爲PF_INET時,sa_data就包含了端口號和ip地址(in_addr結構). 
Java代碼  收藏代碼
  1. struct sockaddr {  
  2.     sa_family_t sa_family;  /* address family, AF_xxx   */  
  3.     char        sa_data[14];    /* 14 bytes of protocol address */  
  4. };  


接下來就是sockaddr_in ,它表示了所有的ipv4的地址結構.可以看到他也就相當於sockaddr 的一個子類. 
Java代碼  收藏代碼
  1. struct sockaddr_in {  
  2.   sa_family_t       sin_family; /* Address family       */  
  3.   __be16        sin_port;   /* Port number          */  
  4.   struct in_addr    sin_addr;   /* Internet address     */  
  5.   /* Pad to size of `struct sockaddr'. */  
  6.   unsigned char     __pad[__SOCK_SIZE__ - sizeof(short int) -  
  7.             sizeof(unsigned short int) - sizeof(struct in_addr)];  
  8. };  


這裏還有一個內核比較新的地質結構sockaddr_storage,他可以容納所有類型的套接口結構,比如ipv4,ipv6..可以看到它是強制對齊的,相比於sockaddr. 

Java代碼  收藏代碼
  1. struct __kernel_sockaddr_storage {  
  2.     unsigned short  ss_family;      /* address family */  
  3. ///每個協議實現自己的地址結構.  
  4.     char        __data[_K_SS_MAXSIZE - sizeof(unsigned short)];  
  5.                 /* space to achieve desired size, */  
  6.                 /* _SS_MAXSIZE value minus size of ss_family */  
  7. } __attribute__ ((aligned(_K_SS_ALIGNSIZE)));   /* force desired alignment */  


接下來看幾個和bind相關的數據結構: 

第一個是inet_hashinfo,它主要用來管理 tcp的bind hash bucket(在tcp的初始化函數中會將tcp_hashinfo初始化.然後在tcp_prot中會將tcp_hashinfo付給結構體h,然後相應的我們就可以通過sock中的sock_common域來存取這個值).後面我們會分析這個流程. 

Java代碼  收藏代碼
  1. struct inet_hashinfo {  
  2.     /* This is for sockets with full identity only.  Sockets here will 
  3.      * always be without wildcards and will have the following invariant: 
  4.      * 
  5.      *          TCP_ESTABLISHED <= sk->sk_state < TCP_CLOSE 
  6.      * 
  7.      * TIME_WAIT sockets use a separate chain (twchain). 
  8.      */  
  9. ///下面會分析這個結構.  
  10.     struct inet_ehash_bucket    *ehash;  
  11.     rwlock_t            *ehash_locks;  
  12.     unsigned int            ehash_size;  
  13.     unsigned int            ehash_locks_mask;  
  14.   
  15.     /* Ok, let's try this, I give up, we do need a local binding 
  16.      * TCP hash as well as the others for fast bind/connect. 
  17.      */  
  18. ///表示所有的已經在使用的端口號的信息.這裏bhash也就是一個hash鏈表,而鏈表的元素是inet_bind_bucket,緊接着我們會分析這個結構.  
  19.     struct inet_bind_hashbucket *bhash;  
  20.   
  21.     unsigned int            bhash_size;  
  22.     /* Note : 4 bytes padding on 64 bit arches */  
  23.   
  24.     /* All sockets in TCP_LISTEN state will be in here.  This is the only 
  25.      * table where wildcard'd TCP sockets can exist.  Hash function here 
  26.      * is just local port number. 
  27.      */  
  28. ///listening_hash表示所有的處於listen狀態的socket.  
  29.     struct hlist_head       listening_hash[INET_LHTABLE_SIZE];  
  30.   
  31.     /* All the above members are written once at bootup and 
  32.      * never written again _or_ are predominantly read-access. 
  33.      * 
  34.      * Now align to a new cache line as all the following members 
  35.      * are often dirty. 
  36.      */  
  37.     rwlock_t            lhash_lock ____cacheline_aligned;  
  38.     atomic_t            lhash_users;  
  39.     wait_queue_head_t       lhash_wait;  
  40.     struct kmem_cache           *bind_bucket_cachep;  
  41. };  


struct inet_ehash_bucket管理所有的tcp狀態在TCP_ESTABLISHED和TCP_CLOSE之間的socket.這裏要注意,twchain表示處於TIME_WAIT的socket. 

Java代碼  收藏代碼
  1. struct inet_ehash_bucket {  
  2.     struct hlist_head chain;  
  3.     struct hlist_head twchain;  
  4. };  



inet_bind_bucket結構就是每個使用的端口的信息,最終會把它鏈接到bhash鏈表中. 

Java代碼  收藏代碼
  1. struct inet_bind_bucket {  
  2.     struct net      *ib_net;  
  3. ///端口號  
  4.     unsigned short      port;  
  5. ///表示這個端口是否能夠被重複使用.  
  6.     signed short        fastreuse;  
  7. ///指向下一個端口的inet_bind_bucket 結構.  
  8.     struct hlist_node   node;  
  9. ///也就是使用這個端口的socket鏈表  
  10.     struct hlist_head   owners;  
  11. };  


最後一個結構是tcp_hashinfo他在 tcp_init中被初始化,而tcp_init是在inet_init中被初始化的.然後tcp_hashinfo會被賦值給tcp_proto和sock的sk_prot域. 

Java代碼  收藏代碼
  1. struct inet_hashinfo __cacheline_aligned tcp_hashinfo = {  
  2.     .lhash_lock  = __RW_LOCK_UNLOCKED(tcp_hashinfo.lhash_lock),  
  3.     .lhash_users = ATOMIC_INIT(0),  
  4.     .lhash_wait  = __WAIT_QUEUE_HEAD_INITIALIZER(tcp_hashinfo.lhash_wait),  
  5. };  


然後來看bind的實現,bind對應的系統調用是sys_bind: 

Java代碼  收藏代碼
  1. asmlinkage long sys_bind(int fd, struct sockaddr __user *umyaddr, int addrlen)  
  2. {  
  3.     struct socket *sock;  
  4.     struct sockaddr_storage address;  
  5.     int err, fput_needed;  
  6.   
  7. ///通過fd查找相應的socket,如果不存在則返回錯誤.  
  8.     sock = sockfd_lookup_light(fd, &err, &fput_needed);  
  9.     if (sock) {  
  10. ///用戶空間和內核的地址拷貝.  
  11.         err = move_addr_to_kernel(umyaddr, addrlen, (struct sockaddr *)&address);  
  12.         if (err >= 0) {  
  13.             err = security_socket_bind(sock,  
  14.                            (struct sockaddr *)&address,  
  15.                            addrlen);  
  16.             if (!err)  
  17. ///調用inet_bind方法.  
  18.                 err = sock->ops->bind(sock,  
  19.                               (struct sockaddr *)  
  20.                               &address, addrlen);  
  21.         }  
  22. ///將socket對應的file結構的引用計數.  
  23.         fput_light(sock->file, fput_needed);  
  24.     }  
  25.     return err;  
  26. }  


sockfd_lookup_light主要是查找fd對應的socket 

Java代碼  收藏代碼
  1. static struct socket *sockfd_lookup_light(int fd, int *err, int *fput_needed)  
  2. {  
  3.     struct file *file;  
  4.     struct socket *sock;  
  5.   
  6.     *err = -EBADF;  
  7. ///通過fd得到對應的file結構  
  8.     file = fget_light(fd, fput_needed);  
  9.     if (file) {  
  10. ///我們在sock_map_fd通過sock_attach_fd中已經把file的private域賦值爲socket,因此這裏就直接返回socket.  
  11.         sock = sock_from_file(file, err);  
  12.         if (sock)  
  13.             return sock;  
  14.         fput_light(file, *fput_needed);  
  15.     }  
  16.     return NULL;  
  17. }  


然後來看inet_bind的實現. 
Java代碼  收藏代碼
  1. int inet_bind(struct socket *sock, struct sockaddr *uaddr, int addr_len)  
  2. {  
  3. ///取得綁定地址.以及相關的socket和inet_sock.  
  4.     struct sockaddr_in *addr = (struct sockaddr_in *)uaddr;  
  5.     struct sock *sk = sock->sk;  
  6.     struct inet_sock *inet = inet_sk(sk);  
  7.     unsigned short snum;  
  8.     int chk_addr_ret;  
  9.     int err;  
  10.   
  11.     /* If the socket has its own bind function then use it. (RAW) */  
  12.     if (sk->sk_prot->bind) {  
  13.         err = sk->sk_prot->bind(sk, uaddr, addr_len);  
  14.         goto out;  
  15.     }  
  16.     err = -EINVAL;  
  17.     if (addr_len < sizeof(struct sockaddr_in))  
  18.         goto out;  
  19. ///得到地址類型,比如廣播地址之類的.  
  20.     chk_addr_ret = inet_addr_type(sock_net(sk), addr->sin_addr.s_addr);  
  21.   
  22.     err = -EADDRNOTAVAIL;  
  23.   
  24. ///主要是判斷綁定的地址不是本地時的一些條件判斷.  
  25.     if (!sysctl_ip_nonlocal_bind &&  
  26.         !inet->freebind &&  
  27.         addr->sin_addr.s_addr != htonl(INADDR_ANY) &&  
  28.         chk_addr_ret != RTN_LOCAL &&  
  29.         chk_addr_ret != RTN_MULTICAST &&  
  30.         chk_addr_ret != RTN_BROADCAST)  
  31.         goto out;  
  32. ///得到端口號.  
  33.     snum = ntohs(addr->sin_port);  
  34.     err = -EACCES;  
  35. ///主要是端口號小於prot_sock(1024)必須得有root權限.如果沒有則退出.capable就是用來判斷權限的.  
  36.     if (snum && snum < PROT_SOCK && !capable(CAP_NET_BIND_SERVICE))  
  37.         goto out;  
  38.   
  39.     /*      We keep a pair of addresses. rcv_saddr is the one 
  40.      *      used by hash lookups, and saddr is used for transmit. 
  41.      * 
  42.      *      In the BSD API these are the same except where it 
  43.      *      would be illegal to use them (multicast/broadcast) in 
  44.      *      which case the sending device address is used. 
  45.      */  
  46.     lock_sock(sk);  
  47.   
  48.     /* Check these errors (active socket, double bind). */  
  49.     err = -EINVAL;  
  50. ///檢測狀態是否爲close.如果是close狀態,說明這個socket前面已經bind過了.而num只有當raw socket時纔會不爲0  
  51.     if (sk->sk_state != TCP_CLOSE || inet->num)  
  52.         goto out_release_sock;  
  53.   
  54. ///設置相應的地址.rcv_saddr是通過hash查找的源地址,而saddr是ip層使用的源地址(ip頭的源地址).  
  55.     inet->rcv_saddr = inet->saddr = addr->sin_addr.s_addr;  
  56. ///如果是多播或者廣播,設置saddr.  
  57.     if (chk_addr_ret == RTN_MULTICAST || chk_addr_ret == RTN_BROADCAST)  
  58.         inet->saddr = 0;  /* Use device */  
  59.   
  60. ///這裏get_port用來發現我們綁定的端口,是否被允許使用.而get_port在tcp中,被實例化爲inet_csk_get_port,接近着我們會分析它的實現.  
  61.     if (sk->sk_prot->get_port(sk, snum)) {  
  62.         inet->saddr = inet->rcv_saddr = 0;  
  63.         err = -EADDRINUSE;  
  64.         goto out_release_sock;  
  65.     }  
  66. ///這兩個鎖不太理解.不知道誰能解釋下.  
  67.     if (inet->rcv_saddr)  
  68.         sk->sk_userlocks |= SOCK_BINDADDR_LOCK;  
  69.     if (snum)  
  70.         sk->sk_userlocks |= SOCK_BINDPORT_LOCK;  
  71. ///設置源端口  
  72.     inet->sport = htons(inet->num);  
  73. ///目的地址和目的端口,暫時設爲0  
  74.     inet->daddr = 0;  
  75.     inet->dport = 0;  
  76.     sk_dst_reset(sk);  
  77.     err = 0;  
  78. out_release_sock:  
  79.     release_sock(sk);  
  80. out:  
  81.     return err;  
  82. }  


這裏我先來介紹下inet_csk_get_port的流程. 

當綁定的port爲0時,這時也就是說需要kernel來分配一個新的port. 
1 首先得到系統的port範圍. 

2  隨機分配一個port. 

3 從bhash中得到當前隨機分配的端口的鏈表(也就是inet_bind_bucket鏈表). 

4 遍歷這個鏈表(鏈表爲空的話,也說明這個port沒有被使用),如果這個端口已經被使用,則將端口號加一,繼續循環,直到找到當前沒有被使用的port,也就是沒有在bhash中存在的port. 

5 新建一個inet_bind_bucket,並插入到bhash中. 

當指定port時. 

1 從bhash中根據hash值(port計算的)取得當前指定端口對應的inet_bind_bucket結構. 

2 如果bhash中存在,則說明,這個端口已經在使用,因此需要判斷這個端口是否允許被reuse. 

3 如果不存在,則步驟和上面的第5部一樣. 

Java代碼  收藏代碼
  1. int inet_csk_get_port(struct sock *sk, unsigned short snum)  
  2. {  
  3.     struct inet_hashinfo *hashinfo = sk->sk_prot->h.hashinfo;  
  4.     struct inet_bind_hashbucket *head;  
  5.     struct hlist_node *node;  
  6.     struct inet_bind_bucket *tb;  
  7.     int ret;  
  8.     struct net *net = sock_net(sk);  
  9.   
  10.     local_bh_disable();  
  11.     if (!snum) {  
  12. ///端口爲0,也就是需要內核來分配端口.  
  13.         int remaining, rover, low, high;  
  14. ///得到端口範圍.  
  15.         inet_get_local_port_range(&low, &high);  
  16.         remaining = (high - low) + 1;  
  17.         rover = net_random() % remaining + low;  
  18.   
  19. ///循環來得到一個當前沒有使用的端口.  
  20.         do {  
  21. ///通過端口爲key,來得到相應的inet_bind_bucket  
  22.             head = &hashinfo->bhash[inet_bhashfn(net, rover,  
  23.                     hashinfo->bhash_size)];  
  24.             spin_lock(&head->lock);  
  25.             inet_bind_bucket_for_each(tb, node, &head->chain)  
  26.                 if (tb->ib_net == net && tb->port == rover)  
  27. ///說明這個端口已被使用,因此需要將端口加1,重新查找.  
  28.                     goto next;  
  29.             break;  
  30.         next:  
  31.             spin_unlock(&head->lock);  
  32. ///如果端口大於最大值,則將它賦值爲最小值(這是因爲我們這個端口是隨機值,因此有可能很多端口就被跳過了),重新查找.  
  33.             if (++rover > high)  
  34.                 rover = low;  
  35.         } while (--remaining > 0);  
  36.   
  37.         /* Exhausted local port range during search?  It is not 
  38.          * possible for us to be holding one of the bind hash 
  39.          * locks if this test triggers, because if 'remaining' 
  40.          * drops to zero, we broke out of the do/while loop at 
  41.          * the top level, not from the 'break;' statement. 
  42.          */  
  43.         ret = 1;  
  44.         if (remaining <= 0)  
  45.             goto fail;  
  46. ///將要分配的端口號.  
  47.         snum = rover;  
  48.     } else {  
  49. ///指定端口號的情況.和上面的方法差不多,只不過只需要一次.  
  50.         head = &hashinfo->bhash[inet_bhashfn(net, snum,  
  51.                 hashinfo->bhash_size)];  
  52.         spin_lock(&head->lock);  
  53.         inet_bind_bucket_for_each(tb, node, &head->chain)  
  54.             if (tb->ib_net == net && tb->port == snum)  
  55.                 goto tb_found;  
  56.     }  
  57.     tb = NULL;  
  58.     goto tb_not_found;  
  59. tb_found:  
  60. ///用來處理端口號已經被使用的情況.他被使用的socket不爲空的情況.  
  61.     if (!hlist_empty(&tb->owners)) {  
  62. ///fastreuse大於0說明其他的socket允許另外的socket也使用這個端口,而reuse表示當前的端口也允許和其他的端口分享這個port.並且socket的狀態必須是TCP_LISTEN,才能做這個判斷.  
  63.         if (tb->fastreuse > 0 &&  
  64.             sk->sk_reuse && sk->sk_state != TCP_LISTEN) {  
  65.             goto success;  
  66.         } else {  
  67.             ret = 1;  
  68. ///如果出錯,調用inet_csk_bind_conflict.主要是有可能一些使用這個端口的socket,有可能使用不同的ip地址.此時,我們是可以使用這個端口的.  
  69.             if (inet_csk(sk)->icsk_af_ops->bind_conflict(sk, tb))  
  70.                 goto fail_unlock;  
  71.         }  
  72.     }  
  73. tb_not_found:  
  74.     ret = 1;  
  75. ///重新分配一個inet_bind_bucket,並鏈接到bhash.  
  76.     if (!tb && (tb = inet_bind_bucket_create(hashinfo->bind_bucket_cachep,  
  77.                     net, head, snum)) == NULL)  
  78.         goto fail_unlock;  
  79.     if (hlist_empty(&tb->owners)) {  
  80. ///設置當前端口的fastreuse,這個域也只能是處於listen的socket才能設置.  
  81.         if (sk->sk_reuse && sk->sk_state != TCP_LISTEN)  
  82.             tb->fastreuse = 1;  
  83.         else  
  84.             tb->fastreuse = 0;  
  85.     } else if (tb->fastreuse &&  
  86.            (!sk->sk_reuse || sk->sk_state == TCP_LISTEN))  
  87.         tb->fastreuse = 0;  
  88. success:  
  89. ///將這個socket加到這個端口的ower中.  
  90.     if (!inet_csk(sk)->icsk_bind_hash)  
  91.         inet_bind_hash(sk, tb, snum);  
  92.     WARN_ON(inet_csk(sk)->icsk_bind_hash != tb);  
  93.     ret = 0;  
  94.   
  95. fail_unlock:  
  96.     spin_unlock(&head->lock);  
  97. fail:  
  98.     local_bh_enable();  
  99.     return ret;  
  100. }  



在看listen的代碼之前.我們也先來看相關的數據結構: 

其中inet_connection_sock我們先前已經介紹過了,它包含了一個icsk_accept_queue的域,這個域是一個request_sock_queue類型,.我們就先來看這個結構: 

request_sock_queue也就表示一個request_sock隊列.這裏我們知道,tcp中分爲半連接隊列(處於SYN_RECVD狀態)和已完成連接隊列(處於established狀態).這兩個一個是剛接到syn,等待三次握手完成,一個是已經完成三次握手,等待accept來讀取. 

這裏每個syn分節到來都會新建一個request_sock結構,並將它加入到listen_sock的request_sock hash表中.然後3次握手完畢後,將它放入到request_sock_queue的rskq_accept_head和rskq_accept_tail隊列中.這樣當accept的時候就直接從這個隊列中讀取了. 


Java代碼  收藏代碼
  1. struct request_sock_queue {  
  2. ///一個指向頭,一個指向結尾.  
  3.     struct request_sock *rskq_accept_head;  
  4.     struct request_sock *rskq_accept_tail;  
  5.     rwlock_t        syn_wait_lock;  
  6.     u8          rskq_defer_accept;  
  7.     /* 3 bytes hole, try to pack */  
  8. ///相應的listen_socket結構.  
  9.     struct listen_sock  *listen_opt;  
  10. };  


listen_sock 表示一個處於listening狀態的socket. 

Java代碼  收藏代碼
  1. struct listen_sock {  
  2. ///log_2 of maximal queued SYNs/REQUESTs ,這裏不太理解這個域的作用.  
  3.     u8          max_qlen_log;  
  4.     /* 3 bytes hole, try to use */  
  5. ///當前的半連接隊列的長度.  
  6.     int         qlen;  
  7. ///也是指當前的半開連接隊列長度,不過這個值會當重傳syn/ack的時候(這裏要注意是這個syn/ack第一次重傳的時候纔會減一)自動減一.  
  8.     int         qlen_young;  
  9.     int         clock_hand;  
  10.     u32         hash_rnd;  
  11. ///這個值表示了當前的syn_backlog(半開連接隊列)的最大值  
  12.     u32         nr_table_entries;  
  13. ///半連接隊列.  
  14.     struct request_sock *syn_table[0];  
  15. };  


最後來看下request_sock,它保存了tcp雙方傳輸所必需的一些域,比如窗口大小,對端速率,對端數據包序列號等等這些值. 
Java代碼  收藏代碼
  1. struct request_sock {  
  2.     struct request_sock     *dl_next; /* Must be first member! */  
  3. ///mss值.  
  4.     u16             mss;  
  5.     u8              retrans;  
  6.     u8              cookie_ts; /* syncookie: encode tcpopts in timestamp */  
  7.     /* The following two fields can be easily recomputed I think -AK */  
  8.     u32             window_clamp; /* window clamp at creation time */  
  9. ///窗口大小.  
  10.     u32             rcv_wnd;      /* rcv_wnd offered first time */  
  11.     u32             ts_recent;  
  12.     unsigned long           expires;  
  13. ///這個域包含了發送ack的操作集合.  
  14.     const struct request_sock_ops   *rsk_ops;  
  15.     struct sock         *sk;  
  16.     u32             secid;  
  17.     u32             peer_secid;  
  18. };  



listen的對應的系統調用是sys_listen,它首先通過sockfd_lookup_light查找到相應的socket,然後調用inet_listen,大體流程和bind差不多,只不過中間調用的是inet_listen罷了. 

這裏還有一個概念那就是backlog,在linux中,backlog的大小指的是已完成連接隊列的大小.而不是和半連接隊列之和.而半開連接的大小一般是和backlog差不多大小. 

而半開連接隊列的最大長度是根據backlog計算的,我們後面會介紹這個. 

因此我們直接來看inet_listen的實現,這個函數主要是進行一些合法性判斷,然後調用inet_csk_listen_start來對相關域進行處理: 


Java代碼  收藏代碼
  1. int inet_listen(struct socket *sock, int backlog)  
  2. {  
  3.     struct sock *sk = sock->sk;  
  4.     unsigned char old_state;  
  5.     int err;  
  6.   
  7.     lock_sock(sk);  
  8.   
  9.     err = -EINVAL;  
  10. ///判斷狀態(非連接狀態)以及socket類型.  
  11.     if (sock->state != SS_UNCONNECTED || sock->type != SOCK_STREAM)  
  12.         goto out;  
  13.   
  14.     old_state = sk->sk_state;  
  15. ///狀態必須爲close或者listen.  
  16.     if (!((1 << old_state) & (TCPF_CLOSE | TCPF_LISTEN)))  
  17.         goto out;  
  18.   
  19.     /* Really, if the socket is already in listen state 
  20.      * we can only allow the backlog to be adjusted. 
  21.      */  
  22. ///非listen狀態,需要我們處理.  
  23.     if (old_state != TCP_LISTEN) {  
  24.         err = inet_csk_listen_start(sk, backlog);  
  25.         if (err)  
  26.             goto out;  
  27.     }  
  28. ///將backlog賦值給sk_max_ack_backlog,也就是完全連接隊列最大值.  
  29.     sk->sk_max_ack_backlog = backlog;  
  30.     err = 0;  
  31.   
  32. out:  
  33.     release_sock(sk);  
  34.     return err;  
  35. }  


然後來看inet_csk_listen_start的實現. 

它的主要工作是新分配一個listen socket,將它加入到inet_connection_sock的icsk_accept_queue域的listen_opt中.然後對當前使用端口進行判斷.最終返回: 


Java代碼  收藏代碼
  1. int inet_csk_listen_start(struct sock *sk, const int nr_table_entries)  
  2. {  
  3.     struct inet_sock *inet = inet_sk(sk);  
  4.     struct inet_connection_sock *icsk = inet_csk(sk);  
  5. ///新分配一個listen socket.  
  6.     int rc = reqsk_queue_alloc(&icsk->icsk_accept_queue, nr_table_entries);  
  7.   
  8.     if (rc != 0)  
  9.         return rc;  
  10. ///先將這兩個ack_backlog賦值爲0.  
  11.     sk->sk_max_ack_backlog = 0;  
  12.     sk->sk_ack_backlog = 0;  
  13.     inet_csk_delack_init(sk);  
  14.   
  15.     /* There is race window here: we announce ourselves listening, 
  16.      * but this transition is still not validated by get_port(). 
  17.      * It is OK, because this socket enters to hash table only 
  18.      * after validation is complete. 
  19.      */  
  20. ///設置狀態.  
  21.     sk->sk_state = TCP_LISTEN;  
  22. ///get_port上面已經分析過了.這裏之所以還要再次判斷一下端口,是爲了防止多線程,也就是另一個線程在我們調用listen之前改變了這個端口的信息.  
  23.     if (!sk->sk_prot->get_port(sk, inet->num)) {  
  24. //端口可用的情況,將端口值付給sport,並加入到inet_hashinfo(上面已經分析過)的listening_hash hash鏈表中.  
  25.         inet->sport = htons(inet->num);  
  26.   
  27.         sk_dst_reset(sk);  
  28. ///這裏調用__inet_hash實現的.  
  29.         sk->sk_prot->hash(sk);  
  30.   
  31.         return 0;  
  32.     }  
  33. ///不可用,則返回錯誤.  
  34.     sk->sk_state = TCP_CLOSE;  
  35.     __reqsk_queue_destroy(&icsk->icsk_accept_queue);  
  36.     return -EADDRINUSE;  
  37. }  


最後我們來看下reqsk_queue_alloc的實現: 


Java代碼  收藏代碼
  1. ///半開連接的最大長度.  
  2. int sysctl_max_syn_backlog = 256;  
  3.   
  4. int reqsk_queue_alloc(struct request_sock_queue *queue,  
  5.               unsigned int nr_table_entries)  
  6. {  
  7.     size_t lopt_size = sizeof(struct listen_sock);  
  8.     struct listen_sock *lopt;  
  9. ///在當前的nr_table_entries(也就是listen傳進來的backlog)和sysctl_max_syn_backlog取一個較小的值.  
  10.     nr_table_entries = min_t(u32, nr_table_entries, sysctl_max_syn_backlog);  
  11.   
  12. ///也就是說nr_table_entries不能小於8.  
  13.     nr_table_entries = max_t(u32, nr_table_entries, 8);  
  14.   
  15. ///其實也就是使nr_table_entries更接近於2的次冪  
  16.     nr_table_entries = roundup_pow_of_two(nr_table_entries + 1);  
  17. ///最終所要分配的listen_sock 的大小.  
  18.     lopt_size += nr_table_entries * sizeof(struct request_sock *);  
  19.     if (lopt_size > PAGE_SIZE)  
  20.         lopt = __vmalloc(lopt_size,  
  21.             GFP_KERNEL | __GFP_HIGHMEM | __GFP_ZERO,  
  22.             PAGE_KERNEL);  
  23.     else  
  24.         lopt = kzalloc(lopt_size, GFP_KERNEL);  
  25.     if (lopt == NULL)  
  26.         return -ENOMEM;  
  27. ///計算max_qlen_log的值,他最小要爲3,最大爲對nr_table_entries求以2爲低的log..  
  28.     for (lopt->max_qlen_log = 3;  
  29.          (1 << lopt->max_qlen_log) < nr_table_entries;  
  30.          lopt->max_qlen_log++);  
  31.   
  32.     get_random_bytes(&lopt->hash_rnd, sizeof(lopt->hash_rnd));  
  33.     rwlock_init(&queue->syn_wait_lock);  
  34.     queue->rskq_accept_head = NULL;  
  35. ///給nr_table_entries賦值.  
  36.     lopt->nr_table_entries = nr_table_entries;  
  37.   
  38.     write_lock_bh(&queue->syn_wait_lock);  
  39. ///將listen_socket賦值給queue->listen_opt  
  40.     queue->listen_opt = lopt;  
  41.     write_unlock_bh(&queue->syn_wait_lock);  
  42.   
  43.     return 0;  
  44. }  
發佈了7 篇原創文章 · 獲贊 4 · 訪問量 8萬+
發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章