Chinaunix首页 | 论坛 | 博客
  • 博客访问: 1800768
  • 博文数量: 306
  • 博客积分: 3133
  • 博客等级: 中校
  • 技术积分: 3932
  • 用 户 组: 普通用户
  • 注册时间: 2009-04-19 16:50
文章分类

全部博文(306)

文章存档

2018年(7)

2017年(18)

2016年(39)

2015年(35)

2014年(52)

2013年(39)

2012年(22)

2011年(29)

2010年(53)

2009年(12)

分类: LINUX

2011-11-22 20:49:10

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;  


首先来看下内核如何处理3次握手的半连接队列和accept队列(其实也就是server端的三次握手的状态变换).而半连接队列和accept队列在内核如何表示,我们上次已经介绍过了,这里就不介绍了.


首先我们知道当3层的数据包到达之后会调用4层的协议handle,tcp的话就是tcp_v4_rcv.如何调用可以看我前面的blog:

而在tcp_v4_rcv中,则最终会调用tcp_v4_do_rcv来处理输入数据包.在看tcp_v4_do_rcv之前,我们先来看在tcp_v4_rcv中,内核如何通过4元组(目的,源端口和地址)来查找对应得sock对象.

在分析之前,我们要知道,当一对tcp连接3次握手完毕后,内核将会重新new一个socket,这个socket中的大部分域都是与主socket相同的.而把这个新的socket的状态设置为established,而主socket的状态依旧为listen状态.

而通过前面的blog分析,我们也知道在inet_hashinfo中将处于listening状态的socket和处于 TCP_ESTABLISHED与TCP_CLOSE之间的状态的socket是分开的,一个是ehash,一个是listening_hash.因此通 过对应的4元组查找socket也是分开在这两个hash链表中操作的.

内核是通过调用__inet_lookup来查找socket的:

Java代码  收藏代码
  1. ///在tcp_v4_rcv中的代码片段.  
  2. sk = __inet_lookup(net, &tcp_hashinfo, iph->saddr,  
  3.             th->source, iph->daddr, th->dest, inet_iif(skb));  
  4.   
  5. static inline struct sock *__inet_lookup(struct net *net,  
  6.                      struct inet_hashinfo *hashinfo,  
  7.                      const __be32 saddr, const __be16 sport,  
  8.                      const __be32 daddr, const __be16 dport,  
  9.                      const int dif)  
  10. {  
  11.     u16 hnum = ntohs(dport);  
  12.     struct sock *sk = __inet_lookup_established(net, hashinfo,  
  13.                 saddr, sport, daddr, hnum, dif);  
  14.   
  15.     return sk ? : __inet_lookup_listener(net, hashinfo, daddr, hnum, dif);  
  16. }  


tcp_hashinfo我们前面也已经分析过了,包含了所有tcp所用到的hash信息,比如socket,port等等.这里的查找其实就是在tcp_hashinfo中(其实是它的域ehash或者listening_hash)查找相应的socket.

我们可以看到内核在这里进行了两次查找,首先是在established状态的socket中查找,处于established状态,说明3次握手已经完成,因此这个socket可以通过简单的4元组hash在hashinfo的ehash中查找.

而当在__inet_lookup_established中没有找到时,则将会__inet_lookup_listener中查找.也就是在处于listening状态的socket中查找(这里主要是通过daddr也就是目的地址来进行匹配).


当找到对应的socket以后就会进入数据包的处理,也就是进入tcp_v4_do_rcv函数.


Java代码  收藏代码
  1. int tcp_v4_do_rcv(struct sock *sk, struct sk_buff *skb)  
  2. {  
  3.     struct sock *rsk;  
  4. ..................................................  
  5.   
  6. ///如果为TCP_ESTABLISHED状态,则进入相关处理  
  7.     if (sk->sk_state == TCP_ESTABLISHED) { /* Fast path */  
  8.         TCP_CHECK_TIMER(sk);  
  9.         if (tcp_rcv_established(sk, skb, tcp_hdr(skb), skb->len)) {  
  10.             rsk = sk;  
  11.             goto reset;  
  12.         }  
  13.         TCP_CHECK_TIMER(sk);  
  14.         return 0;  
  15.     }  
  16.   
  17. ///进行包头的合法性校验.  
  18.     if (skb->len < tcp_hdrlen(skb) || tcp_checksum_complete(skb))  
  19.         goto csum_err;  
  20. ///进入TCP_LISTEN状态.  
  21.     if (sk->sk_state == TCP_LISTEN) {  
  22.         struct sock *nsk = tcp_v4_hnd_req(sk, skb);  
  23.         if (!nsk)  
  24.             goto discard;  
  25.   
  26.         if (nsk != sk) {  
  27.             if (tcp_child_process(sk, nsk, skb)) {  
  28.                 rsk = nsk;  
  29.                 goto reset;  
  30.             }  
  31.             return 0;  
  32.         }  
  33.     }  
  34.   
  35.     TCP_CHECK_TIMER(sk);  
  36. ///进入其他状态的处理.除了ESTABLISHED和TIME_WAIT状态.  
  37.     if (tcp_rcv_state_process(sk, skb, tcp_hdr(skb), skb->len)) {  
  38.         rsk = sk;  
  39.         goto reset;  
  40.     }  
  41.     TCP_CHECK_TIMER(sk);  
  42.     return 0;  
  43. ......................................................................  
  44. }  


可以看到当进来之后,会通过判断socket的不同状态来进入不同的处理.这里其实就分了3种状态,TCP_ESTABLISHED,TCP_LISTEN和剩余的的状态.

我们这里先不分析TCP_ESTABLISHED.

我们先来看当第一个syn分解到达后,内核会做怎么样处理.首先它会进入tcp_v4_hnd_req函数,这个函数我们后面会处理,这里只需要 知道当为第一个syn分节时,它会返回当前socket.因此此时nsk == sk,所以我们进入tcp_rcv_state_process函数,这个函数处理除了ESTABLISHED和TIME_WAIT状态之外的所有状态.

