tcp协议栈处理各种事件的分析

42 篇文章 1 订阅
21 篇文章 0 订阅
首先我们来看socket如何将一些状态的变化通知给对应的进程,比如可读,可写,出错等等。 

先来看sock结构中这几个相关域: 

Java代码   收藏代码
  1. struct sock {  
  2. ..........................  
  3. wait_queue_head_t   *sk_sleep;  
  4. .....................................  
  5. void        (*sk_state_change)(struct sock *sk);  
  6. void        (*sk_data_ready)(struct sock *sk, int bytes);  
  7. void        (*sk_write_space)(struct sock *sk);  
  8. void        (*sk_error_report)(struct sock *sk);  
  9. int (*sk_backlog_rcv)(struct sock *sk,struct sk_buff *skb);    
  10. };  


这里我们一个个来说。 

sk_sleep是一个等待队列,也就是所有阻塞在这个sock上的进程,我们通知用户进程就是通过这个等待队列来做的。 

sk_state_change是一个sock状态改变的回调函数,也就是当sock的状态变迁了(比如从established到clos_wait状态),那么就会调用这个函数。 

sk_data_ready 这个函数是当当前sock有可读数据的时候,就会被调用。 

sk_write_space 这个函数是当当前的sock有可写的空间的时候,就会被调用。 

sk_error_report 这个函数是当当前的sock出错(比如收到一个rst)后就会被调 
用. 

接下来我们就来看这几个函数的具体实现。 

在看之前我们先来看一个sk_has_sleeper的实现,它的作用就是用来判断是否有进程阻塞在当前的sock,也就是sk_slleep这个等待队列上是否有元素。 

我们每次唤醒用户进程之前都会调用这个函数进行判断,如果没有那就不用唤醒进程了。 

Java代码   收藏代码
  1. static inline int sk_has_sleeper(struct sock *sk)  
  2. {  
  3.     smp_mb__after_lock();  
  4. ///判断等待队列是否有元素。  
  5. return sk->sk_sleep && waitqueue_active(sk->sk_sleep);  
  6. }  


先来看sock_def_wakeup,这个函数用来唤醒所有阻塞在当前的sock的进程。 

Java代码   收藏代码
  1. static void sock_def_wakeup(struct sock *sk)  
  2. {  
  3.     read_lock(&sk->sk_callback_lock);  
  4. ///先检测是否有进程阻塞在当前的sock上,如果有才会去唤醒等待队列。  
  5.     if (sk_has_sleeper(sk))  
  6.         wake_up_interruptible_all(sk->sk_sleep);  
  7.     read_unlock(&sk->sk_callback_lock);  
  8. }  


然后是sk_data_ready,这个是用来发起可读事件的。 

Java代码   收藏代码
  1. static void sock_def_readable(struct sock *sk, int len)  
  2. {  
  3.     read_lock(&sk->sk_callback_lock);  
  4. ///首先判断是否有进程休眠在sock上。如果有则同步唤醒所有的阻塞的进程,这里注意传递的参数是POLLIN,这样我们就能通过epoll这类来捕捉事件了。  
  5.     if (sk_has_sleeper(sk))  
  6.            wake_up_interruptible_sync_poll(sk->sk_sleep, POLLIN | POLLRDNORM | POLLRDBAND);  
  7. ///这里主要是处理异步的唤醒事件。  
  8.     sk_wake_async(sk, SOCK_WAKE_WAITD, POLL_IN);  
  9.     read_unlock(&sk->sk_callback_lock);  
  10. }  


接下来是sock_def_write_space,它用来发起可写事件。 

可以看到这个实现和前面的有所不同,它会先判断已提交的内存大小sk_wmem_alloc的2倍和sk的发送缓冲区(sk_sndbuf),如果大于sndbuf,则不要唤醒。这是因为为了防止太小的缓冲区导致每次写只能写一部分,从而效率太低。 

