• 深入了解- TCP拥塞状态机 tcp_fastretrans_alert


    【推荐阅读】

    浅析linux内核网络协议栈--linux bridge

    virtio-net 实现机制【一】(图文并茂)

    深入理解SR-IOV和IO虚拟化

    这里主要说的是TCP拥塞情况下的状态状态处理

    1. /* Process an event, which can update packets-in-flight not trivially.
    2. * Main goal of this function is to calculate new estimate for left_out,
    3. * taking into account both packets sitting in receiver's buffer and
    4. * packets lost by network.
    5. *
    6. * Besides that it does CWND reduction, when packet loss is detected
    7. * and changes state of machine.
    8. *
    9. * It does _not_ decide what to send, it is made in function
    10. * tcp_xmit_retransmit_queue().
    11. */
    12. static void tcp_fastretrans_alert(struct sock *sk, int pkts_acked, int flag)
    13. {
    14. struct inet_connection_sock *icsk = inet_csk(sk);
    15. struct tcp_sock *tp = tcp_sk(sk);
    16. int is_dupack = !(flag & (FLAG_SND_UNA_ADVANCED | FLAG_NOT_DUP)); // 判断是不是重复的ACK
    17. int do_lost = is_dupack || ((flag & FLAG_DATA_SACKED) && // 判断是不是丢包:若是重复ACK 或者 SACK而且提前确认中没有到的包数量>重拍指标
    18. (tcp_fackets_out(tp) > tp->reordering)); // 后面会单独说说SACK和FACK内容,觉得总是理解不好
    19. int fast_rexmit = 0;
    20. if (WARN_ON(!tp->packets_out && tp->sacked_out)) // 如果packet_out为0,那么不可能有sacked_out
    21. tp->sacked_out = 0;
    22. if (WARN_ON(!tp->sacked_out && tp->fackets_out))
    23. tp->fackets_out = 0;
    24. /* Now state machine starts. // 下面开始状态处理
    25. * A. ECE, hence prohibit cwnd undoing, the reduction is required. */
    26. if (flag & FLAG_ECE) // 如果是ECE
    27. tp->prior_ssthresh = 0;// 禁止拥塞窗口撤销,并开始减小拥塞窗口
    28. /* B. In all the states check for reneging SACKs. */
    29. if (tcp_check_sack_reneging(sk, flag)) // 检查ACK是不是确认了已经被SACK选择确认的包了
    30. return;
    31. /* C. Process data loss notification, provided it is valid. */
    32. if (tcp_is_fack(tp) && (flag & FLAG_DATA_LOST) && // 提前确认、数据丢失
    33. before(tp->snd_una, tp->high_seq) && // 我们需要注意high_seq 可以标志为LOST的段序号的最大值
    34. icsk->icsk_ca_state != TCP_CA_Open && // 状态不是OPEN
    35. tp->fackets_out > tp->reordering) { // 同上面说的
    36. tcp_mark_head_lost(sk, tp->fackets_out - tp->reordering); // 发现丢包,需要标志出丢失的包。 (1) 这个函数后面看
    37. NET_INC_STATS_BH(LINUX_MIB_TCPLOSS);
    38. }
    39. /* D. Check consistency of the current state. */
    40. tcp_verify_left_out(tp); // #define tcp_verify_left_out(tp) WARN_ON(tcp_left_out(tp) > tp->packets_out)
    41. // 检查丢失的包应该比发送出去的包小,即确定确定left_out < packets_out
    42. /* E. Check state exit conditions. State can be terminated
    43. * when high_seq is ACKed. */ // 下面检测状态退出条件!当high_seq 被确认的时候,这个状态就可以终止了
    44. if (icsk->icsk_ca_state == TCP_CA_Open) { // 如果是open状态
    45. BUG_TRAP(tp->retrans_out == 0); // 重传数量应该=0才是合理的
    46. tp->retrans_stamp = 0; // 将重传发送时间置0
    47. } else if (!before(tp->snd_una, tp->high_seq)) { // 如果high_seq已经被确认
    48. switch (icsk->icsk_ca_state) { //
    49. case TCP_CA_Loss: //
    50. icsk->icsk_retransmits = 0; // 超时重传次数归零
    51. if (tcp_try_undo_recovery(sk)) // 尝试将前面的拥塞窗口的调整撤销,在这种情况下弄不清楚包的情况(2
    52. return; // 如果使用了SACK,那么不管undo成功与否,都会返回Open
    53. break;
    54. case TCP_CA_CWR: // 发生某些道路拥塞,需要减慢发送速度
    55. /* CWR is to be held something *above* high_seq
    56. * is ACKed for CWR bit to reach receiver. */
    57. if (tp->snd_una != tp->high_seq) {
    58. tcp_complete_cwr(sk); // 完成道路拥塞情况处理,就是减小cwnd(3
    59. tcp_set_ca_state(sk, TCP_CA_Open); // 将状态设置成OPEN
    60. }
    61. break;
    62. case TCP_CA_Disorder:
    63. tcp_try_undo_dsack(sk); // 尝试撤销cwnd的减少,因为DSACK确认了所有的重传数据(4
    64. if (!tp->undo_marker || // 跟踪了重传数据包?
    65. /* For SACK case do not Open to allow to undo
    66. * catching for all duplicate ACKs. */
    67. tcp_is_reno(tp) || tp->snd_una != tp->high_seq) { // 没有SACK || 两者不同步
    68. tp->undo_marker = 0;
    69. tcp_set_ca_state(sk, TCP_CA_Open); // 将状态转换成OPEN
    70. }
    71. break;
    72. case TCP_CA_Recovery:
    73. if (tcp_is_reno(tp)) // 没有SACK
    74. tcp_reset_reno_sack(tp); // sacked_out=0
    75. if (tcp_try_undo_recovery(sk)) // 尝试撤销
    76. return;
    77. tcp_complete_cwr(sk); // 完成处理
    78. break;
    79. }
    80. }
    81. /* F. Process state. */
    82. switch (icsk->icsk_ca_state) {
    83. case TCP_CA_Recovery:
    84. if (!(flag & FLAG_SND_UNA_ADVANCED)) { // snd_una没有改变
    85. if (tcp_is_reno(tp) && is_dupack) // 不是SACK,而且是重复的ACK
    86. tcp_add_reno_sack(sk); // 接收到重复的ACK,tp->sacked_out++; 并且检查新的reorder问题(5
    87. } else
    88. do_lost = tcp_try_undo_partial(sk, pkts_acked); // 部分ACK接收并撤销窗口操作(6)注意返回的是是否需要重传表示
    89. break; // 1代表重传,0代表不需要重传
    90. case TCP_CA_Loss:
    91. if (flag & FLAG_DATA_ACKED) // 如果是数据确认
    92. icsk->icsk_retransmits = 0; // 超时重传置次数0
    93. if (tcp_is_reno(tp) && flag & FLAG_SND_UNA_ADVANCED) // 没有ACK,&& snd_una改变了
    94. tcp_reset_reno_sack(tp); // 重置sacked=0
    95. if (!tcp_try_undo_loss(sk)) { // 尝试撤销拥塞调整,然后进入OPEN状态(7
    96. tcp_moderate_cwnd(tp); // 调整窗口(8
    97. tcp_xmit_retransmit_queue(sk); // 重传丢失的包(9
    98. return;
    99. }
    100. if (icsk->icsk_ca_state != TCP_CA_Open)
    101. return;
    102. /* Loss is undone; fall through to processing in Open state. */
    103. default:
    104. if (tcp_is_reno(tp)) { // 么有SACK,那么就是RENO算法处理:收到三个dup-ACK(即sacked_out==3),就开始重传
    105. if (flag & FLAG_SND_UNA_ADVANCED) // 如果收到少于 3 个 dupack 后又收到累计确认,则会重置之前的 sacked_out 计数
    106. tcp_reset_reno_sack(tp); // 重新置0
    107. if (is_dupack) // 如果收到一个dup-ack,将sacked_out++
    108. tcp_add_reno_sack(sk);
    109. }
    110. if (icsk->icsk_ca_state == TCP_CA_Disorder)
    111. tcp_try_undo_dsack(sk); // DSACK确认了所有重传数据
    112. if (!tcp_time_to_recover(sk)) { // 判断是否进入恢复状态
    113. tcp_try_to_open(sk, flag);// 如果不可以,那么会判断是否进入Open、Disorder、CWR等状态
    114. return; // 只有收到三个dup-ack时候,才进入快速回复,否则都返回
    115. }
    116. /* MTU probe failure: don't reduce cwnd */
    117. if (icsk->icsk_ca_state < TCP_CA_CWR &&
    118. icsk->icsk_mtup.probe_size &&
    119. tp->snd_una == tp->mtu_probe.probe_seq_start) {
    120. tcp_mtup_probe_failed(sk); // MTU探测失败
    121. /* Restores the reduction we did in tcp_mtup_probe() */
    122. tp->snd_cwnd++;
    123. tcp_simple_retransmit(sk); // 做一个简单的转发,而不使用回退机制。用于路径MTU发现。
    124. return;
    125. }
    126. // 说明已经收到第 3 个连续 dupack,此时 sacked_out = 3,进入恢复态
    127. /* Otherwise enter Recovery state */
    128. // 进入恢复状态
    129. if (tcp_is_reno(tp))
    130. NET_INC_STATS_BH(LINUX_MIB_TCPRENORECOVERY);
    131. else
    132. NET_INC_STATS_BH(LINUX_MIB_TCPSACKRECOVERY);
    133. tp->high_seq = tp->snd_nxt;
    134. tp->prior_ssthresh = 0;
    135. tp->undo_marker = tp->snd_una;
    136. tp->undo_retrans = tp->retrans_out;
    137. if (icsk->icsk_ca_state < TCP_CA_CWR) {
    138. if (!(flag & FLAG_ECE))
    139. tp->prior_ssthresh = tcp_current_ssthresh(sk); // 根据状态获取当前门限值
    140. tp->snd_ssthresh = icsk->icsk_ca_ops->ssthresh(sk); // 更新
    141. TCP_ECN_queue_cwr(tp);
    142. }
    143. tp->bytes_acked = 0;
    144. tp->snd_cwnd_cnt = 0;
    145. tcp_set_ca_state(sk, TCP_CA_Recovery); // 键入恢复状态
    146. fast_rexmit = 1; // 快速重传
    147. }
    148. if (do_lost || (tcp_is_fack(tp) && tcp_head_timedout(sk))) // 如果丢失需要重传 || 超时重传
    149. tcp_update_scoreboard(sk, fast_rexmit); // 标志丢失和超时的数据包,增加lost_out(10)
    150. tcp_cwnd_down(sk, flag); // 减小cwnd窗口(11)
    151. tcp_xmit_retransmit_queue(sk);// 重传丢失包
    152. }
    【文章福利】小编推荐自己的Linux内核技术交流群: 【977878001】整理一些个人觉得比较好得学习书籍、视频资料共享在群文件里面,有需要的可以自行添加哦!!!前100进群领取,额外赠送一份 价值699的内核资料包(含视频教程、电子书、实战项目及代码)

    内核资料直通车:Linux内核源码技术学习路线+视频教程代码资料

    学习直通车:Linux内核源码/内存调优/文件系统/进程管理/设备驱动/网络协议栈

    下面看一下里面的函数:

    先看:tcp_mark_head_lost:通过给丢失的数据包标志TCPCB_LOST,就可以表明哪些数据包需要重传。

    注意参数:packets = fackets_out - reordering,其实就是sacked_out + lost_out。被标志为LOST的段数不能超过packets。

    那么packets 就是标记丢失的包们数量

    1. /* Mark head of queue up as lost. With RFC3517 SACK, the packets is
    2. * is against sacked "cnt", otherwise it's against facked "cnt"
    3. */
    4. static void tcp_mark_head_lost(struct sock *sk, int packets)
    5. {
    6. struct tcp_sock *tp = tcp_sk(sk);
    7. struct sk_buff *skb;
    8. int cnt, oldcnt;
    9. int err;
    10. unsigned int mss;
    11. BUG_TRAP(packets <= tp->packets_out); // 丢失的包不可能比所有发出去的包的数量
    12. if (tp->lost_skb_hint) { // 如果已经有标识为丢失的段了
    13. skb = tp->lost_skb_hint; // 下一个需要标记的数据段
    14. cnt = tp->lost_cnt_hint; // 已经标记了多少段
    15. } else {
    16. skb = tcp_write_queue_head(sk); // 获得链表的第一个结构元素
    17. cnt = 0; // 初始化标记了0个数据
    18. }
    19. // 下面开始遍历
    20. tcp_for_write_queue_from(skb, sk) {
    21. if (skb == tcp_send_head(sk)) // return sk->sk_send_head; 即snd_nxt,那么还没有发送不需要处理,break;
    22. break;
    23. /* TODO: do this better */
    24. /* this is not the most efficient way to do this... */
    25. tp->lost_skb_hint = skb; // 更新丢失队列信息
    26. tp->lost_cnt_hint = cnt;
    27. if (after(TCP_SKB_CB(skb)->end_seq, tp->high_seq)) // high_seq是最大的标记为LOST的号,不可以超过这个
    28. break; // 若这个skb超过,退出
    29. oldcnt = cnt; // 保存cnt
    30. if (tcp_is_fack(tp) || tcp_is_reno(tp) ||
    31. (TCP_SKB_CB(skb)->sacked & TCPCB_SACKED_ACKED))
    32. cnt += tcp_skb_pcount(skb); // 表示这个段已经被标记
    33. if (cnt > packets) {
    34. if (tcp_is_sack(tp) || (oldcnt >= packets)) // 已经超过了丢失包数量,break
    35. break;
    36. mss = skb_shinfo(skb)->gso_size; // 得到MSS
    37. err = tcp_fragment(sk, skb, (packets - oldcnt) * mss, mss); // 下面分配,前面说过了
    38. if (err < 0)
    39. break;
    40. cnt = packets;
    41. }
    42. // 下面这一段就是做标记动作
    43. if (!(TCP_SKB_CB(skb)->sacked & (TCPCB_SACKED_ACKED|TCPCB_LOST))) {
    44. TCP_SKB_CB(skb)->sacked |= TCPCB_LOST; // 标识
    45. tp->lost_out += tcp_skb_pcount(skb); // 丢失包+=
    46. tcp_verify_retransmit_hint(tp, skb); // 其实就是标记这个丢失,加入重传标记队列
    47. }
    48. }
    49. tcp_verify_left_out(tp);
    50. }

    看一下tcp_verify_retransmit_hint函数:

    1. static void tcp_verify_retransmit_hint(struct tcp_sock *tp, struct sk_buff *skb)
    2. {
    3. if ((tp->retransmit_skb_hint == NULL) ||
    4. before(TCP_SKB_CB(skb)->seq,
    5. TCP_SKB_CB(tp->retransmit_skb_hint)->seq))
    6. tp->retransmit_skb_hint = skb; // 加入这个队列
    7. if (!tp->lost_out ||
    8. after(TCP_SKB_CB(skb)->end_seq, tp->retransmit_high)) // 如果最后一个数据标号比high大,明显更新high
    9. tp->retransmit_high = TCP_SKB_CB(skb)->end_seq;
    10. }

    OK,再看一下这个函数tcp_try_undo_recovery:

    1. static int tcp_try_undo_recovery(struct sock *sk)
    2. {
    3. struct tcp_sock *tp = tcp_sk(sk);
    4. if (tcp_may_undo(tp)) { // 如果可以undo
    5. /* Happy end! We did not retransmit anything
    6. * or our original transmission succeeded.
    7. */
    8. DBGUNDO(sk, inet_csk(sk)->icsk_ca_state == TCP_CA_Loss ? "loss" : "retrans");
    9. tcp_undo_cwr(sk, 1); // 具体处理
    10. if (inet_csk(sk)->icsk_ca_state == TCP_CA_Loss)
    11. NET_INC_STATS_BH(LINUX_MIB_TCPLOSSUNDO);
    12. else
    13. NET_INC_STATS_BH(LINUX_MIB_TCPFULLUNDO);
    14. tp->undo_marker = 0;
    15. }
    16. if (tp->snd_una == tp->high_seq && tcp_is_reno(tp)) {
    17. /* Hold old state until something *above* high_seq
    18. * is ACKed. For Reno it is MUST to prevent false
    19. * fast retransmits (RFC2582). SACK TCP is safe. */
    20. tcp_moderate_cwnd(tp); // 更新窗口大小
    21. return 1;
    22. }
    23. tcp_set_ca_state(sk, TCP_CA_Open);
    24. return 0;
    25. }

    OK看一下tcp_may_undo函数:检测能否撤销

    1. static inline bool tcp_may_undo(const struct tcp_sock *tp)
    2. {
    3. return tp->undo_marker && (!tp->undo_retrans || tcp_packet_delayed(tp));
    4. }

    首先得有undo_marker标识才OK!然后undo_retrans的意思是最近的Recovery时间内重传的数据包个数,如果收到一个DSACK那么undo_retrans减一,如果最后等于0,那么说明都被确认了,没有必要重传,所以没有必要调整窗口。或tcp_packet_delayed(tp)条件。如下:

    1. static inline int tcp_packet_delayed(struct tcp_sock *tp)
    2. {
    3. return !tp->retrans_stamp ||
    4. (tp->rx_opt.saw_tstamp && tp->rx_opt.rcv_tsecr &&
    5. (__s32)(tp->rx_opt.rcv_tsecr - tp->retrans_stamp) < 0); // 接收ACK时间在重传数据之前
    6. }

    下面 看一下这个函数tcp_complete_cwr:

    1. static inline void tcp_complete_cwr(struct sock *sk)
    2. {
    3. struct tcp_sock *tp = tcp_sk(sk);
    4. tp->snd_cwnd = min(tp->snd_cwnd, tp->snd_ssthresh); // 调整窗口
    5. tp->snd_cwnd_stamp = tcp_time_stamp;
    6. tcp_ca_event(sk, CA_EVENT_COMPLETE_CWR); // 出发事件
    7. }

    1. /* CWND moderation, preventing bursts due to too big ACKs
    2. * in dubious situations.
    3. */
    4. static inline void tcp_moderate_cwnd(struct tcp_sock *tp) // 修改窗口值
    5. {
    6. tp->snd_cwnd = min(tp->snd_cwnd,
    7. tcp_packets_in_flight(tp) + tcp_max_burst(tp)); // 防止怀疑的ACK情况,所以取min值
    8. tp->snd_cwnd_stamp = tcp_time_stamp;

    再看看这个函数tcp_try_undo_dsack:当DSACK确认所有的重传数据,那么undo_retrans=0,那么需要回复窗口原来的情况

    1. /* Try to undo cwnd reduction, because D-SACKs acked all retransmitted data */
    2. static void tcp_try_undo_dsack(struct sock *sk)
    3. {
    4. struct tcp_sock *tp = tcp_sk(sk);
    5. if (tp->undo_marker && !tp->undo_retrans) { // 所有的段都被确认了
    6. DBGUNDO(sk, "D-SACK");
    7. tcp_undo_cwr(sk, 1); // 撤销(1
    8. tp->undo_marker = 0;
    9. NET_INC_STATS_BH(LINUX_MIB_TCPDSACKUNDO);
    10. }
    11. }

    撤销函数

    1. static void tcp_undo_cwr(struct sock *sk, const int undo)
    2. {
    3. struct tcp_sock *tp = tcp_sk(sk);
    4. if (tp->prior_ssthresh) { // 如果保存了旧的门限值
    5. const struct inet_connection_sock *icsk = inet_csk(sk);
    6. if (icsk->icsk_ca_ops->undo_cwnd)
    7. tp->snd_cwnd = icsk->icsk_ca_ops->undo_cwnd(sk); // 这个函数可以自己添加
    8. else
    9. tp->snd_cwnd = max(tp->snd_cwnd, tp->snd_ssthresh << 1); // 如果没有定义那个函数,那么做简单的处理
    10. if (undo && tp->prior_ssthresh > tp->snd_ssthresh) {
    11. tp->snd_ssthresh = tp->prior_ssthresh;
    12. TCP_ECN_withdraw_cwr(tp);
    13. }
    14. } else { // 没有保存旧的阈值
    15. tp->snd_cwnd = max(tp->snd_cwnd, tp->snd_ssthresh); //
    16. }
    17. tcp_moderate_cwnd(tp); // 上面已经说了
    18. tp->snd_cwnd_stamp = tcp_time_stamp;
    19. /* There is something screwy going on with the retrans hints after
    20. an undo */
    21. tcp_clear_all_retrans_hints(tp);// 清空所有的重传信息
    22. }

    接收到重复的ACK,那么需要对sacked_out处理,看函数tcp_add_reno_sack:

    1. /* Emulate SACKs for SACKless connection: account for a new dupack. */
    2. static void tcp_add_reno_sack(struct sock *sk)
    3. {
    4. struct tcp_sock *tp = tcp_sk(sk);
    5. tp->sacked_out++; // 收到重复的ACK,那么这个值++
    6. tcp_check_reno_reordering(sk, 0); // 检查是否有reordering(1
    7. tcp_verify_left_out(tp); //
    8. }

    看看这个检查reordering函数:

    1. /* If we receive more dupacks than we expected counting segments
    2. * in assumption of absent reordering, interpret this as reordering.
    3. * The only another reason could be bug in receiver TCP.
    4. */
    5. static void tcp_check_reno_reordering(struct sock *sk, const int addend)
    6. {
    7. struct tcp_sock *tp = tcp_sk(sk);
    8. if (tcp_limit_reno_sacked(tp)) // 检查sack的数量是否超过限度
    9. tcp_update_reordering(sk, tp->packets_out + addend, 0); // 如果是reordering则更新reordering
    10. }

    1. /* Limits sacked_out so that sum with lost_out isn't ever larger than
    2. * packets_out. Returns zero if sacked_out adjustement wasn't necessary.
    3. */
    4. int tcp_limit_reno_sacked(struct tcp_sock *tp) // 限制sacked_out目的是使得sacked_out + lost_out <= packeted_out
    5. {
    6. u32 holes;
    7. holes = max(tp->lost_out, 1U); // 获得hole
    8. holes = min(holes, tp->packets_out);
    9. if ((tp->sacked_out + holes) > tp->packets_out) { // 如果大于发出的包,那么reordering就需要了
    10. tp->sacked_out = tp->packets_out - holes; // 因为此处的dup-ack是reorder造成的
    11. return 1;
    12. }
    13. return 0;
    14. }

    下面看看更新reordering函数tcp_update_reordering

    1. static void tcp_update_reordering(struct sock *sk, const int metric,
    2. const int ts)
    3. {
    4. struct tcp_sock *tp = tcp_sk(sk);
    5. if (metric > tp->reordering) { // 如果现在的数量 > 之前的reorder
    6. tp->reordering = min(TCP_MAX_REORDERING, metric); // 获得ordering值(注意不能超过最大设置值)
    7. /* This exciting event is worth to be remembered. 8) */
    8. if (ts)
    9. NET_INC_STATS_BH(LINUX_MIB_TCPTSREORDER); // 统计信息
    10. else if (tcp_is_reno(tp))
    11. NET_INC_STATS_BH(LINUX_MIB_TCPRENOREORDER);
    12. else if (tcp_is_fack(tp))
    13. NET_INC_STATS_BH(LINUX_MIB_TCPFACKREORDER);
    14. else
    15. NET_INC_STATS_BH(LINUX_MIB_TCPSACKREORDER);
    16. #if FASTRETRANS_DEBUG > 1
    17. printk(KERN_DEBUG "Disorder%d %d %u f%u s%u rr%d\n",
    18. tp->rx_opt.sack_ok, inet_csk(sk)->icsk_ca_state,
    19. tp->reordering,
    20. tp->fackets_out,
    21. tp->sacked_out,
    22. tp->undo_marker ? tp->undo_retrans : 0);
    23. #endif
    24. tcp_disable_fack(tp); // 禁用fack(fack是基于有序的,因为已经使用order了,所以禁用fack)
    25. }
    26. }

    下面再看一下这个tcp_try_undo_partial函数:在恢复状态,收到部分ACK确认,使用这个函数撤销拥塞调整。

    1. /* Undo during fast recovery after partial ACK. */
    2. static int tcp_try_undo_partial(struct sock *sk, int acked)
    3. {
    4. struct tcp_sock *tp = tcp_sk(sk);
    5. /* Partial ACK arrived. Force Hoe's retransmit. */ // 收到部分ACK,对于SACK来说不需要重传,对于RENO需要
    6. int failed = tcp_is_reno(tp) || (tcp_fackets_out(tp) > tp->reordering); // 或者facked_out数量比reordering要大
    7. if (tcp_may_undo(tp)) { // 是否可以调整(上面已说)
    8. /* Plain luck! Hole if filled with delayed
    9. * packet, rather than with a retransmit.
    10. */
    11. if (tp->retrans_out == 0) // 重传包=0
    12. tp->retrans_stamp = 0; // 重置重传时间
    13. tcp_update_reordering(sk, tcp_fackets_out(tp) + acked, 1); // 需要更新reordering( 上面 )
    14. DBGUNDO(sk, "Hoe");
    15. tcp_undo_cwr(sk, 0); // 撤销操作( 上面 )
    16. NET_INC_STATS_BH(LINUX_MIB_TCPPARTIALUNDO);
    17. /* So... Do not make Hoe's retransmit yet.
    18. * If the first packet was delayed, the rest
    19. * ones are most probably delayed as well.
    20. */
    21. failed = 0; // 表示不用重传了,可以发送新的数据
    22. }
    23. return failed; // 返回是否需要重传
    24. }

    下面继续看tcp_try_undo_loss函数:收到部分确认之后,从loss状态撤销窗口调整

    1. /* Undo during loss recovery after partial ACK. */
    2. static int tcp_try_undo_loss(struct sock *sk)
    3. {
    4. struct tcp_sock *tp = tcp_sk(sk);
    5. if (tcp_may_undo(tp)) { // 如果可以undo
    6. struct sk_buff *skb;
    7. tcp_for_write_queue(skb, sk) { // 遍历整个发送queue
    8. if (skb == tcp_send_head(sk)) // 直到还没有发送的数据头之前(前面的都已经发送)
    9. break;
    10. TCP_SKB_CB(skb)->sacked &= ~TCPCB_LOST; // 清除LOST标记
    11. }
    12. tcp_clear_all_retrans_hints(tp); // 清除所有的重传信息
    13. DBGUNDO(sk, "partial loss");
    14. tp->lost_out = 0; // 重置
    15. tcp_undo_cwr(sk, 1); // 撤销窗口调整
    16. NET_INC_STATS_BH(LINUX_MIB_TCPLOSSUNDO);
    17. inet_csk(sk)->icsk_retransmits = 0;
    18. tp->undo_marker = 0;
    19. if (tcp_is_sack(tp))
    20. tcp_set_ca_state(sk, TCP_CA_Open); // 设置状态OPEN
    21. return 1;
    22. }
    23. return 0;
    24. }

    下面看一下tcp_update_scoreboard函数:其实就是更新lost包数量,这个涉及到不同的算法不一样的结果,没有SACK(reno),有SACK,有FACK情况

    1) 没有SACK:每次收到重复的ACK或部分ack时,标志一个包为丢失。

    2) 有SACK:sacked_out - reordering > 0 时候,标记为这么多丢失,若小于0,标记为1个丢失(前提是有重传标识)

    3) 有FACK:fackets_out - reordering >0 时候,标记为这么多丢失,若小于0,标记为1个丢失

    ( 注意:小于0的情况是因为考虑到reordering情况 )

    1. /* Account newly detected lost packet(s) */
    2. static void tcp_update_scoreboard(struct sock *sk, int fast_rexmit)
    3. {
    4. struct tcp_sock *tp = tcp_sk(sk);
    5. if (tcp_is_reno(tp)) { // 最普通的,没有SACK情况
    6. tcp_mark_head_lost(sk, 1); // 标记为一个丢失
    7. } else if (tcp_is_fack(tp)) { // 如果是fack
    8. int lost = tp->fackets_out - tp->reordering; // 判断这个值大小
    9. if (lost <= 0)
    10. lost = 1; // 小于0指标记一个
    11. tcp_mark_head_lost(sk, lost); // 否则标记所有的
    12. } else { // 仅仅有SACK情况
    13. int sacked_upto = tp->sacked_out - tp->reordering;
    14. if (sacked_upto < fast_rexmit)
    15. sacked_upto = fast_rexmit;
    16. tcp_mark_head_lost(sk, sacked_upto); // 同上
    17. }
    18. /* New heuristics: it is possible only after we switched
    19. * to restart timer each time when something is ACKed.
    20. * Hence, we can detect timed out packets during fast
    21. * retransmit without falling to slow start.
    22. */
    23. if (tcp_is_fack(tp) && tcp_head_timedout(sk)) { // 下面检查超时包( 先检查第一个数据包是否超时 )
    24. struct sk_buff *skb;
    25. skb = tp->scoreboard_skb_hint ? tp->scoreboard_skb_hint
    26. : tcp_write_queue_head(sk);
    27. tcp_for_write_queue_from(skb, sk) {
    28. if (skb == tcp_send_head(sk))
    29. break;
    30. if (!tcp_skb_timedout(sk, skb)) // 检查所有的超时包,没有超时的就break
    31. break;
    32. if (!(TCP_SKB_CB(skb)->sacked & (TCPCB_SACKED_ACKED|TCPCB_LOST))) {
    33. TCP_SKB_CB(skb)->sacked |= TCPCB_LOST; // 标记为lost
    34. tp->lost_out += tcp_skb_pcount(skb); // 增加lost数量
    35. tcp_verify_retransmit_hint(tp, skb);
    36. }
    37. }
    38. tp->scoreboard_skb_hint = skb;
    39. tcp_verify_left_out(tp);
    40. }
    41. }

    下面继续看这个减小窗口函数:tcp_cwnd_down,

    1. /* Decrease cwnd each second ack. */ // 每收到2个确认将拥塞窗口减1,直到拥塞窗口等于慢启动阈值。
    2. static void tcp_cwnd_down(struct sock *sk, int flag)
    3. {
    4. struct tcp_sock *tp = tcp_sk(sk);
    5. int decr = tp->snd_cwnd_cnt + 1; // 计数器
    6. if ((flag & (FLAG_ANY_PROGRESS | FLAG_DSACKING_ACK)) ||
    7. (tcp_is_reno(tp) && !(flag & FLAG_NOT_DUP))) {
    8. tp->snd_cwnd_cnt = decr & 1;// 因为此处只可能是0,1三个值,这样的操作其实就是切换值,例如现在是第一个ACK,即之前的snd_cwnd_cnt=0,decr=1,那么1&1=1,将snd_cwnd_cnt赋值为1;第二个ACK到来,decr=2,则2&1=0,相当于又将snd_cwnd_cnt初始化为0,因为两个ACK就需要处理一次。
    9. decr >>= 1; // 除以2,是判断是第一个ACK,还是第二个;第一个的话值=0,下面不会执行,是2的话=1,下面一句会执行
    10. if (decr && tp->snd_cwnd > tcp_cwnd_min(sk)) // 如果是第二个ACK && 比最小的门限值还大一点,那么还需要减小cwnd
    11. tp->snd_cwnd -= decr; // 减小一个,^_^
    12. tp->snd_cwnd = min(tp->snd_cwnd, tcp_packets_in_flight(tp) + 1); // 用于微调,和外面的数据包数量比较
    13. tp->snd_cwnd_stamp = tcp_time_stamp; // 改变时间戳
    14. }
    15. }

     

  • 相关阅读:
    探究map为什么不能遍历的同时进行增删操作
    Rust 错误处理
    十、网络编程之 poll 详解.md
    Go - 用户服务和Web服务
    docker的常用命令(持续更新中)
    AR贴纸特效SDK,无缝贴合的虚拟体验
    bert----学习笔记
    分布式重点知识总结
    上线就破100W!京东面试官的Spring高级源码手抄本,真不能再细了
    Greenplum学习笔记——第二部分:集群部署
  • 原文地址:https://blog.csdn.net/m0_74282605/article/details/128203167