熬过了几个夜晚,终于把TCP的拥塞处理的Linux撸了一遍,仓促中也总结了一幅巨大的图,然而今天下午的例会讨论后,我自己说着说着发现还有一些值得商榷的地方,有的是笔误,也有的是一些细节依然没有搞清楚,因此特此勘误,原文我只修改了文字,因为重新贴图代价实在太大,再者,我希望留下一些错误的印记,这样也能看清楚整个发展的历程,希望两篇一起看。
虽然在大师级的神看来,这不算什么,但是对于我,这是一个比较浩大的工程,错误之处在所难免,请谅解!谢谢!如果能共同进步,这就是我分享的最终目的和动力。
1.今天是我的生日,我觉得就这么睡去不符合这个特殊日子的风格,因此就趁着二两酒把这篇勘误补齐吧。
2.这是第一篇勘误,我相信原图还有很多问题,以后会持续更新,风格和方式与这个一致。
问题1:mark lost的时候到底是怎么进行的
这个涉及到了原图中的What to retransmit,到底Linux内核函数tcp_mark_head_lost是怎么进行的呢?看下面的发送序列:UNA|hole1|hole2|sack1|sack2|hole3|hole4|sack3-7|unack1|unack4|snd_nxt|...
我们得知,sack_out的值是2+5=7,假设默认reordering是3,那么在不启用fack的情况下,应该有7-3=4个数据包被标记为LOST,该标记哪些呢?
选择:从una开始,标记没有被sack的4个数据包,即UNA,hole1,hole2,hole3。这也是我在原图中的理解,但事实上呢?我们开看代码(Linix 3.10),tcp_mark_head_lost:
static void tcp_mark_head_lost(struct sock *sk, int packets, int mark_head){ struct tcp_sock *tp = tcp_sk(sk); struct sk_buff *skb; int cnt, oldcnt; int err; unsigned int mss; /* Use SACK to deduce losses of new sequences sent during recovery */ const u32 loss_high = tcp_is_sack(tp) ? tp->snd_nxt : tp->high_seq; ... tcp_for_write_queue_from(skb, sk) { if (skb == tcp_send_head(sk)) break; /* TODO: do this better */ /* this is not the most efficient way to do this... */ tp->lost_skb_hint = skb; tp->lost_cnt_hint = cnt; if (after(TCP_SKB_CB(skb)->end_seq, loss_high)) break; oldcnt = cnt; if (tcp_is_fack(tp) || tcp_is_reno(tp) || // 请注意,被标记为SACKED的数据包也会占据mark lost计数!!如上例,当UNA,hole1,hole2被标记完 // 以后,下一个本应该标记hole3,但是由于sack1占据可一个“位置”,因此如果只标记4个LOST,那么将会 // 到sack1为止!!,不会再标记hole3! // 但这并不意味着LOST标记仅仅会标记左边数第一系列的空洞,如果标记数为5,那么由于sack2也占据了一个 // 位置,标记为LOST的依然是UNA,hole1,hole2,但是如果是标记6个LOST,那么结果将是标记UNA,hole1,hole2, // hole3为LOST!明白了吗? (TCP_SKB_CB(skb)->sacked & TCPCB_SACKED_ACKED)) cnt += tcp_skb_pcount(skb); if (cnt > packets) { ... } tcp_skb_mark_lost(tp, skb); if (mark_head) break; } tcp_verify_left_out(tp);}
总结:标记LOST的时候,会从左到右,标记的时候并不会跳过已经被SACK的数据包,只是,如果它已经被SACK了,那么将不会被置LOST标志。这有什么意义?在SACK reneging的时候,将会有意义。
这意味着,可以重传多少数据包和空洞的位置有关,一个合理的假设是,如果中间大量连续积累了SACKed的数据包,仅仅说明在这些数据包前面的空洞丢失的可能性比较大,这是真丢包,而不是乱序,然而话如果反着说,如果被SACKed的数据包间隔交替到来,这恰恰说明乱序的可能大于丢包,此时我们到底要重传哪些呢?如果给我安放一个偏置电阻的权力,我就可以完美解决这个问题,然而电阻可以在电子市场一块钱抓一把,跟老板混熟了还可以白拿,偏置算法可就是不是随便百度一下就能用的了....(百度?嗯,百度!)
问题2:关于When to retransmit
原图中,在描述SACK模式的时候,我在图中只画了3个数据包被SACK,但是事实上,要想触发重传,必须有至少4个数据包被SACK,这是一个笔误。问题3:原图中的乱序检测,在描述重复SACK的时候,我将reordering的右沿滑到了snd_high,但事实上,应该是最高被SACK的那个位置,正如下面的文字描述的那样,犯这样的错误,真不应该!
问题4:严格说,这是我自己想的一个问题,原图原文不一定错,然而人不就是不断否定自己才能进步么?
请看How to retransmit,旁白中有关于RFC3517的描述,规范了传输的优先级,第一优先传输LOST数据包,第二传输新数据,然而我们看到,tcp_xmit_retransmit_queue,发现Linux实现第二优先传输新数据的时候,完全依赖“output路径”,也就是说在传输完LOST数据包之后,只要发现有新数据等待传输,直接就退出了tcp_xmit_retransmit_queue,至于说output路径到底什么时候被触发,已经可以传输多少新数据,完全不管!
在解耦合方面,这么做是恰当的(请注意这个函数的名字是retranmit,而不是transmit),但是难道这样不会造成窗口浪费吗?试想当前窗口为100,我有10个LOST数据需要重传,这意味着
可以发送90个新数据包,然而可能此时没有那么多新数据包,难道这个时候进行“前向重传”不好吗?
再分享一下我老师大神的人工智能教程吧。零基础!通俗易懂!风趣幽默!还带黄段子!希望你也加入到我们人工智能的队伍中来!https://blog.csdn.net/jiangjunshow