Java代码   收藏代码
  1. static void sock_def_write_space(struct sock *sk)  
  2. {  
  3.     read_lock(&sk->sk_callback_lock);  
  4.   
  5.     /* Do not wake up a writer until he can make "significant" 
  6.      * progress.  --DaveM 
  7.      */  
  8. ///先判断内存使用。  
  9.     if ((atomic_read(&sk->sk_wmem_alloc) << 1) <= sk->sk_sndbuf) {  
  10. ///判断是否有需要被唤醒的进程。  
  11.     if (sk_has_sleeper(sk))  
  12. ///同步的唤醒进程,可以看到事件是POLLOUT.  
  13.            wake_up_interruptible_sync_poll(sk->sk_sleep, POLLOUT |  
  14.                         POLLWRNORM | POLLWRBAND);  
  15. ///这里也是处理一些异步唤醒。  
  16.         if (sock_writeable(sk))  
  17.             sk_wake_async(sk, SOCK_WAKE_SPACE, POLL_OUT);  
  18.     }  
  19.   
  20.     read_unlock(&sk->sk_callback_lock);  
  21. }  


最后是sock_def_error_report,他是当sock有错误时会被调用来唤醒相关的进程。 

Java代码   收藏代码
  1. static void sock_def_error_report(struct sock *sk)  
  2. {  
  3.     read_lock(&sk->sk_callback_lock);  
  4.   
  5. ///这里只需要注意上报的事件是POLL_ERR。  
  6.     if (sk_has_sleeper(sk))  
  7.         wake_up_interruptible_poll(sk->sk_sleep, POLLERR);  
  8. ///处理异步的唤醒。  
  9.     sk_wake_async(sk, SOCK_WAKE_IO, POLL_ERR);  
  10.     read_unlock(&sk->sk_callback_lock);  
  11. }  


这里我们看到所有的处理最终都会调用sk_wake_async来处理异步的事件。这个函数其实比较简单,主要是处理如果我们在用户空间设置了相关的句柄的O_ASYNC属性时,也就是信号io,我们需要单独的处理信号io。 

Java代码   收藏代码
  1. int sock_wake_async(struct socket *sock, int how, int band)  
  2. {  
  3.     if (!sock || !sock->fasync_list)  
  4.         return -1;  
  5. ///通过how的不同来进行不同的处理,这里每次都要先测试flags的状态。  
  6.     switch (how) {  
  7.     case SOCK_WAKE_WAITD:  
  8.         if (test_bit(SOCK_ASYNC_WAITDATA, &sock->flags))  
  9.             break;  
  10.         goto call_kill;  
  11.     case SOCK_WAKE_SPACE:  
  12.         if (!test_and_clear_bit(SOCK_ASYNC_NOSPACE, &sock->flags))  
  13.             break;  
  14.         /* fall through */  
  15.     case SOCK_WAKE_IO:  
  16. call_kill:  
  17. ///发送信号给应用进程。(SIGIO)  
  18.         __kill_fasync(sock->fasync_list, SIGIO, band);  
  19.         break;  
  20.     case SOCK_WAKE_URG:  
  21. ///这里是发送urgent信号(SIGURG)给应用进程。  
  22.         __kill_fasync(sock->fasync_list, SIGURG, band);  
  23.     }  
  24.     return 0;  
  25. }  


看完这些函数,我们再来看这些函数什么时候会被调用并且调用后如何被select,epoll这类的框架所捕捉。 

先来看sk_state_change(也就是sock_def_wakeup),直接搜索内核代码。可以看到有6个地方调用了sk_state_change函数,分别是inet_shutdown,tcp_done,tcp_fin,tcp_rcv_synsent_state_process以及tcp_rcv_state_process函数。可以看到这些基本都是tcp状态机的的状态变迁的地方,也就是每次状态的变迁都会调用state_change函数。 

我们一个个来看,有些可能前面已经分析过了,这里就简要介绍下。先是inet_shutdown. 

这个函数我们知道系统调用shutdown最终会调用到这个函数: 

