第八章 tcp接(传输层)--基于Linux 3.10

在IP层的接收,提到其调用的函数是inttcp_v4_rcv(struct sk_buff *skb),其参数是包含数据信息的sk_buff。这个函数定义于:

net/ipv4/tcp_ipv4.c 
1961 int tcp_v4_rcv(struct sk_buff *skb)
1962 {
1963     const struct iphdr *iph;         //ip层头标识字段
1964     const struct tcphdr *th;         //tcp层头标识字段
1965     struct sock *sk;                    
1966     int ret;
1967     struct net *net = dev_net(skb->dev); //命名空间会使用到该结构体
//PACKET_HOST指示该数据包目的是本机,类似的还有PACKET_BROADCAST等
1969     if (skb->pkt_type != PACKET_HOST)  
1970         goto discard_it;
1971 
1972     /* Count it even if it's bad */
1973     TCP_INC_STATS_BH(net, TCP_MIB_INSEGS);  // 统计信息更新
1974  
/*判断segment的头长度是否合法,有三种情况: 
1、sizeof(struct tcphdr) <= skb的tcp头长度;则满足segment的tcp头长度要求。通常都会满足该要求。 
2、 sizeof(struct tcphdr) > skb头+数据长度之和;出错丢弃该包。 
3、 介于1、2两种情况之间的长度,这种情况在数据包分片时会遇到。 
*/
1975     if (!pskb_may_pull(skb, sizeof(struct tcphdr)))  
1976         goto discard_it;
1977  
/*获得传输层头*/
1978     th = tcp_hdr(skb);  
1979  
/*再一次判断数据包的合法性,doff是tcp包中记录的该packet的首部长度,*/
1980     if (th->doff < sizeof(struct tcphdr) / 4)
1981         goto bad_packet;
1982     if (!pskb_may_pull(skb, th->doff * 4))
1983         goto discard_it;
1984 
1985     /* An explanation is required here, I think.
1986      * Packet length and doff are validated by header prediction,
1987      * provided case of th->doff==0 is eliminated.
1988      * So, we defer the checks. */ 
/*skb->csum:存放硬件或者软件计算的payload的checksum不包括伪头,但是是否有意义由skb->ip_summed的值决定。 
CHECKSUM_UNNECESSARY表示网卡或者协议栈已经计算和验证了L4层的头和校验值。也就是计算了tcp udp的伪头。 
CHECKSUM_COMPLETE表示硬件进行了计算,计算结果存储在skb->csum中。 
如果硬件完成了tcp的头校验,那么skb->ip_summed & CHECKSUM_UNNECESSARY,的值等于1,这就是skb_csum_unnecessary的返回值,否则调用tcp_v4_checksum_init判断硬件是否有该特性,如果有则使用硬件计算,如果没有只能多消耗点cpu完成计算了。
*/ 
1989     if (!skb_csum_unnecessary(skb) && tcp_v4_checksum_init(skb))
1990         goto csum_error;
1991  
/*这里再一次获得tcp的头,由于tcp_hdr返回的是指针,并且在计算校验和时可能更改过地址,所以这里再一次赋值*/
1992     th = tcp_hdr(skb);
1993     iph = ip_hdr(skb);  
/*TCP 控制块初始化,这里初始化使用的都是tcp头中字段信息*/
1994     TCP_SKB_CB(skb)->seq = ntohl(th->seq);
1995     TCP_SKB_CB(skb)->end_seq = (TCP_SKB_CB(skb)->seq + th->syn + th->fin +
1996                     skb->len - th->doff * 4);
1997     TCP_SKB_CB(skb)->ack_seq = ntohl(th->ack_seq);
1998     TCP_SKB_CB(skb)->when    = 0;
1999     TCP_SKB_CB(skb)->ip_dsfield = ipv4_get_dsfield(iph);
2000     TCP_SKB_CB(skb)->sacked  = 0;
2001  
/*查找对应的skb,也就是用户空间使用socket()创建的套接字在内核中的对应的sock*/
2002     sk = __inet_lookup_skb(&tcp_hashinfo, skb, th->source, th->dest);
2003     if (!sk)
2004         goto no_tcp_socket;
2005 
2006 process:  
/*如果这是主动关闭了套接字,则套接字的状态就是TCP_TIME_WAIT,被动的一端状态是TCP_CLOSE*/
2007     if (sk->sk_state == TCP_TIME_WAIT)
2008         goto do_time_wait;
2009  
/*查看其生存时间TTL(time to live)是否超时,如果超时统计一下然后丢弃*/
2010     if (unlikely(iph->ttl < inet_sk(sk)->min_ttl)) {
2011         NET_INC_STATS_BH(net, LINUX_MIB_TCPMINTTLDROP);
2012         goto discard_and_relse;
2013     }
2014  
/*当内核定义了CONFIG_XFRM变量时,会启用xfrm安全检查,对于不满足安检的丢弃。*/
2015     if (!xfrm4_policy_check(sk, XFRM_POLICY_IN, skb))
2016         goto discard_and_relse;  
/*和桥接防火墙相关的,如果配置实用桥接防火墙,则skb中的nfct、nfct_reasm、nf_bridge字段会被使用到*/
2017     nf_reset(skb);
2018 
/*套接字过滤器,如果pkt_len是0,则要丢弃该包,如果skb->len小于pkt_len则要接收该数据包,继续往下走*到2022行。/
2019     if (sk_filter(sk, skb))
2020         goto discard_and_relse;
2021 
/*dev是net_device结构体,在之一文章中对该结构体已经详细描述过了*/
2022     skb->dev = NULL;
2023  
/*获得套接字的嵌套自旋锁*/
2024     bh_lock_sock_nested(sk);
2025     ret = 0;  
/*sock_owned_by_user会判断(sk)->sk_lock.owned,该字段用于指示sock目前是否处于用户空间,在BH或者中断情况是没有被这个函数(进程)own的,之一的文章中的NAPI就会启用BH,BH中的数据是没有被用户对应的进程拥有*/
2026     if (!sock_owned_by_user(sk)) { 
/*没有被own的情况处理,如果是支持DMA操作的,则net_dma_find_channel获得DMA通道号,然后调用tcp_v4_do_rcv进行实际的数据接收*/
2027 #ifdef CONFIG_NET_DMA
2028         struct tcp_sock *tp = tcp_sk(sk);
2029         if (!tp->ucopy.dma_chan && tp->ucopy.pinned_list)
2030             tp->ucopy.dma_chan = net_dma_find_channel();
2031         if (tp->ucopy.dma_chan)
2032             ret = tcp_v4_do_rcv(sk, skb);
2033         else
2034 #endif
2035         {
2036             if (!tcp_prequeue(sk, skb))
2037                 ret = tcp_v4_do_rcv(sk, skb);
2038         } 
/*已经被own的情况处理,需要持有per-socket锁,sk_add_backlog注释见后面*/
2039     } else if (unlikely(sk_add_backlog(sk, skb,
2040                        sk->sk_rcvbuf + sk->sk_sndbuf))) {
2041         bh_unlock_sock(sk);
2042         NET_INC_STATS_BH(net, LINUX_MIB_TCPBACKLOGDROP);
2043         goto discard_and_relse;
2044     } 
/*释放前面获得的锁*/
2045     bh_unlock_sock(sk);
2046 
/*减少sock的引用计数*/ 
2047     sock_put(sk);
2048 
2049     return ret;
2110 }     

