已经进行到这里,接下来需要讲的就是tcp的recv如何读取到内核的数据的了。
一 先看看struct sock的缓存队列sk_receive_queue,
这个队列就是内核的软中断上下文和进程空间的tcp_recvmsg(应用层调用tcp_recv)之间通信的队列。
因此,我们只需要找到哪里写队列,哪里读队列的就好了。
二 继续上一节的软中断上下文处理
tcp_v4_rcv()//主要流程
{
//如果失败,直接调用tcp_v4_do_rcv()存储到struct sock的sk_receive_queue中
if (!tcp_prequeue(sk, skb))//数据放到prequeue中,如果失败了,直接放到receive_queue中
ret = tcp_v4_do_rcv(sk, skb);
}
bool tcp_prequeue(struct sock *sk, struct sk_buff *skb)
{//最终也是从prequeue取出数据,转移到sk_receive_queue中
while ((skb1 = __skb_dequeue(&tp->ucopy.prequeue)) != NULL)
sk_backlog_rcv(sk, skb1);
{
sk_backlog_rcv(sk,skb);//函数指针tcp_v4_do_rcv()
}
}
//上面应该是做一个缓冲,避免应用层读取太慢了,sk_receive_queue存不下了
int tcp_v4_do_rcv(struct sock *sk, struct sk_buff *skb)
{
tcp_rcv_established();//这里我们仅仅看ESTABLISHED状态,
{
eaten = tcp_queue_rcv(sk, skb, tcp_header_len,
&fragstolen);
{
__skb_queue_tail(&sk->sk_receive_queue, skb);
}
}
}
三 再看tcp_recvmsg函数调用
int tcp_recvmsg(struct sock *sk, struct msghdr *msg, size_t len, int nonblock,
int flags, int *addr_len)
{
timeo = sock_rcvtimeo(sk, nonblock);//获取超时时间,如果是非阻塞为0
seq = &tp->copied_seq;//读取数据起始seq
target = sock_rcvlowat(sk, flags & MSG_WAITALL, len);//socket设置了,MSG_WAITALL选项,要等到数据满足接收长度的条件的时候,才返回,下面会挂起cpu
skb_queue_walk(&sk->sk_receive_queue, skb) //这是一个宏,循环读取sk_receive_queue中的skb
{
///由于tcp是字节流,因此我们拷贝给用户空间,需要正序的拷贝给用户,这里的第一个seq前面已经描述了,表示当前的总的sock连接中的未读数据的起始序列号,而后一个seq表示当前skb的起始序列号。因此这个差值如果小于skb->len,就表示,当前的skb就是我们需要读取的那个skb(因为它的序列号最小).
offset = *seq - TCP_SKB_CB(skb)->seq;
}
//接下来是清理读取一部分sk_receive_queue后的清理工作。可以自行阅读源码
}
四 下一节我们讲解,软中断如何激活socket描述符的,以及epoll和select实现机制。