Java代码   收藏代码
  1. int inet_shutdown(struct socket *sock, int how)  
  2. {  
  3.     struct sock *sk = sock->sk;  
  4. .......................................  
  5. ///唤醒等待的进程。  
  6.     sk->sk_state_change(sk);  
  7.     release_sock(sk);  
  8.     return err;  
  9. }  


然后是tcp_done,这个函数也就是在整个tcp连接完全断开或者说当前的sock没有和任何进程相关联(SOCK_DEAD)的状态,或者收到reset时,所做的工作。要注意这个函数不会free buffer的,它只是处理状态机的一些东西: 

Java代码   收藏代码
  1. void tcp_done(struct sock *sk)  
  2. {  
  3.     if (sk->sk_state == TCP_SYN_SENT || sk->sk_state == TCP_SYN_RECV)  
  4.         TCP_INC_STATS_BH(sock_net(sk), TCP_MIB_ATTEMPTFAILS);  
  5.   
  6. ///最终状态机回到初始状态(也就是接到最后一个ack).  
  7.     tcp_set_state(sk, TCP_CLOSE);  
  8. ///清理定时器  
  9.     tcp_clear_xmit_timers(sk);  
  10. ///设置shutdown的状态。  
  11.     sk->sk_shutdown = SHUTDOWN_MASK;  
  12.   
  13. ///如果还有进程和它相关联则调用state_change来唤醒相关进程。  
  14.     if (!sock_flag(sk, SOCK_DEAD))  
  15.         sk->sk_state_change(sk);  
  16.     else  
  17. ///否则destroy这个sock  
  18.         inet_csk_destroy_sock(sk);  
  19. }  


然后是tcp_fin函数,我们要重点来看这个函数,这个函数代表我们接受到了一个fin分节。 

1 我们知道当服务器端收到一个fin后会,它会发送一个ack,然后通知应用程序,紧接着进入close_wait状态,等待应用程序的关闭。 

2 当我们处于TCP_FIN_WAIT1状态(客户端)时,如果收到一个fin,会进入tcp_closing状态。 

3 当我们处于TCP_FIN_WAIT2状态(客户端)时,如果收到fin,则会进入time_wait状态。 

Java代码   收藏代码
  1. static void tcp_fin(struct sk_buff *skb, struct sock *sk, struct tcphdr *th)  
  2. {  
  3.     struct tcp_sock *tp = tcp_sk(sk);  
  4.   
  5.   
  6.     inet_csk_schedule_ack(sk);  
  7.   
  8.     sk->sk_shutdown |= RCV_SHUTDOWN;  
  9. ///设置sock的状态。  
  10.     sock_set_flag(sk, SOCK_DONE);  
  11.   
  12. ///不同的状态进入不同的处理  
  13.     switch (sk->sk_state) {  
  14. ///syn_recv状态和establish状态的处理是相同的。  
  15.     case TCP_SYN_RECV:  
  16.     case TCP_ESTABLISHED:  
  17.         /* Move to CLOSE_WAIT */  
  18. ///直接进入close_wait状态  
  19.         tcp_set_state(sk, TCP_CLOSE_WAIT);  
  20. ///设置pingpong,也就是这个时候所有数据包都是立即ack  
  21.         inet_csk(sk)->icsk_ack.pingpong = 1;  
  22.         break;  
  23. ///处于这两个状态,说明这个fin只不过是个重传数据包。  
  24.     case TCP_CLOSE_WAIT:  
  25.     case TCP_CLOSING:  
  26.   
  27.         break;  
  28.     case TCP_LAST_ACK:  
  29.         /* RFC793: Remain in the LAST-ACK state. */  
  30.         break;  
  31.   
  32. ///我们在等待fin,  
  33.     case TCP_FIN_WAIT1:  
  34. ///因此我们先发送ack,给对端,然后设置状态为closing状态。  
  35.         tcp_send_ack(sk);  
  36.         tcp_set_state(sk, TCP_CLOSING);  
  37.         break;  
  38.   
  39. ///这个状态说明我们已经接到ack,在等待最后的fin。  
  40.     case TCP_FIN_WAIT2:  
  41. ///发送ack给对方,  
  42.         tcp_send_ack(sk);  
  43. //然后进入time-wait状态,并启动定时器。  
  44.         tcp_time_wait(sk, TCP_TIME_WAIT, 0);  
  45.         break;  
  46.     default:  
  47. ///其他的状态收到都是错误的。  
  48.         printk(KERN_ERR "%s: Impossible, sk->sk_state=%d\n",  
  49.                __func__, sk->sk_state);  
  50.         break;  
  51.     }  
  52.   
  53. ///可以看到一接到fin,就会马上清理调ofo队列。  
  54.     __skb_queue_purge(&tp->out_of_order_queue);  
  55.     if (tcp_is_sack(tp))  
  56.         tcp_sack_reset(&tp->rx_opt);  
  57.     sk_mem_reclaim(sk);  
  58.   
  59. ///这里判断是否有进程和sock关联。  
  60.     if (!sock_flag(sk, SOCK_DEAD)) {  
  61. ///ok,现在通知进程状态的改变。  
  62.         sk->sk_state_change(sk);  
  63. ///这里注意如果是两端只有一端关闭,则是不会发送poll_hup而是发送poll_in也就是可读事件(这里也只是处理信号io的东西)  
  64.         if (sk->sk_shutdown == SHUTDOWN_MASK ||  
  65.             sk->sk_state == TCP_CLOSE)  
  66.             sk_wake_async(sk, SOCK_WAKE_WAITD, POLL_HUP);  
  67.         else  
  68.             sk_wake_async(sk, SOCK_WAKE_WAITD, POLL_IN);  
  69.     }  
  70. }  


