网上找了很多文章,对于基础不太好的我来说,还是不懂到底什么时候skb挂到prequeue中,又是什么时候挂到backlog中,或者receive中。
于是只能不停的看代码,尽量每一行都能看懂。功夫不负有心人,终于理解了。在此做一下笔记。
首先,有2种方式表示标识用户正在调用recv函数,及内核的tcp_recvmsg:
1:sk->sk_lock.owned,其值为1表示有进程进入了tcp_recvmsg函数,并且执行了lock_sock。
注意:sk->sk_lock.owned其值为0,肯定是被release了,但是tcp_recvmsg函数中,并不是仅仅执行完函数,退出时,才会执行release。
在sk_wait_data函数中,让进程休眠时,也会release。
2:tp->ucopy.task
不等于null,表示进程正在读数据。
1和2的各个组合,正好能够表示进程的状态:
组合1:
sk->sk_lock.owned ==0 && tp->ucopy.task == null
要么就是压根没调用tcp_recvmsg,要么是调用后,退出了tcp_recvmsg
组合2:
sk->sk_lock.owned ==1 && tp->ucopy.task == null
执行中,即进程上下文中,进程已经进入tcp_recvmsg,但是还没读,准备读。
组合3:
sk->sk_lock.owned ==1 && && tp->ucopy.task ==null
进程进入tcp_recvmsg,并且正在读。
组合4:
sk->sk_lock.owned ==0 && && tp->ucopy.task != null
组合4最最重要!!!!!
tcp_do_rcv函数中,当sk->sk_lock.owned ==0 时,就会去执行tcp_prequeue。
tcp_prequeue中,就是判断如果tp->ucopy.task != null,就把skb挂到prequeue中。
而组合4出现的愿意,就是recv系统调用调用tcp_reccmsg之后,发现skb中的字节数,不够我们需要拷贝的字节数,导致进程休眠,即阻塞形系统调用导致进程休眠,
这种情况下,会将skb挂到prequeue中。
如果sk->sk_lock.owned ==0并且tp->ucopy.task == null,那就乖乖的挂到receive中。
如果sk->sk_lock.owned ==1 ,表示进程正在处于recv调用中,可能在执行代码的某个部分,反正在执行,进程没有休眠,也没有退出。。等等,为了软中断和进程上下文同步,将收到的skb挂到backlog中。进程在release_sock()中会处理backlog队列中的数据。
/* Packet is added to VJ-style prequeue for processing in process * context, if a reader task is waiting. Apparently, this exciting * idea (VJ's mail "Re: query about TCP header on tcp-ip" of 07 Sep 93) * failed somewhere. Latency? Burstiness? Well, at least now we will * see, why it failed. 8)8) --ANK * * NOTE: is this not too big to inline? */
//能进入这个函数,表示sk->sk_lock.owned ==0 static inline int tcp_prequeue(struct sock *sk, struct sk_buff *skb) { struct tcp_sock *tp = tcp_sk(sk); if (sysctl_tcp_low_latency || !tp->ucopy.task) return 0; //sysctl_tcp_low_latency 默认为0,!tp->ucopy.task不等于null,表示正在读取数据,而进入函数的条件又是进程没有锁住,
//出现这种现象的唯一情况,就是进程没有读够想要的数据,休眠了 __skb_queue_tail(&tp->ucopy.prequeue, skb); tp->ucopy.memory += skb->truesize; if (tp->ucopy.memory > sk->sk_rcvbuf) { struct sk_buff *skb1; BUG_ON(sock_owned_by_user(sk)); while ((skb1 = __skb_dequeue(&tp->ucopy.prequeue)) != NULL) { sk_backlog_rcv(sk, skb1); NET_INC_STATS_BH(sock_net(sk), LINUX_MIB_TCPPREQUEUEDROPPED); } tp->ucopy.memory = 0; } else if (skb_queue_len(&tp->ucopy.prequeue) == 1) {
//唤醒进程,进程立刻读取prequeue中的数据。 wake_up_interruptible_poll(sk->sk_sleep, POLLIN | POLLRDNORM | POLLRDBAND); if (!inet_csk_ack_scheduled(sk)) inet_csk_reset_xmit_timer(sk, ICSK_TIME_DACK, (3 * tcp_rto_min(sk)) / 4, TCP_RTO_MAX); } return 1; }