该函数调用sk_add_backlog()接收数据包,其位于include/linux/sock.h;

backlog队列比较特殊,需要持有锁才能操作该队列,为了降低延迟,使用了一个技巧rmem_alloc用于64为体系结构的一个填充,和backlog本身没有关系。

  750 /* OOB backlog add */
 751 static inline void __sk_add_backlog(struct sock *sk, struct sk_buff *skb)
 752 {
 753     /* dont let skb dst not refcounted, we are going to leave rcu lock */
 754     skb_dst_force(skb);
 755 
/*将skb添加到backlog上,原链表为空添加方法*/
 756     if (!sk->sk_backlog.tail)
 757         sk->sk_backlog.head = skb;
 758     else
/*原链表非空的添加方法*/
 759         sk->sk_backlog.tail->next = skb;
 760 
 761     sk->sk_backlog.tail = skb;
 762     skb->next = NULL;
 763 }

 770 static inline bool sk_rcvqueues_full(const struct sock *sk, const struct sk_buff *skb,
 771                      unsigned int limit)
 772 {
/* sk->sk_backlog.len为backlog队列长度,sk_rmem_alloc是上面提到的填充字段,qsize是发送队列和接收队列的长度之和*/
 773     unsigned int qsize = sk->sk_backlog.len + atomic_read(&sk->sk_rmem_alloc);
 774 
 775     return qsize > limit;
 776 }
 777 
 778 /* The per-socket spinlock must be held here. */
 779 static inline __must_check int sk_add_backlog(struct sock *sk, struct sk_buff *skb,
 780                           unsigned int limit)
 781 {
/*队列长度不合理,则执行783行*/
 782     if (sk_rcvqueues_full(sk, skb, limit))
 783         return -ENOBUFS;
 784 
 785     __sk_add_backlog(sk, skb);
 786     sk->sk_backlog.len += skb->truesize;
 787     return 0;
 788 }   