然后就是tcp_rcv_synsent_state_process函数,这个函数主要用来处理syn_sent状态的数据。这个函数前面的blog已经介绍过了,这里就不介绍了,这里它会在接受到ack之后调用state_change函数,也就是要进入established状态。 

接下来就是tcp_rcv_state_process函数中的两个调用的地方,第一个地方是当处于TCP_SYN_RECV状态然后接受到一个ack,此时需要进入established状态,则会调用state_change函数. 

这里要注意的是第二个地方,那就是当处于TCP_FIN_WAIT1,然后接受到了一个ack,此时由于应用程序调用close时,设置了linger套接口选项,因此我们这里有可能需要唤醒休眠等待的进程。 

接下来的可读和可写的回调函数我就简单的介绍下,先来看可读函数sk_data_ready。它会在收到urgent数据后马上调用(tcp_urg中).还有就是dma中,再就是tcp_rcv_established中,我们主要来看这个,因为这个是我们最主要的接收函数。 

这里要知道我们是处于软中断上下文中,然后调用data_ready来通知应用程序的。 
来看代码片断: 

Java代码   收藏代码
  1. if (eaten)  
  2.                 __kfree_skb(skb);  
  3.             else  
  4.                 sk->sk_data_ready(sk, 0);  


可以看到只有当eaten为0时才会唤醒等待队列。并且这段代码是处于fast path中的。而eaten为0说明拷贝给用户空间失败。 

然后是sk_write_space函数,这个函数主要是在tcp_new_space中以及sock_wfree中被调用。 

这两个函数第一个是当接收到一个ack之后,我们能从write_queue中删除这条报文的时候被调用。 

第二个函数是当free写buff的时候被调用。 


接下来来详细看sk_error_report 回调函数,这个函数,我们主要来看两个调用它的地方,一个是tcp_disconnect中,一个是tcp_reset中。 

第一个函数是用来断开和对端的连接。第二个函数是处理rst分节的。 

第一个函数就不介绍了,我前面的blog已经分析过了,这个函数被调用,是当我们自己调用close的时候或者说当前的sock要被关闭的时候会调用这个函数。 

来看第二个函数,也就是处理rst分节的部分。 

