zoukankan      html  css  js  c++  java
  • TCP的核心系列 — SACK和DSACK的实现(五)

    18版本对于每个SACK块,都是从重传队列头开始遍历。37版本则可以选择性的遍历重传队列的某一部分,忽略

    SACK块间的间隙、或者已经cache过的部分。这主要是通过tcp_sacktag_skip()和tcp_sacktag_walk()完成的。

    tcp_sacktag_skip()可以直接找到包含某个序号的skb,通常用于定位SACK块的开头。

    tcp_sacktag_walk()则遍历两个序号之间的skb,通常用于遍历一个SACK块。

    本文主要内容:SACK的遍历函数tcp_sacktag_skip()和tcp_sacktag_walk()。

    Author:zhangskd

    tcp_sacktag_skip

    从当前skb开始遍历,查找skip_to_seq序号对应的skb,同时统计fackets_out。

    这样可以从当前包,直接遍历到某个块的start_seq,而不用从头开始遍历,也可以跳过块间的间隙。

    /* Avoid all extra work that is being done by sacktag while walking in a normal way */
    static struct sk_buff *tcp_sacktag_skip(struct sk_buff *skb, struct sock *sk,
                                   struct tcp_sacktag_state *state, u32 skip_to_seq)
    {
        tcp_for_write_queue_from(skb, sk) {
            if (skb == tcp_send_head(sk)) /* 到了发送队列头,即下一个将要发送的数据包 */
                break;
     
            if (after(TCP_SKB_CB(skb)->end_seq, skip_to_seq)) /* 找到包含skip_to_seq序号的数据包了 */
                break;
    
            state->fack_count += tcp_skb_pcount(skb); /* 统计fackets_out个数 */
        }
    
        return skb; /* 返回包含skip_to_seq的skb */
    }
    

     

    tcp_sacktag_walk

    遍历一个SACK块,如果SACK块包含了多个连续的skb,那么先尝试合并这些段。

    为什么要合并呢?因为下次遍历的时候,要遍历的包个数就减少了,能提高效率。

    如果skb完全包含在块中,则调用tcp_sacktag_one更新该段的记分牌。

    static struct sk_buff *tcp_sacktag_walk(struct sk_buff *skb, struct sock *sk,
                                            struct tcp_sack_block *next_dup,
                                            struct tcp_sacktag_state *state,
                                            u32 start_seq, u32 end_seq,
                                            int dup_sack_in)
    {
        struct tcp_sock *tp = tcp_sk(sk);
        struct sk_buff *tmp;
    
        tcp_for_write_queue_from(skb, sk) {
            int in_sack = 0;
            int dup_sack = dup_sack_in;
    
            if (skb == tcp_send_head(sk)) /* 遍历到发送队列头了 */
                break;
    
            if (! before(TCP_SKB_CB(skb)->seq, end_seq)) /* skb序号超过SACK块了 */
                break;
    
            /* 如果下一个块是DSACK,且此skb可能包含在其中 */
            if ((next_dup != NULL) && 
                before(TCP_SKB_CB(skb)->seq, next_dup->end_seq)) {
    
                /* 此skb是否完全包含在DSACK块中 */
                in_sack = tcp_match_skb_to_sack(sk, skb, next_dup->start_seq, next_dup->end_seq);
    
                if (in_sack > 0)
                    dup_sack = 1; /* 表示这个skb被DSACK */
            }
    
            if (in_sack <= 0) {
                /* 一个SACK块可能包括多个skb,尝试把这些连续的skb合并 */
                tmp = tcp_shift_skb_data(sk, skb, state, start_seq, end_seq, dup_sack);
    
                if (tmp != NULL) { /* 合并成功 */
                    if (tmp != skb) { /* tmp和当前段地址不同,则跳到合并后的段处理 */
                        skb = tmp;
                        continue;
                    }
                    in_sack = 0;
    
                } else { /* 合并不成功,单独处理这个段 */
                    in_sack = tcp_match_skb_to_sack(sk, skb, start_seq, end_seq); /* 段是否完全包含在块中 */
                }
            }
            
            if (unlikely(in_sack < 0))
                break;
    
            /* 如果这个段完全包含在块中,进行处理 */
            if (in_sack) {
    
                /* 就是在这里:标志这个段的记分牌!*/
                TCP_SKB_CB(skb)->sacked = tcp_sacktag_one(skb, sk, state, dup_sack, tcp_skb_pcount(skb));
     
                /* 如果当前skb的开始序列号大于被SACK的包的最高初始序列号 */
                if (! before(TCP_SKB_CB(skb)->seq, tcp_highest_sack_seq(tp)))
                    tcp_advance_highest_sack(sk, skb); /*把highest_sack设为skb->next */
            }
    
            state->fack_count += tcp_skb_pcount(skb); /* 更新fackets_out */
        }
    
        return skb; /* 遍历到此skb退出 */
    } 
    

    tcp_match_skb_to_sack()用于检查一个数据段是否完全包含在一个SACK块中,主要考虑到GSO分段。

    /* Check if skb is fully within the SACK block.
     * In presence of GSO skbs, the incoming SACK may not exactly match but we can find smaller MSS
     * aligned portion of it that matches. Therefore we might need to fragment which may fail and creates
     * some hassle (caller must handle error case returns).
     * FIXME: this could be merged to shift decision code
     */
    static int tcp_match_skb_to_sack(struct sock *sk, struct sk_buff *skb, u32 start_seq, u32 end_seq)
    {
        int in_sack, err;
        unsigned int pkt_len;
        unsigned int mss;
    
        /* 如果start_seq <= skb->seq < skb->end_seq <= end_seq,说明skb完全包含在SACK块中 */
        in_sack = ! after(start_seq, TCP_SKB_CB(skb)->seq) &&
                           ! before(end_seq, TCP_SKB_CB(skb)->end_seq);
    
        /* 如果有GSO分段,skb可能部分包含在块中 */
        if (tcp_skb_pcount(skb) > 1 && ! in_sack &&
            after(TCP_SKB_CB(skb)->end_seq, start_seq)) {
    
            mss = tcp_skb_mss(skb);
            in_sack = ! after(start_seq, TCP_SKB_CB(skb)->seq); /* 前半部在块中 */
     
            /* 这里根据skb->seq和start_seq的大小,分情况处理 */
            if (! in_sack) { /* 后半部在块中 */
                pkt_len = start_seq - TCP_SKB_CB(skb)->seq; /* skb在块之前的部分 */
                if (pkt_len < mss)
                    pkt_len = mss;
    
            } else {
                pkt_len = end_seq - TCP_SKB_CB(skb)->seq; /* skb在块内的部分 */
                if (pkt_len < mss)
                    return -EINVAL;
            }
            
            /* Round if necessary so that SACKs cover only full MSSes and/or the remaining
             * small portion (if present)
             */
            if (pkt_len > mss) {
                unsigned int new_len = (pkt_len / mss) * mss;
                if (! in_sack && new_len < pkt_len) {
                    new_len += mss;
                    if (new_len > skb->len)
                        return 0;
                }
                pkt_len = new_len;
            }
    
            err = tcp_fragment(sk, skb, pkt_len, mss); /* 把skb分为两个包,SACK块内的和SACK块外的 */
        }
    
        return in_sack;
    }
    

     

    tcp_shift_skb_data()尝试把SACK块内的多个包合成一个,可以提升遍历效率。

    一个SACK块可能包括多个skb,尝试把这些连续的skb合成一个。

    /* Try to collapsing SACK blocks spanning across multiple skbs to a single skb. */
    static struct sk_buff *tcp_shift_skb_data(struct sock *sk, struct sk_buff *skb,
                                              struct tcp_sacktag_state *state,
                                              u32 start_seq, u32 end_seq, int dup_sack)
    {
        struct tcp_sock *tp = tcp_sk(sk);
        struct sk_buff *prev;
        int mss;
        int pcount = 0;
        int len;
        int in_sack;
    
        if (! sk_can_gso(sk))
            goto fallback;
    
        ...
    
    fallback:
        NET_INC_STATS_BH(sock_net(sk), LINUX_MIB_SACKSHIFTFALLBACK);
        return NULL;
    }
    
  • 相关阅读:
    这才是世上最全的“软件测试”思维导图!
    Scrum3.0 敏捷开发白皮书
    敏捷软件质量保证的方法与实践
    C# DataGridView 列的显示顺序
    Xamarin.android 重写axml控件
    Xamarin控件使用之GridView
    Sql 的 RAISERROR用法
    Xamarin.Android 怎么定义一个按钮和返回键功能一样回到上一个界面
    Xamarin.android Activity动画切换效果实现
    Xamarin.Android之封装个简单的网络请求类
  • 原文地址:https://www.cnblogs.com/aiwz/p/6333335.html
Copyright © 2011-2022 走看看