我们这里只看他的listen状态处理,后面的话也是遇到一个状态,我们看一个状态的处理:


Java代码  收藏代码
  1. int tcp_rcv_state_process(struct sock *sk, struct sk_buff *skb,  
  2.               struct tcphdr *th, unsigned len)  
  3. {  
  4.     struct tcp_sock *tp = tcp_sk(sk);  
  5. ///取得对应的inet_connection_sock .  
  6.     struct inet_connection_sock *icsk = inet_csk(sk);  
  7.     int queued = 0;  
  8.     tp->rx_opt.saw_tstamp = 0;  
  9.   
  10.     switch (sk->sk_state) {  
  11.     case TCP_LISTEN:  
  12. ///当为ack分节,则返回1,而对应内核会发送一个rst给对端.  
  13.         if (th->ack)  
  14.             return 1;  
  15. ///如果是rst,则忽略这个分组.  
  16.         if (th->rst)  
  17.             goto discard;  
  18. ///是syn分组,因此调用对应的虚函数conn_request,而这个函数在tcpv4中被初始化为tcp_v4_conn_request.  
  19.         if (th->syn) {  
  20.             if (icsk->icsk_af_ops->conn_request(sk, skb) < 0)  
  21.                 return 1;  
  22.             kfree_skb(skb);  
  23.             return 0;  
  24.         }  
  25.         goto discard;  
  26. ............................................................  
  27. }  


可以看到最终会调用tcp_v4_conn_request来处理syn分组,我们接下来就来看这个函数的实现.

先来看几个相关的函数,第一个是reqsk_queue_is_full,他来判断半连接队列是否已满.其实实现很简单,就是判断qlen和max_qlen_log的大小:

Java代码  收藏代码
  1. static inline int reqsk_queue_is_full(const struct request_sock_queue *queue)  
  2. {  
  3.     return queue->listen_opt->qlen >> queue->listen_opt->max_qlen_log;  
  4. }  


第二个是sk_acceptq_is_full,它用来判断accept队列是否已满.这个也是很简单,比较当前的队列大小sk_ack_backlog与最大的队列大小sk_max_ack_backlog.

Java代码  收藏代码
  1. static inline int sk_acceptq_is_full(struct sock *sk)  
  2. {  
  3.     return sk->sk_ack_backlog > sk->sk_max_ack_backlog;  
  4. }  


最后一个是tcp_openreq_init,它用来新建一个inet_request_sock,我们知道每次一个syn到达后,我们都会新建一个inet_request_sock,并加入到半连接队列.

Java代码  收藏代码
  1. static inline void tcp_openreq_init(struct request_sock *req,  
  2.                     struct tcp_options_received *rx_opt,  
  3.                     struct sk_buff *skb)  
  4. {  
  5.     struct inet_request_sock *ireq = inet_rsk(req);  
  6.   
  7.     req->rcv_wnd = 0;        /* So that tcp_send_synack() knows! */  
  8.     req->cookie_ts = 0;  
  9.     tcp_rsk(req)->rcv_isn = TCP_SKB_CB(skb)->seq;  
  10.     req->mss = rx_opt->mss_clamp;  
  11.     req->ts_recent = rx_opt->saw_tstamp ? rx_opt->rcv_tsval : 0;  
  12.     ireq->tstamp_ok = rx_opt->tstamp_ok;  
  13.     ireq->sack_ok = rx_opt->sack_ok;  
  14.     ireq->snd_wscale = rx_opt->snd_wscale;  
  15.     ireq->wscale_ok = rx_opt->wscale_ok;  
  16.     ireq->acked = 0;  
  17.     ireq->ecn_ok = 0;  
  18.     ireq->rmt_port = tcp_hdr(skb)->source;  
  19. }  



接下来来看tcp_v4_conn_request的实现,
Java代码  收藏代码
  1. int tcp_v4_conn_request(struct sock *sk, struct sk_buff *skb)  
  2. {  
  3.     struct inet_request_sock *ireq;  
  4.     struct tcp_options_received tmp_opt;  
  5.     struct request_sock *req;  
  6.     __be32 saddr = ip_hdr(skb)->saddr;  
  7.     __be32 daddr = ip_hdr(skb)->daddr;  
  8. ///这个名字实在是无语,when具体表示什么不太理解,只是知道它是用来计算rtt的.  
  9.     __u32 isn = TCP_SKB_CB(skb)->when;  
  10.     struct dst_entry *dst = NULL;  
  11. #ifdef CONFIG_SYN_COOKIES  
  12.     int want_cookie = 0;  
  13. #else  
  14. #define want_cookie 0 /* Argh, why doesn't gcc optimize this :( */  
  15. #endif  
  16.   
  17. ///如果是广播或者多播,则丢掉这个包.  
  18.     if (skb->rtable->rt_flags & (RTCF_BROADCAST | RTCF_MULTICAST))  
  19.         goto drop;  
  20.   
  21. ///判断半连接队列是否已经满掉.如果满掉并且处于非timewait状态,则丢掉这个包(如果设置了SYN Cookie则会继续进行,因为SYN Cookie不需要新分配半连接队列,详细的SYN Cookie请google)  
  22.     if (inet_csk_reqsk_queue_is_full(sk) && !isn) {  
  23. #ifdef CONFIG_SYN_COOKIES  
  24.         if (sysctl_tcp_syncookies) {  
  25.             want_cookie = 1;  
  26.         } else  
  27. #endif  
  28.         goto drop;  
  29.     }  
  30. ///如果accept队列已满,并且qlen_young大于一就丢掉这个包,这里qlen_young大于一表示在syn队列中已经有足够多的(这里不包括重传的syn)请求了.  
  31.     if (sk_acceptq_is_full(sk) && inet_csk_reqsk_queue_young(sk) > 1)  
  32.         goto drop;  
  33.     req = inet_reqsk_alloc(&tcp_request_sock_ops);  
  34.     if (!req)  
  35.         goto drop;  
  36. ...................................................  
  37.   
  38. ///对tmp_opt进行初始化,而tcp_options_received中包含了tcp的一些选项信息(比如mss,窗口扩大因子等等)  
  39.     tcp_clear_options(&tmp_opt);  
  40.     tmp_opt.mss_clamp = 536;  
  41.     tmp_opt.user_mss  = tcp_sk(sk)->rx_opt.user_mss;  
  42.   
  43. ///对对端的tcp_options_received进行解析,并对本端得tcp_options_received进行初始化.  
  44.     tcp_parse_options(skb, &tmp_opt, 0);  
  45.   
  46. .......................................................  
  47. ///这里对新的req进行初始化.  
  48.   
  49.     tcp_openreq_init(req, &tmp_opt, skb);  
  50. ...............................................  
  51.   
  52. ///这里将tcp_options_received保存到req中.  
  53.     ireq->opt = tcp_v4_save_options(sk, skb);  
  54.     if (!want_cookie)  
  55.         TCP_ECN_create_request(req, tcp_hdr(skb));  
  56.   
  57.     if (want_cookie) {  
  58. #ifdef CONFIG_SYN_COOKIES  
  59.         syn_flood_warning(skb);  
  60.         req->cookie_ts = tmp_opt.tstamp_ok;  
  61. #endif  
  62.         isn = cookie_v4_init_sequence(sk, skb, &req->mss);  
  63.     }else if (!isn) {  
  64. .............................................  
  65. ///计算当前一个合适的isn,并返回.  
  66.         isn = tcp_v4_init_sequence(skb);  
  67.     }  
  68.   
  69. ///赋值发送给对端的isn  
  70.     tcp_rsk(req)->snt_isn = isn;  
  71.   
  72. ///发送syn和ack(如果设置了want_cookie则不会将这个req链接到半连接队列中.  
  73.     if (__tcp_v4_send_synack(sk, req, dst) || want_cookie)  
  74.         goto drop_and_free;  
  75.   
  76. ///将这个req链接到半连接队列中.  
  77.     inet_csk_reqsk_queue_hash_add(sk, req, TCP_TIMEOUT_INIT);  
  78.     return 0;  
  79.   
  80. drop_and_release:  
  81.     dst_release(dst);  
  82. drop_and_free:  
  83.     reqsk_free(req);  
  84. drop:  
  85.     return 0;  
  86. }  



而tcp_v4_hnd_req的主要工作是在半连接队列中看是否存在当前的socket,如果存在则说明这个有可能是最终的ack包,因此将会 做一系列的合法性校验(比如重传,rst,syn等等),最终确定这个是ack后会调用对应的新建socket的虚函数syn_recv_sock.

Java代码  收藏代码
  1. static struct sock *tcp_v4_hnd_req(struct sock *sk, struct sk_buff *skb)  
  2. {  
  3.     struct tcphdr *th = tcp_hdr(skb);  
  4.     const struct iphdr *iph = ip_hdr(skb);  
  5.     struct sock *nsk;  
  6.     struct request_sock **prev;  
  7. ///通过socket,查找对应request_sock  
  8.     struct request_sock *req = inet_csk_search_req(sk, &prev, th->source,  
  9.                                iph->saddr, iph->daddr);  
  10.     if (req)  
  11. ///如果存在则进入req的相关处理.  
  12.         return tcp_check_req(sk, skb, req, prev);  
  13.   
  14. ///不存在,则通过inet_lookup_established查找.这是因为有可能当我们进入这个函数之前,socket的状态被改变了,也就是这个socket的状态已经不是listen了.  
  15.   
  16.     nsk = inet_lookup_established(sock_net(sk), &tcp_hashinfo, iph->saddr,  
  17.             th->source, iph->daddr, th->dest, inet_iif(skb));  
  18.   
  19.     if (nsk) {  
  20.         if (nsk->sk_state != TCP_TIME_WAIT) {  
  21. ///非tw状态返回新的socket.  
  22.             bh_lock_sock(nsk);  
  23.             return nsk;  
  24.         }  
  25. ///如果是timewait状态则返回空.  
  26.         inet_twsk_put(inet_twsk(nsk));  
  27.         return NULL;  
  28.     }  
  29.   
  30. #ifdef CONFIG_SYN_COOKIES  
  31.     if (!th->rst && !th->syn && th->ack)  
  32.         sk = cookie_v4_check(sk, skb, &(IPCB(skb)->opt));  
  33. #endif  
  34.     return sk;  
  35. }  



tcp_check_req最主要工作就是调用虚函数,新建一个socket,并返回.

先来看几个相关的函数,第一个是inet_csk_reqsk_queue_unlink,它主要用来从半连接队列unlink掉一个元素.:

Java代码  收藏代码
  1. static inline void inet_csk_reqsk_queue_unlink(struct sock *sk,  
  2.                            struct request_sock *req,  
  3.                            struct request_sock **prev)  
  4. {  
  5.     reqsk_queue_unlink(&inet_csk(sk)->icsk_accept_queue, req, prev);  
  6. }  
  7.   
  8. static inline void reqsk_queue_unlink(struct request_sock_queue *queue,  
  9.                       struct request_sock *req,  
  10.                       struct request_sock **prev_req)  
  11. {  
  12.     write_lock(&queue->syn_wait_lock);  
  13. ///处理链表.  
  14.     *prev_req = req->dl_next;  
  15.     write_unlock(&queue->syn_wait_lock);  
  16. }  


第二个是inet_csk_reqsk_queue_removed,它主要用来修改对应的qlen和qlen_young的值.


Java代码  收藏代码
  1. static inline void inet_csk_reqsk_queue_removed(struct sock *sk,  
  2.                         struct request_sock *req)  
  3. {  
  4.     if (reqsk_queue_removed(&inet_csk(sk)->icsk_accept_queue, req) == 0)  
  5.         inet_csk_delete_keepalive_timer(sk);  
  6. }  
  7.   
  8. static inline int reqsk_queue_removed(struct request_sock_queue *queue,  
  9.                       struct request_sock *req)  
  10. {  
  11.     struct listen_sock *lopt = queue->listen_opt;  
  12. ///如果重传数为0则说明没有重传过,因此qlen_young跟着也减一.  
  13.     if (req->retrans == 0)  
  14.         --lopt->qlen_young;  
  15.   
  16.     return --lopt->qlen;  
  17. }  


最后是inet_csk_reqsk_queue_add,它用来把新的req加入到accept队列中.


Java代码  收藏代码
  1. static inline void inet_csk_reqsk_queue_add(struct sock *sk,  
  2.                         struct request_sock *req,  
  3.                         struct sock *child)  
  4. {  
  5.     reqsk_queue_add(&inet_csk(sk)->icsk_accept_queue, req, sk, child);  
  6. }  
  7.   
  8.   
  9. static inline void reqsk_queue_add(struct request_sock_queue *queue,  
  10.                    struct request_sock *req,  
  11.                    struct sock *parent,  
  12.                    struct sock *child)  
  13. {  
  14.     req->sk = child;  
  15.     sk_acceptq_added(parent);  
  16. ///可以看到刚好就是request_sock_queue的rskq_accept_head与rskq_accept_tail保存accept队列.  
  17.     if (queue->rskq_accept_head == NULL)  
  18.         queue->rskq_accept_head = req;  
  19.     else  
  20.         queue->rskq_accept_tail->dl_next = req;  
  21.   
  22.     queue->rskq_accept_tail = req;  
  23.     req->dl_next = NULL;  
  24. }  


然后再来看tcp_check_req的实现.
Java代码  收藏代码
  1. struct sock *tcp_check_req(struct sock *sk,struct sk_buff *skb,  
  2.                struct request_sock *req,  
  3.                struct request_sock **prev)  
  4. {  
  5.     const struct tcphdr *th = tcp_hdr(skb);  
  6.     __be32 flg = tcp_flag_word(th) & (TCP_FLAG_RST|TCP_FLAG_SYN|TCP_FLAG_ACK);  
  7.     int paws_reject = 0;  
  8.     struct tcp_options_received tmp_opt;  
  9.     struct sock *child;  
  10.   
  11.     tmp_opt.saw_tstamp = 0;  
  12. ......................................  
  13. ///如果只有rst和syn域则发送一个rst给对端.  
  14. if (flg & (TCP_FLAG_RST|TCP_FLAG_SYN)) {  
  15.         TCP_INC_STATS_BH(sock_net(sk), TCP_MIB_ATTEMPTFAILS);  
  16.         goto embryonic_reset;  
  17.     }  
  18.   
  19. ///如果是重传的syn,则重新发送syn和ack分组.  
  20.     if (TCP_SKB_CB(skb)->seq == tcp_rsk(req)->rcv_isn &&  
  21.         flg == TCP_FLAG_SYN &&  
  22.         !paws_reject) {  
  23.         req->rsk_ops->rtx_syn_ack(sk, req);  
  24.         return NULL;  
  25.     }  
  26.   
  27.     ..........................................  
  28.   
  29. ///确定有设置ack分节.  
  30.     if (!(flg & TCP_FLAG_ACK))  
  31.         return NULL;  
  32.   
  33. ///这里主要处理TCP_DEFER_ACCEPT被设置的情况,如果它被设置,则丢掉这个包.(这是因为TCP_DEFER_ACCEPT会等待数据真正发过来才处理的,而不是最后一个ack发过来就处理)  
  34.     if (inet_csk(sk)->icsk_accept_queue.rskq_defer_accept &&  
  35.         TCP_SKB_CB(skb)->end_seq == tcp_rsk(req)->rcv_isn + 1) {  
  36.         inet_rsk(req)->acked = 1;  
  37.         return NULL;  
  38.     }  
  39.   
  40. ///可以创建一个新的socket了.返回一个包含新创建的socket的request结构.  
  41.     child = inet_csk(sk)->icsk_af_ops->syn_recv_sock(sk, skb, req, NULL);  
  42.     if (child == NULL)  
  43.         goto listen_overflow;  
  44. ..................................  
  45. #endif  
  46. ///创建成功,则在request_sock_queue的listen_opt中unlink掉这个req.也就是从半连接队列中删除这个req.  
  47.     inet_csk_reqsk_queue_unlink(sk, req, prev);  
  48. ///修改对应的 qlen和qlen_young的值.  
  49.     inet_csk_reqsk_queue_removed(sk, req);  
  50. ///最后加入到accept队列中.这里注意最终是将新的socket赋值给对应的req.  
  51.     inet_csk_reqsk_queue_add(sk, req, child);  
  52.     return child;  
  53.   
  54. listen_overflow:  
  55.     if (!sysctl_tcp_abort_on_overflow) {  
  56.         inet_rsk(req)->acked = 1;  
  57.         return NULL;  
  58.     }  
  59.   
  60. embryonic_reset:  
  61.     NET_INC_STATS_BH(sock_net(sk), LINUX_MIB_EMBRYONICRSTS);  
  62.     if (!(flg & TCP_FLAG_RST))  
  63.         req->rsk_ops->send_reset(sk, skb);  
  64.   
  65.     inet_csk_reqsk_queue_drop(sk, req, prev);  
  66.     return NULL;  
  67. }  



最后我们来看内核如何创建一个新的socket,tcp 协议使用tcp_v4_syn_recv_sock来实现,它做的其实很简单就是新建一个socket,并且设置状态为TCP_SYN_RECV(在 inet_csk_clone中),父socket继续处于listen状态,然后对新的socket进行一些赋值,然后对一些定时器进行初始化.这里定 时器我们全部都略过了,以后会专门来分析tcp中的定时器.


最后从tcp_v4_hnd_req中返回,判断是否与父socket相等,然后调用tcp_child_process函数:

这个函数主要是完成最终的三次握手,将子socket设置为TCP_ESTABLISHED然后根据条件唤醒被accept阻塞的主socket:

Java代码  收藏代码
  1. int tcp_child_process(struct sock *parent, struct sock *child,  
  2.               struct sk_buff *skb)  
  3. {  
  4.     int ret = 0;  
  5.     int state = child->sk_state;  
  6.   
  7.     if (!sock_owned_by_user(child)) {  
  8. ///完成最终的三次握手.  
  9.         ret = tcp_rcv_state_process(child, skb, tcp_hdr(skb),  
  10.                         skb->len);  
  11.         /* Wakeup parent, send SIGIO */  
  12.         if (state == TCP_SYN_RECV && child->sk_state != state)  
  13. ///唤醒阻塞的主socket.  
  14.             parent->sk_data_ready(parent, 0);  
  15.     } else {  
  16.         /* Alas, it is possible again, because we do lookup 
  17.          * in main socket hash table and lock on listening 
  18.          * socket does not protect us more. 
  19.          */  
  20.         sk_add_backlog(child, skb);  
  21.     }  
  22.   
  23.     bh_unlock_sock(child);  
  24.     sock_put(child);  
  25.     return ret;  
  26. }  


最后来分析下在tcp_rcv_state_process中的处理当前的TCP_SYN_RECV状态,它主要是为将要到来的数据传输做一些准备,设置一些相关域.:


Java代码  收藏代码
  1. case TCP_SYN_RECV:  
  2.             if (acceptable) {  
  3.                 tp->copied_seq = tp->rcv_nxt;  
  4.                 smp_mb();  
  5. ///设置状态为TCP_ESTABLISHED.  
  6.                 tcp_set_state(sk, TCP_ESTABLISHED);  
  7.                 sk->sk_state_change(sk);  
  8.   
  9. ///这里的wake应该是针对epoll这类的  
  10.                 if (sk->sk_socket)  
  11.                     sk_wake_async(sk,  
  12.                               SOCK_WAKE_IO, POLL_OUT);  
  13.   
  14. ///设置期望接收的isn号,也就是第一个字节的序列和窗口大小.  
  15.                 tp->snd_una = TCP_SKB_CB(skb)->ack_seq;  
  16.                 tp->snd_wnd = ntohs(th->window) <<  
  17.                           tp->rx_opt.snd_wscale;  
  18.                 tcp_init_wl(tp, TCP_SKB_CB(skb)->ack_seq,  
  19.                         TCP_SKB_CB(skb)->seq);  
  20.   
  21. .........................................................................  
  22.             break
先来看下accept的实现.

其实accept的作用很简单,就是从accept队列中取出三次握手完成的socket,并将它关联到vfs上(其实操作和调用 sys_socket时新建一个socket类似).然后返回.这里还有个要注意的,如果这个传递给accept的socket是非阻塞的话,就算 accept队列为空,也会直接返回,而是阻塞的话就会休眠掉,等待accept队列有数据后唤醒他.

接下来我们就来看它的实现,accept对应的系统调用是 sys_accept,而他则会调用do_accept,因此我们直接来看do_accept:

Java代码  收藏代码
  1. long do_accept(int fd, struct sockaddr __user *upeer_sockaddr,  
  2.            int __user *upeer_addrlen, int flags)  
  3. {  
  4.     struct socket *sock, *newsock;  
  5.     struct file *newfile;  
  6.     int err, len, newfd, fput_needed;  
  7.     struct sockaddr_storage address;  
  8. .............................................  
  9. ///这个函数前面已经分析过了,也就是通过fd,得到相应的socket.  
  10.     sock = sockfd_lookup_light(fd, &err, &fput_needed);  
  11.     if (!sock)  
  12.         goto out;  
  13.   
  14.     err = -ENFILE;  
  15. ///新建一个socket,也就是这个函数将要返回的socket.这里注意我们得到的是一个socket,而不是sock.下面会解释为什么这么做.  
  16.     if (!(newsock = sock_alloc()))  
  17.         goto out_put;  
  18.   
  19.     newsock->type = sock->type;  
  20.     newsock->ops = sock->ops;  
  21.   
  22.     /* 
  23.      * We don't need try_module_get here, as the listening socket (sock) 
  24.      * has the protocol module (sock->ops->owner) held. 
  25.      */  
  26.     __module_get(newsock->ops->owner);  
  27. ///找到一个新的可用的文件句柄,以及file结构.是为了与刚才新建的socket关联起来.  
  28.     newfd = sock_alloc_fd(&newfile, flags & O_CLOEXEC);  
  29.     if (unlikely(newfd < 0)) {  
  30.         err = newfd;  
  31.         sock_release(newsock);  
  32.         goto out_put;  
  33.     }  
  34. ///将新的socket和file关联起来.(这里所做的和我们第一篇所分析的信件socket的步骤是一样的,不理解的,可以去看我前面的blog  
  35.     err = sock_attach_fd(newsock, newfile, flags & O_NONBLOCK);  
  36.     if (err < 0)  
  37.         goto out_fd_simple;  
  38.   
  39.     err = security_socket_accept(sock, newsock);  
  40.     if (err)  
  41.         goto out_fd;  
  42. ///调用inet_accept  
  43.     err = sock->ops->accept(sock, newsock, sock->file->f_flags);  
  44.     if (err < 0)  
  45.         goto out_fd;  
  46. ///这里也就是取得accept到的句柄的源地址.也就是填充传递进来的upeer_sockaddr.  
  47.     if (upeer_sockaddr) {  
  48.         if (newsock->ops->getname(newsock, (struct sockaddr *)&address,  
  49.                       &len, 2) < 0) {  
  50.             err = -ECONNABORTED;  
  51.             goto out_fd;  
  52.         }  
  53.         err = move_addr_to_user((struct sockaddr *)&address,  
  54.                     len, upeer_sockaddr, upeer_addrlen);  
  55.         if (err < 0)  
  56.             goto out_fd;  
  57.     }  
  58.   
  59.     /* File flags are not inherited via accept() unlike another OSes. */  
  60. ///最终将新的file结构和fd关联起来,其实也就是最终将这个fd关联到当前进程的files中.  
  61.     fd_install(newfd, newfile);  
  62.     err = newfd;  
  63.   
  64.     security_socket_post_accept(sock, newsock);  
  65.   
  66. out_put:  
  67. ///文件描述符的引用计数加一.  
  68.     fput_light(sock->file, fput_needed);  
  69. out:  
  70. ///返回句柄.  
  71.     return err;  
  72. .......................................  
  73. }  


可以看到流程很简单,最终的实现都集中在inet_accept中了.而inet_accept主要做的就是

1 调用inet_csk_accept来进行对accept队列的操作.它会返回取得的sock.

2 将从inet_csk_accept返回的sock链接到传递进来的(也就是在do_accept中new的socket)中.这里就知道我们上面为什么只需要new一个socket而不是sock了.因为sock我们是直接从accept队列中取得的.


3 设置新的socket的状态为SS_CONNECTED.

Java代码  收藏代码
  1. int inet_accept(struct socket *sock, struct socket *newsock, int flags)  
  2. {  
  3.     struct sock *sk1 = sock->sk;  
  4.     int err = -EINVAL;  
  5. ///调用inet_csk_accept.  
  6.     struct sock *sk2 = sk1->sk_prot->accept(sk1, flags, &err);  
  7.   
  8.     if (!sk2)  
  9.         goto do_err;  
  10.   
  11.     lock_sock(sk2);  
  12. ///测试tcp连接的状态.  
  13.     WARN_ON(!((1 << sk2->sk_state) &  
  14.           (TCPF_ESTABLISHED | TCPF_CLOSE_WAIT | TCPF_CLOSE)));  
  15. ///将返回的sock链接到socket.  
  16.     sock_graft(sk2, newsock);  
  17. ///设置状态.  
  18.     newsock->state = SS_CONNECTED;  
  19.     err = 0;  
  20.     release_sock(sk2);  
  21. do_err:  
  22.     return err;  
  23. }  


inet_csk_accept就是从accept队列中取出sock然后返回.

在看他的源码之前先来看几个相关函数的实现:

首先是reqsk_queue_empty,他用来判断accept队列是否为空:

Java代码  收藏代码
  1. static inline int reqsk_queue_empty(struct request_sock_queue *queue)  
  2. {  
  3.     return queue->rskq_accept_head == NULL;  
  4. }  


然后是reqsk_queue_get_child,他主要是从accept队列中得到一个sock:

Java代码  收藏代码
  1. static inline struct sock *reqsk_queue_get_child(struct request_sock_queue *queue,  
  2.                          struct sock *parent)  
  3. {  
  4. ///首先从accept队列中remove这个socket并返回.  
  5.     struct request_sock *req = reqsk_queue_remove(queue);  
  6. ///取得socket.  
  7.     struct sock *child = req->sk;  
  8.   
  9.     WARN_ON(child == NULL);  
  10. ///这里主要是将sk_ack_backlog减一,也就是accept当前的数目减一.  
  11.     sk_acceptq_removed(parent);  
  12.     __reqsk_free(req);  
  13.     return child;  
  14. }  


这里还有一个inet_csk_wait_for_connect,它是用来在accept队列为空的情况下,休眠掉一段时间 (这里每个socket都有一个等待队列的(等待队列的用法请google,我这里就不阐述了).这里是每个调用的进程都会声明一个wait队列,然后将 它连接到主的socket的等待队列链表中,然后休眠,等到唤醒.

Java代码  收藏代码
  1. static int inet_csk_wait_for_connect(struct sock *sk, long timeo)  
  2. {  
  3.     struct inet_connection_sock *icsk = inet_csk(sk);  
  4. ///定义一个waitqueue.  
  5.     DEFINE_WAIT(wait);  
  6.     int err;  
  7. ..................................................  
  8.     for (;;) {  
  9. ///这里也就是把当前的进程的等待队列挂入sk中的sk_sleep队列,sk也就是主的那个socket.  
  10.         prepare_to_wait_exclusive(sk->sk_sleep, &wait,  
  11.                       TASK_INTERRUPTIBLE);  
  12.         release_sock(sk);  
  13. ///再次判断是否为空.  
  14.         if (reqsk_queue_empty(&icsk->icsk_accept_queue))  
  15. ///这个函数里面会休眠timeo时间(调用schedule让出cpu),或者被当accept队列有数据时唤醒(我们前面也有介绍这个)主的等待队列链表.,  
  16.             timeo = schedule_timeout(timeo);  
  17.         lock_sock(sk);  
  18.         err = 0;  
  19. ///非空则跳出.  
  20.         if (!reqsk_queue_empty(&icsk->icsk_accept_queue))  
  21.             break;  
  22.         err = -EINVAL;  
  23.         if (sk->sk_state != TCP_LISTEN)  
  24.             break;  
  25.         err = sock_intr_errno(timeo);  
  26.         if (signal_pending(current))  
  27.             break;  
  28. ///设置错误号.  
  29.         err = -EAGAIN;  
  30. ///时间为0则直接退出.  
  31.         if (!timeo)  
  32.             break;  
  33.     }  
  34. ///这里也就会从sk_sleep中remove掉当前的wait队列.  
  35.     finish_wait(sk->sk_sleep, &wait);  
  36.     return err;  
  37. }  


然后来看inet_csk_accept的源码,这里有个阻塞和非阻塞的问题.非阻塞的话会直接返回的,就算accept队列为空.这个时侯设置errno为-EAGAIN.

Java代码  收藏代码
  1. struct sock *inet_csk_accept(struct sock *sk, int flags, int *err)  
  2. {  
  3.     struct inet_connection_sock *icsk = inet_csk(sk);  
  4.     struct sock *newsk;  
  5.     int error;  
  6.   
  7.     lock_sock(sk);  
  8.   
  9.     /* We need to make sure that this socket is listening, 
  10.      * and that it has something pending. 
  11.      */  
  12.     error = -EINVAL;  
  13. ///sk也就是主socket,他的状态我们前面也讲过会一直是TCP_LISTEN.  
  14.     if (sk->sk_state != TCP_LISTEN)  
  15.         goto out_err;  
  16.   
  17. ///然后判断accept队列是否为空  
  18.     if (reqsk_queue_empty(&icsk->icsk_accept_queue)) {  
  19. ///如果是O_NONBLOCK,则返回0,此时下面的inet_csk_wait_for_connect也就会立即返回.  
  20.         long timeo = sock_rcvtimeo(sk, flags & O_NONBLOCK);  
  21.   
  22.         /* If this is a non blocking socket don't sleep */  
  23.         error = -EAGAIN;  
  24.         if (!timeo)  
  25.             goto out_err;  
  26. ///休眠或者立即返回.  
  27.         error = inet_csk_wait_for_connect(sk, timeo);  
  28.         if (error)  
  29.             goto out_err;  
  30.     }  
  31. ///得到sock并从accept队列中remove.  
  32.     newsk = reqsk_queue_get_child(&icsk->icsk_accept_queue, sk);  
  33.     WARN_ON(newsk->sk_state == TCP_SYN_RECV);  
  34. out:  
  35.     release_sock(sk);  
  36.     return newsk;  
  37. out_err:  
  38.     newsk = NULL;  
  39.     *err = error;  
  40.     goto out;  
  41. }  


最后来大概分析下connect的实现.它的具体流程是:

1 由fd得到socket,并且将地址复制到内核空间

2 调用inet_stream_connect进行主要的处理.

这里要注意connect也有个阻塞和非阻塞的区别,阻塞的话调用inet_wait_for_connect休眠,等待握手完成,否则直接返回.

Java代码  收藏代码
  1. asmlinkage long sys_connect(int fd, struct sockaddr __user *uservaddr,  
  2.                 int addrlen)  
  3. {  
  4.     struct socket *sock;  
  5.     struct sockaddr_storage address;  
  6.     int err, fput_needed;  
  7. ///得到socket.  
  8.     sock = sockfd_lookup_light(fd, &err, &fput_needed);  
  9.     if (!sock)  
  10.         goto out;  
  11. ///拷贝地址.  
  12.     err = move_addr_to_kernel(uservaddr, addrlen, (struct sockaddr *)&address);  
  13.     if (err < 0)  
  14.         goto out_put;  
  15.   
  16.     err =  
  17.         security_socket_connect(sock, (struct sockaddr *)&address, addrlen);  
  18.     if (err)  
  19.         goto out_put;  
  20. ///调用处理函数.  
  21.     err = sock->ops->connect(sock, (struct sockaddr *)&address, addrlen,  
  22.                  sock->file->f_flags);  
  23. out_put:  
  24.     fput_light(sock->file, fput_needed);  
  25. out:  
  26.     return err;  
  27. }  



然后来看inet_stream_connect,他的主要工作是:

1 判断socket的状态.只有当为SS_UNCONNECTED也就是非连接状态时才调用tcp_v4_connect来进行连接处理.

2 判断tcp的状态sk_state只能为TCPF_SYN_SENT或者TCPF_SYN_RECV,才进入相关处理.

3 如果状态合适并且socket为阻塞模式则调用inet_wait_for_connect进入休眠等待握手完成,否则直接返回,并设置错误号为EINPROGRESS.

Java代码  收藏代码
  1. int inet_stream_connect(struct socket *sock, struct sockaddr *uaddr,  
  2.             int addr_len, int flags)  
  3. {  
  4.     struct sock *sk = sock->sk;  
  5.     int err;  
  6.     long timeo;  
  7.   
  8.     lock_sock(sk);  
  9. ............................................  
  10.   
  11.     switch (sock->state) {  
  12.     default:  
  13.         err = -EINVAL;  
  14.         goto out;  
  15.     case SS_CONNECTED:  
  16.         err = -EISCONN;  
  17.         goto out;  
  18.     case SS_CONNECTING:  
  19.         err = -EALREADY;  
  20.         /* Fall out of switch with err, set for this state */  
  21.         break;  
  22.     case SS_UNCONNECTED:  
  23.         err = -EISCONN;  
  24.         if (sk->sk_state != TCP_CLOSE)  
  25.             goto out;  
  26. ///调用tcp_v4_connect来处理连接.主要是发送syn.  
  27.         err = sk->sk_prot->connect(sk, uaddr, addr_len);  
  28.         if (err < 0)  
  29.             goto out;  
  30. ///设置状态.  
  31.         sock->state = SS_CONNECTING;  
  32. ///设置错误号.  
  33.         err = -EINPROGRESS;  
  34.         break;  
  35.     }  
  36. ///和上面的处理一样,如果非阻塞返回0,否则返回timeo.  
  37.     timeo = sock_sndtimeo(sk, flags & O_NONBLOCK);  
  38.   
  39.     if ((1 << sk->sk_state) & (TCPF_SYN_SENT | TCPF_SYN_RECV)) {  
  40. ///如果非阻塞则直接返回.否则进入休眠等待三次握手完成并唤醒他.(这个函数和上面的inet_csk_wait_for_connect函数实现很类似,因此这里就不分析了)  
  41.         if (!timeo || !inet_wait_for_connect(sk, timeo))  
  42.             goto out;  
  43.   
  44.         err = sock_intr_errno(timeo);  
  45.         if (signal_pending(current))  
  46.             goto out;  
  47.     }  
  48.   
  49.     /* Connection was closed by RST, timeout, ICMP error 
  50.      * or another process disconnected us. 
  51.      */  
  52.     if (sk->sk_state == TCP_CLOSE)  
  53.         goto sock_error;  
  54. ///设置socket状态.为已连接.  
  55.     sock->state = SS_CONNECTED;  
  56.     err = 0;  
  57. out:  
  58.     release_sock(sk);  
  59.     return err;  
  60.   
  61. sock_error:  
  62.     err = sock_error(sk) ? : -ECONNABORTED;  
  63.     sock->state = SS_UNCONNECTED;  
  64.     if (sk->sk_prot->disconnect(sk, flags))  
  65.         sock->state = SS_DISCONNECTING;  
  66.     goto out;  
  67. }  


tcp_v4_connect的源码就不分析了,我这里只大概的介绍下他的流程:

1 判断地址的一些合法性.

2 调用ip_route_connect来查找出去的路由(包括查找临时端口等等).

3 设置sock的状态为TCP_SYN_SENT,并调用inet_hash_connect来查找一个临时端口(也就是我们出去的端口),并加入到对应的hash链表(具体操作和get_port很相似).

4 调用tcp_connect来完成最终的操作.这个函数主要用来初始化将要发送的syn包(包括窗口大小isn等等),然后将这个sk_buffer加入 到socket的写队列.最终调用tcp_transmit_skb传输到3层.再往下的操作就可以看我前面的blog了.

最后来看下3次握手的客户端的状态变化,还是看tcp_rcv_state_process函数,这里我们进来的socket假设就是TCP_SYN_SENT状态,也就是在等待syn和ack分节:

Java代码  收藏代码
  1. int tcp_rcv_state_process(struct sock *sk, struct sk_buff *skb,  
  2.               struct tcphdr *th, unsigned len)  
  3. {  
  4. ..........................................  
  5.   
  6.     switch (sk->sk_state) {  
  7.     case TCP_CLOSE:  
  8.         goto discard;  
  9.   
  10.     case TCP_LISTEN:  
  11.         ..................................  
  12.   
  13.     case TCP_SYN_SENT:  
  14. ///进入对应的状态机处理函数.  
  15.         queued = tcp_rcv_synsent_state_process(sk, skb, th, len);  
  16.         if (queued >= 0)  
  17.             return queued;  
  18.   
  19.         /* Do step6 onward by hand. */  
  20.         tcp_urg(sk, skb, th);  
  21.         __kfree_skb(skb);  
  22.         tcp_data_snd_check(sk);  
  23.         return 0;  
  24.     }  



然后来看tcp_rcv_synsent_state_process中的状态变化:


Java代码  收藏代码
  1. static int tcp_rcv_synsent_state_process(struct sock *sk, struct sk_buff *skb,  
  2.                      struct tcphdr *th, unsigned len)  
  3. {  
  4. ..................  
  5.   
  6.     if (th->ack) {  
  7. ....................................  
  8. ///如果是rst分节,则进行相关处理,  
  9.         if (th->rst) {  
  10.             tcp_reset(sk);  
  11.             goto discard;  
  12.         }  
  13. ///如果过来的ack分节没有syn分节则直接丢掉这个包,然后返回.  
  14.         if (!th->syn)  
  15.             goto discard_and_undo;  
  16.   
  17. ..................................................  
  18. ///如果校验都通过则设置状态为TCP_ESTABLISHED,下面就会发送最后一个ack分节.  
  19.         tcp_set_state(sk, TCP_ESTABLISHED);  
  20.   
  21.         .......................................  
  22.     }  
  23.   
  24. ....................................................  
  25.   
  26.     if (th->syn) {  
  27. ///如果只有syn分节,则此时设置状态为TCP_SYN_RECV.  
  28.         tcp_set_state(sk, TCP_SYN_RECV);  
  29.   
  30. ...................................  
  31. ///发送ack分节给对方.  
  32.         tcp_send_synack(sk);  
  33.         goto discard;  
  34. #endif  
  35.     }  
  36. ...................  
  37. }  


这里如果只接受到syn,则三次握手还没完成,我们还在等待最后一个ack,因此此时有数据报的话,会再次落入tcp_rcv_state_process函数:

Java代码  收藏代码
  1. if (th->ack) {  
  2. ///是否这个ack可被接受.  
  3.         int acceptable = tcp_ack(sk, skb, FLAG_SLOWPATH);  
  4.   
  5.         switch (sk->sk_state) {  
  6.         case TCP_SYN_RECV:  
  7.             if (acceptable) {  
  8.   
  9.                 tp->copied_seq = tp->rcv_nxt;  
  10.                 smp_mb();  
  11. ///设置为TCP_ESTABLISHED,三次握手完成.  
  12.                 tcp_set_state(sk, TCP_ESTABLISHED);  
  13.                 sk->sk_state_change(sk);  
  14. ///唤醒休眠在connect的队列.  
  15.                 if (sk->sk_socket)  
  16.                     sk_wake_async(sk,  
  17.                               SOCK_WAKE_IO, POLL_OUT);  
  18.   
  19.             ........................................  
  20.             } else {  
  21.                 return 1;  
  22.             }  
  23.             break;  
阅读(2209) | 评论(0) | 转发(0) |
0

上一篇:socket 之TIME_WAIT状态

下一篇:[转]net_rx()

给主人留下些什么吧!~~