这里可以看到是先设置错误号,也就是防止应用程序读写错误的sock,然后发送给应用程序不同的信号。 
Java代码   收藏代码
  1. static void tcp_reset(struct sock *sk)  
  2. {  
  3. ///不同的状态设置不同的错误号  
  4.     switch (sk->sk_state) {  
  5.     case TCP_SYN_SENT:  
  6.         sk->sk_err = ECONNREFUSED;  
  7.         break;  
  8.     case TCP_CLOSE_WAIT:  
  9.         sk->sk_err = EPIPE;  
  10.         break;  
  11.     case TCP_CLOSE:  
  12.         return;  
  13.     default:  
  14. ///其他状态都是ECONNRESET。  
  15.         sk->sk_err = ECONNRESET;  
  16.     }  
  17.   
  18. ///传递错误给应用程序。  
  19.     if (!sock_flag(sk, SOCK_DEAD))  
  20.         sk->sk_error_report(sk);  
  21.   
  22.     tcp_done(sk);  
  23. }  


这里我们来看这几个错误号,可以看到 

1 当TCP_SYN_SENT状态时如果收到rst,则会设置错误号为ECONNREFUSED,并唤醒进程。 

2 当为TCP_CLOSE_WAIT状态时,则设置错误号为EPIPE。 

3 剩余的状态的话错误号都为 ECONNRESET。 

接下来我们就来看当读或者写已经收到rst的sock会出现什么情况。 

先是写函数,前面的blog我们知道写函数是tcp_sendmsg。我们来看代码片断: 

Java代码   收藏代码
  1. ///如果是established或者close_wait状态则进入wait_connect处理,我们上面可以看到(tcp_reset)接收到rst后,我们会通过tcp_done设置状态为tcp_close,所以我们一定会进入这里。  
  2. if ((1 << sk->sk_state) & ~(TCPF_ESTABLISHED | TCPF_CLOSE_WAIT))  
  3. ///这里不等于0也就是表示sk_err有值。接下来会详细分析这个函数。  
  4.     if ((err = sk_stream_wait_connect(sk, &timeo)) != 0)  
  5.             goto out_err;  
  6.   
  7. ..................................  
  8. ...............................................  
  9. out_err:  
  10. ///调用这个函数进行处理  
  11.     err = sk_stream_error(sk, flags, err);  



在看这个函数之前我们先来看sock_error函数,这个用来返回sk->sk_err,然后清除这个error。 

Java代码   收藏代码
  1. static inline int sock_error(struct sock *sk)  
  2. {  
  3.     int err;  
  4. ///如果为空则直接返回。  
  5.     if (likely(!sk->sk_err))  
  6.         return 0;  
  7. ///这个是用汇编实现的。返回sk_err然后设置sk_err为0.也就是清空。  
  8.     err = xchg(&sk->sk_err, 0);  
  9. //返回错误号,这里主要加了个负号。  
  10.     return -err;  
  11. }  


然后我们来看sk_stream_wait_connect的实现。我们这里只看他的错误处理部分。 

Java代码   收藏代码
  1. int sk_stream_wait_connect(struct sock *sk, long *timeo_p)  
  2. {  
  3.     struct task_struct *tsk = current;  
  4.     DEFINE_WAIT(wait);  
  5.     int done;  
  6.   
  7.     do {  
  8. ///首先取得sk_err,然后清空sk_err  
  9.         int err = sock_error(sk);  
  10. ///如果err存在则直接返回。  
  11.         if (err)  
  12.             return err;  
  13. //到达这里说明err是0,如果状态不是sent或者recv则返回-EPIPE.  
  14.         if ((1 << sk->sk_state) & ~(TCPF_SYN_SENT | TCPF_SYN_RECV))  
  15.             return -EPIPE;  
  16. .......................................  
  17.   
  18.     } while (!done);  
  19.     return 0;  
  20. }  

这里为什么要判断完err之后还要再次判断状态呢,等下面我们分析sk_stream_error函数之后一起来看。 

然后来看sk_stream_error函数,要知道err这里传递进来的是wait_connect返回的值. 