到这里没有继续调用函数了,只看到数据放在了backlog上就没有继续显示调用其它函数进行进一步的接收工作。 

接下来从上至下看,在第五章中提到 tcp_recvmsg完成将数据由应用程序真正送入传输层的工作。

1560 int tcp_recvmsg(struct kiocb *iocb, struct sock *sk, struct msghdr *msg,
1561         size_t len, int nonblock, int flags, int *addr_len)
1562 {
/*判断sock的状态,TCP_LISTEN用于被动接收*/
1579     if (sk->sk_state == TCP_LISTEN)
1580         goto out;
1581 
/*应用设置的超时值在此设置,若未设置,则超时功能不启用*/
1582     timeo = sock_rcvtimeo(sk, nonblock);
1583 
1584     /* 紧急数据处理,OOB是out of band,带外信号,规范中使用带外传输紧急和重要的数据*/
1585     if (flags & MSG_OOB)
1586         goto recv_urg;
/*//待拷贝的下一个序列号*/
1603     seq = &tp->copied_seq; 
/*MSG_PEEK预读标志,MSG_PEEK标志会将套接字接收队列中的可读的数据拷贝到缓冲区,但不会使套接子接收队列中的数据减少,
下一次调用recv函数  
//仍然能够读到相同数据
*/
1604     if (flags & MSG_PEEK) {
1605         peek_seq = tp->copied_seq;
1606         seq = &peek_seq;
1607     }
/*应用程序设置MSG_WAITALL时,target等于len用户空间要接收的数据长度,否则置为1*/
1609     target = sock_rcvlowat(sk, flags & MSG_WAITALL, len); 
1633     do {
1634         u32 offset; 
1648         skb_queue_walk(&sk->sk_receive_queue, skb) {
/*已经接收到的segment和当前接收到的segment的序列号的差值*/
//如果用户的缓冲区(即用户malloc的buf)长度够大,offset一般是0。  
//即 “下次准备拷贝数据的序列号”==此时获取报文的起始序列号  
//什么情况下offset >0呢?很简答,如果用户缓冲区12字节,而这个skb有120字节  
//那么一次recv系统调用,只能获取skb中的前12个字节,下一次执行recv系统调用  
//offset就是12了,表示从第12个字节开始读取数据,前12个字节已经读取了。
//那这个"已经读取12字节"这个消息,存在哪呢?  
//在*seq = &tp->copied_seq;中
1658             offset = *seq - TCP_SKB_CB(skb)->seq;
1659             if (tcp_hdr(skb)->syn)
1660                 offset--;
1661             if (offset < skb->len)
1662                 goto found_ok_skb;
1663             if (tcp_hdr(skb)->fin)
1664                 goto found_fin_ok; 
1668         }
//执行到了这里,表明小循环中break了,既然break了,说明sk_receive_queue中  
//已经没有skb可以读取了  
//如果没有执行到这里说明前面的小循环中执行了goto,读到有用的skb,或者读到fin都会goto。  
//没有skb可以读取,说明什么?  
//可能性1:当用户第一次调用recv时,压根没有数据到来  
//可能性2:skb->len一共20字节,假设用户调用一次 recv,读取12字节,再调用recv,  
//读取12字节,此时skb由于上次已经被读取了12字节,只剩下8字节。  
//于是代码的逻辑上,再会要求获取skb,来读取剩下的8字节。  
  
//可能性1的情况下,copied == 0,肯定不会进这个if。后续将执行休眠  
//可能性2的情况下,情况比较复杂。可能性2表明数据没有读够用户想要的len长度  
//虽然进程上下文中,没有读够数据,但是可能我们在读数据的时候  
//软中断把数据放到backlog队列中了,而backlog对队列中的数据或许恰好让我们读够数  
//据。  
  
//copied了数据的,copied肯定>=1,而target 是1或者len  
//copied只能取0(可能性1),或者0~len(可能性2)  
//copied >= target 表示我们取得我们想要的数据了,何必进行休眠,直接return  
//如果copied 没有达到我们想要的数据,则看看sk_backlog是否为空  
//空的话,尽力了,只能尝试休眠  
//非空的话,还有一线希望,我们去sk_backlog找找数据,看看是否能够达到我们想要的  
//数据大小  
  
//我觉得copied == target是会出现的,但是出现的话,也不会进现在这个流程  
//说明情况下copied == target  
1670         /* Well, if we have backlog, try to process it now yet. */
1671 
1672         if (copied >= target && !sk->sk_backlog.tail)
1673             break;
1674 
1675         if (copied) {
//可能性2,拷贝了数据,但是没有拷贝到指定大小
1676             if (sk->sk_err ||
1677                 sk->sk_state == TCP_CLOSE ||
1678                 (sk->sk_shutdown & RCV_SHUTDOWN) ||
1679                 !timeo ||
1680                 signal_pending(current))
1681                 break;
1682         } else {
//可能性1 
1683             if (sock_flag(sk, SOCK_DONE))
1684                 break;
1685 
1686             if (sk->sk_err) {
1687                 copied = sock_error(sk);
1688                 break;
1689             }
1690 
1691             if (sk->sk_shutdown & RCV_SHUTDOWN)
1692                 break;
1693 
1694             if (sk->sk_state == TCP_CLOSE) {
1695                 if (!sock_flag(sk, SOCK_DONE)) {
1696                     /* This occurs when user tries to read
1697                      * from never connected socket.
1698                      */
1699                     copied = -ENOTCONN;
1700                     break;
1701                 }
1702                 break;
1703             }
1704 //是否是阻塞的,不是,就return了。
1705             if (!timeo) {
1706                 copied = -EAGAIN;
1707                 break;
1708             }
1709 
1710             if (signal_pending(current)) {
1711                 copied = sock_intr_errno(timeo);
1712                 break;
1713             }
1714         }
1716         tcp_cleanup_rbuf(sk, copied);
1717 
1718         if (!sysctl_tcp_low_latency && tp->ucopy.task == user_recv) {
1719             /* Install new reader */
1720             if (!user_recv && !(flags & (MSG_TRUNC | MSG_PEEK))) {
1721                 user_recv = current;
1722                 tp->ucopy.task = user_recv;
1723                 tp->ucopy.iov = msg->msg_iov;
1724             }
1725 
1726             tp->ucopy.len = len;
1757             if (!skb_queue_empty(&tp->ucopy.prequeue))
1758                 goto do_prequeue;
1759 
1760             /* __ Set realtime policy in scheduler __ */
1761         }
1773         if (copied >= target) {
1774             /* Do not sleep, just process backlog. */
1775             release_sock(sk);
1776             lock_sock(sk);
1777         } else
//在此处睡眠了,将在tcp_prequeue函数中调用wake_up_interruptible_poll唤醒
1778             sk_wait_data(sk, &timeo);
//软中断会判断用户是正在读取检查并且睡眠了,如果是的话,就直接把数据拷贝  
//到prequeue队列,然后唤醒睡眠的进程。因为进程睡眠,表示没有读到想要的字节数  
//此时,软中断有数据到来,直接给进程,这样进程就能以最快的速度被唤醒。 

1785         if (user_recv) {
1786             int chunk;
1787 
1788             /* __ Restore normal policy in scheduler __ */
1789 
1790             if ((chunk = len - tp->ucopy.len) != 0) {
1791                 NET_ADD_STATS_USER(sock_net(sk), LINUX_MIB_TCPDIRECTCOPYFROMBACKLOG, chunk);
1792                 len -= chunk;
1793                 copied += chunk;
1794             }
1795 
1796             if (tp->rcv_nxt == tp->copied_seq &&
1797                 !skb_queue_empty(&tp->ucopy.prequeue)) {
1798 do_prequeue:
1799                 tcp_prequeue_process(sk);
1800 
1801                 if ((chunk = len - tp->ucopy.len) != 0) {
1802                     NET_ADD_STATS_USER(sock_net(sk), LINUX_MIB_TCPDIRECTCOPYFROMPREQUEUE, chunk);
1803                     len -= chunk;
1804                     copied += chunk;
1805                 }
1806             }
1807         }
1808         if ((flags & MSG_PEEK) &&
1809             (peek_seq - copied - urg_hole != tp->copied_seq)) {
1810             net_dbg_ratelimited("TCP(%s:%d): Application bug, race in MSG_PEEK\n",
1811                         current->comm,
1812                         task_pid_nr(current));
1813             peek_seq = tp->copied_seq;
1814         }
1815         continue;
1816 
//skb中还有多少聚聚没有拷贝。  
//正如前面所说的,offset是上次已经拷贝了的,这次从offset开始接下去拷贝  

1817     found_ok_skb:
1818         /* Ok so how much can we use? */
1819         used = skb->len - offset;
//很有可能used的大小,即skb剩余长度,依然大于用户的缓冲区大小(len)。所以依然  
//只能拷贝len长度。一般来说,用户还得执行一次recv系统调用。直到skb中的数据读完 

1820         if (len < used)
1821             used = len;
1822 
1823         /* Do we have urgent data here? */
1824         if (tp->urg_data) {
1825             u32 urg_offset = tp->urg_seq - *seq;
1826             if (urg_offset < used) {
1827                 if (!urg_offset) {
1828                     if (!sock_flag(sk, SOCK_URGINLINE)) {
1829                         ++*seq;
1830                         urg_hole++;
1831                         offset++;
1832                         used--;
1833                         if (!used)
1834                             goto skip_copy;
1835                     }
1836                 } else
1837                     used = urg_offset;
1838             }
1839         }
1840 
1841         if (!(flags & MSG_TRUNC)) {
1870             {
//一般都会进这个if,进行数据的拷贝,把能够读到的数据,放到用户的缓冲区  
1871                 err = skb_copy_datagram_iovec(skb, offset,
1872                         msg->msg_iov, used);
1873                 if (err) {
1874                     /* Exception. Bailout! */
1875                     if (!copied)
1876                         copied = -EFAULT;
1877                     break;
1878                 }
1879             }
1880         }
//更新标志位,seq 是指针,指向了tp->copied_seq  
//used是我们有能力拷贝的数据大小,即已经拷贝到用户缓冲区的大小  
//正如前面所说,如果用户的缓冲区很小,一次recv拷贝不玩skb中的数据,  
//我们需要保存已经拷贝了的大小,下次recv时,从这个大小处继续拷贝。  
//所以需要更新copied_seq。
1882         *seq += used;
1883         copied += used;
1884         len -= used;
1885 
1886         tcp_rcv_space_adjust(sk);
1887 
1888 skip_copy:
1889         if (tp->urg_data && after(tp->copied_seq, tp->urg_seq)) {
1890             tp->urg_data = 0;
1891             tcp_fast_path_check(sk);
1892         }
//这个就是判断我们是否拷贝完了skb中的数据,如果没有continue  
//这种情况下,len经过 len -= used; ,已经变成0,所以continue的效果相当于  
//退出了这个大循环。可以理解,你只能拷贝len长度,拷贝完之后,那就return了。  
  
//还有一种情况used + offset ==  skb->len,表示skb拷贝完了。这时我们只需要释放skb  
//下面会讲到
1893         if (used + offset < skb->len)
1894             continue;
//看看这个数据报文是否含有fin,含有fin,则goto到found_fin_ok
1896         if (tcp_hdr(skb)->fin)
1897             goto found_fin_ok; 
//执行到这里,标明used + offset ==  skb->len,报文也拷贝完了,那就把skb摘链释放 
1898         if (!(flags & MSG_PEEK)) {
1899             sk_eat_skb(sk, skb, copied_early);
1900             copied_early = false;
1901         }
//这个cintinue不一定是退出大循环,可能还会执行循环。  
//假设用户设置缓冲区12字节,你skb->len长度20字节。  
//第一次recv读取了12字节,skb剩下8,下一次调用recv再想读取12,  
//但是只能读取到这8字节了。  
//此时len 变量长度为4,那么这个continue依旧在这个循环中,  
//函数还是再次从do开始,使用skb_queue_walk,找skb  
//如果sk_receive_queue中skb仍旧有,那么继续读,直到len == 0  
//如果没有skb了,我们怎么办?我们的len还有4字节怎么办?  
//这得看用户设置的recv函数阻塞与否,即和timeo变量相关了。
1902         continue;
1903 
1904     found_fin_ok:
1905         /* Process the FIN. */
1906         ++*seq;
1907         if (!(flags & MSG_PEEK)) {
//把skb从sk_receive_queue中摘链 
1908             sk_eat_skb(sk, skb, copied_early);
1909             copied_early = false;
1910         }
1911         break;
1912     } while (len > 0);
1913 
//到这里是大循环退出  
//休眠过的进程,然后退出大循环 ,才满足 if (user_recv) 条件
1914     if (user_recv) {
1915         if (!skb_queue_empty(&tp->ucopy.prequeue)) {
1916             int chunk;
1917 
1918             tp->ucopy.len = copied > 0 ? len : 0;
1919 
1920             tcp_prequeue_process(sk);
1921 
1922             if (copied > 0 && (chunk = len - tp->ucopy.len) != 0) {
1923                 NET_ADD_STATS_USER(sock_net(sk), LINUX_MIB_TCPDIRECTCOPYFROMPREQUEUE, chunk);
1924                 len -= chunk;
1925                 copied += chunk;
1926             }
1927         }
1928 //数据读取完毕,清零
1929         tp->ucopy.task = NULL;
1930         tp->ucopy.len = 0;
1931     }     
1943     /* According to UNIX98, msg_name/msg_namelen are ignored
1944      * on connected socket. I was just happy when found this 8) --ANK
1945      */
1946 
1947     /* Clean up data we have read: This will do ACK frames. /
//很重要,将更新缓存,并且适当的时候发送ack 
1948     tcp_cleanup_rbuf(sk, copied);
1949 
1950     release_sock(sk);
1951     return copied;
1952 
1953 out:
1954     release_sock(sk);
1955     return err;
1956 
1957 recv_urg:
1958     err = tcp_recv_urg(sk, msg, len, flags);
1959     goto out;
1960 
1961 recv_sndq:
1962     err = tcp_peek_sndq(sk, msg, len);
1963     goto out;
1964 }




  • 0
    点赞
  • 2
    收藏
    觉得还不错? 一键收藏
  • 打赏
    打赏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

shichaog

你的鼓励将是我创作的最大动力

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

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

余额充值