Java代码   收藏代码
  1. int sk_stream_error(struct sock *sk, int flags, int err)  
  2. {  
  3. ///如果传递进来的err是-EPIPE,进入第一个处理。  
  4.     if (err == -EPIPE)  
  5. //如果sk_err没有被清掉,则返回sk_err否则返回-EPIPE.  
  6.         err = sock_error(sk) ? : -EPIPE;  
  7. //如果是EPIPE的话,会直接发送信号给应用程序  
  8.     if (err == -EPIPE && !(flags & MSG_NOSIGNAL))  
  9.         send_sig(SIGPIPE, current, 0);  
  10. ///返回错误号  
  11.     return err;  
  12. }  


接下来我们把两个函数一起来看。 

当我们收到一个rst之后我们设置状态为tcp_close.然后设置相应的错误号。 

假设现在我们在收到rst的sock上调用send方法,此时我们会进入sk_stream_wait_connect,然后直接返回err。现在就有3种情况: 

1 我们在TCP_SYN_SENT状态收到的rst,此时的错误号为ECONNREFUSED,因此我们会返回ECONNREFUSED,设置err为ECONNREFUSED,然后进入sk_stream_error处理。由于我们的err并不等于EPIPE,因此我们会直接返回错误号。也就是应用进程回收到错误号。不过此时sk_err已经清0了。 

2 当我们在TCP_CLOSE_WAIT收到rst,此时的错误号为EPIPE,因此我们进入sk_stream_error的时候,err就是EPIPE,此时我们就会发送EPIPE信号给进程,并返回EPIPE的错误号。 

3 我们在其他状态收到rst,此时错误号为ECONNRESET,可以看到这个的处理和上面TCP_SYN_SENT状态收到的rst的处理一致,只不过返回的错误号不一样罢了。 

紧接着,我们来看当我们第一次调用完毕之后,第二次调用会发生什么。这里可以看到只有上面的1,3两种情况第二次调用才会有效果。 

当我们再次进入sk_stream_wait_connect,此时由于sk_err已经被清0,因此我们会进入第二个处理也就是状态判断,由于我们是tcp_close状态,因此我们会直接返回-EPIPE.此时我们出来之后,会再次进入sk_stream_error,而之后调用就和上面的2 一样了。会直接发送epipe信号,然后返回epipe. 


然后是tcp_recvmsg,也就是接收函数。 

下面是代码片断。这里要知道,就算收到rst,如果sock没有完全关闭,我们还是可以从缓冲区读取数据的。 

这个函数我前面的blog已经介绍过了。详细的介绍可以看我前面的blog。 

Java代码   收藏代码
  1. //copied为已经复制的数据。这里可以看到如果已经复制了一些数据,并且sk_err有值,则直接跳出循环。  
  2.         if (copied) {  
  3.             if (sk->sk_err ||  
  4.                 sk->sk_state == TCP_CLOSE ||  
  5.                 (sk->sk_shutdown & RCV_SHUTDOWN) ||!timeo ||signal_pending(current))  
  6.                 break;  
  7.         } else {  
  8.             if (sock_flag(sk, SOCK_DONE))  
  9.                 break;  
  10. ///如果没有值,可以看到cpoied直接被设置为错误号。  
  11.             if (sk->sk_err) {  
  12.                 copied = sock_error(sk);  
  13.                 break;  
  14.             }  
  15. ///到这里copied为0  
  16.             if (sk->sk_shutdown & RCV_SHUTDOWN)  
  17.                 break;  


这个函数最终的返回值是copied,也就是说当缓冲区有数据,我们的返回值为拷贝完的数据,而没有数据的话,直接会返回错误号。也就是说是ECONNREFUSED,EPIPE或者ECONNRESET。而当再次调用,还是没有数据的话,会直接返回0. 

最后我们来看tcp_poll的实现,这个函数也就是当sock的等待队列有事件触发式会被调用的。有关slect和epoll的源码分析可以看这个: 

http://docs.google.com/Doc?docid=0AZr7tK22PNlAZGRqdHZ4NHFfMTQ0Zmh0OTIzZzQ&hl=en 

http://docs.google.com/Doc?docid=0AZr7tK22PNlAZGRqdHZ4NHFfMTQ2ZnIzMmtwaHM&hl=en 

ok,我们来看tcp_poll.它的功能很简单,就是得到触发的事件掩码,然后返回给 
select,poll或者epoll。 

我们来看代码。这里要知道shutdown域,这个域主要是通过shutdown来设置。不过其他地方偶尔也会设置,比如收到fin(见上面的tcp_fin). 

Java代码   收藏代码
  1. unsigned int tcp_poll(struct file *file, struct socket *sock, poll_table *wait)  
  2. {  
  3.     unsigned int mask;  
  4.     struct sock *sk = sock->sk;  
  5.     struct tcp_sock *tp = tcp_sk(sk);  
  6.   
  7.     sock_poll_wait(file, sk->sk_sleep, wait);  
  8. ///如果是tcp_listen的话,单独处理。  
  9.     if (sk->sk_state == TCP_LISTEN)  
  10.         return inet_csk_listen_poll(sk);  
  11.   
  12.     mask = 0;  
  13. //如果sk_err有设置则添加POLLERR事件。  
  14.     if (sk->sk_err)  
  15.         mask = POLLERR;  
  16.   
  17.   
  18. //如果shutdown被设置,或者tcp状态为close,则添加POLLHUP  
  19.     if (sk->sk_shutdown == SHUTDOWN_MASK || sk->sk_state == TCP_CLOSE)  
  20.         mask |= POLLHUP;  
  21. ///如果shutdown被设置为RCV_SHUTDOWN则添加 POLLIN | POLLRDNORM | POLLRDHUP状态(我们通过上面知道,当接收到fin后就会设置为RCV_SHUTDOWN)。  
  22.     if (sk->sk_shutdown & RCV_SHUTDOWN)  
  23.         mask |= POLLIN | POLLRDNORM | POLLRDHUP;  
  24.   
  25. ///如果不是TCPF_SYN_SENT以及TCPF_SYN_RECV状态。  
  26.     if ((1 << sk->sk_state) & ~(TCPF_SYN_SENT | TCPF_SYN_RECV)) {  
  27.         int target = sock_rcvlowat(sk, 0, INT_MAX);  
  28.   
  29.         if (tp->urg_seq == tp->copied_seq &&  
  30.             !sock_flag(sk, SOCK_URGINLINE) &&  
  31.             tp->urg_data)  
  32.             target--;  
  33.   
  34.   
  35.         if (tp->rcv_nxt - tp->copied_seq >= target)  
  36.             mask |= POLLIN | POLLRDNORM;  
  37. ///如果没有设置send_shutdown.则进入下面的处理。  
  38.         if (!(sk->sk_shutdown & SEND_SHUTDOWN)) {  
  39.   
  40. ///如果可用空间大于最小的buf,则添加POLLOUT | POLLWRNORM事件。  
  41.             if (sk_stream_wspace(sk) >= sk_stream_min_wspace(sk)) {  
  42.                 mask |= POLLOUT | POLLWRNORM;  
  43.             } else {  /* send SIGIO later */  
  44.   
  45. //设置flag的标记位  
  46.         set_bit(SOCK_ASYNC_NOSPACE,     &sk->sk_socket->flags);  
  47.         set_bit(SOCK_NOSPACE, &sk->sk_socket->flags);  
  48.   
  49. //再次判断,因为有可能此时又有空间了。  
  50.     if (sk_stream_wspace(sk) >= sk_stream_min_wspace(sk))  
  51.                     mask |= POLLOUT | POLLWRNORM;  
  52.             }  
  53.         }  
  54. //如果有紧急(urgent)数据,添加POLLPRI事件。  
  55.         if (tp->urg_data & TCP_URG_VALID)  
  56.             mask |= POLLPRI;  
  57.     }  
  58.     return mask;  
  59. }  
  • 0
    点赞
  • 8
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值