2026/7/21 13:33:49

Linux tcp_retransmit_skb 超时重传与 FRTO 恢复

Linux tcp_retransmit_skb 超时重传与 FRTO 恢复 Linux tcp_retransmit_skb 超时重传与 FRTO 恢复tcp_retransmit_skb 是 TCP 超时重传的核心执行函数由重传定时器 tcp_write_timer 或快速重传路径 tcp_enter_recovery 触发。该函数从 write_queue 中选择 skb 进行重传调用 __tcp_retransmit_skb 执行实际克隆和发送操作。重传不涉及从 write_queue 移除 skb仅通过 skb_clone 创建副本发送原始 skb 保留在队列中等待 ACK。cint __tcp_retransmit_skb(struct sock *sk, struct sk_buff *skb, int segs){struct tcp_sock *tp tcp_sk(sk);struct sk_buff *segs_skb;int err;if (tcp_rtx_queue_empty(sk))return -EINVAL;if (skb_page_frag_refill(tp-md5sig_info ? sizeof(struct tcp_md5sig) : 0,sk-sk_frag, sk-sk_allocation))return -ENOMEM;tcp_init_tso_segs(skb, tcp_current_mss(sk));segs tcp_skb_pcount(skb);tcp_trim_head(skb, tcp_current_mss(sk));err tcp_transmit_skb(sk, skb, 1, GFP_ATOMIC);if (err 0) {tp-retrans_out segs;if (TCP_SKB_CB(skb)-seq tp-snd_una)tp-lost_out - segs;...}return err;}tcp_retransmit_skb 调用前必须确保 skb 确实在 write_queue 中且未被 SACKed 完全确认。内核通过 tcp_rtx_queue_empty 检查队列非空然后遍历队列定位第一个需要重传的 skb。重传选择逻辑在 tcp_xmit_retransmit_queue 中实现从 tcp_write_queue_head 开始检查每个 skb 的 tcp_skb_cb-sacked 标志位跳过 TCPCB_SACKED_ACKED已确认和 TCPCB_SACKED_RETRANS已重传但未确认的 skb优先重传 TCPCB_LOST 标记的数据。cvoid tcp_xmit_retransmit_queue(struct sock *sk){struct tcp_sock *tp tcp_sk(sk);struct sk_buff *skb, *rtx_head tcp_rtx_queue_head(sk);int packet_cnt;if (tp-retrans_out tp-lost_out)return;skb rtx_head;packet_cnt 0;while (skb) {if (skb tcp_write_queue_tail(sk))break;if (!tcp_skb_is_write_queue(sk, skb))break;if (TCP_SKB_CB(skb)-sacked TCPCB_SACKED_ACKED)goto next;if (TCP_SKB_CB(skb)-sacked TCPCB_LOST) {if (tcp_retransmit_skb(sk, skb, tcp_skb_pcount(skb)))return;packet_cnt tcp_skb_pcount(skb);if (packet_cnt tp-lost_out)return;}next:skb tcp_write_queue_next(sk, skb);}}丢失标记 TCPCB_LOST 在 tcp_mark_head_lost 中设置触发条件依赖于 fackets_outSACK 确认的 skb 之外尚未确认的数据包数量和 reordering 因子 tp-reord。tp-reord 决定了 sender 将乱序判定为丢包的容忍度当乱序程度小于 reord即使 SACK blocks 指示空洞也不会标记 LOST。但在 RACK 算法启动后套接字支持 TCP RACK丢失检测完全由 tcp_rack_mark_lost 接管不再依赖传统的 FACK 判断。FRTOForward RTO-RecoveryRFC 5682是检测虚假超时重传spurious RTO的机制。当 RTO 超时后收到 ACKFRTO 通过观察该 ACK 是否确认了新的数据来判定 RTO 是否真实。spurious RTO 常见于无线链路中突发的延迟抖动——发送端 RTO 超时后原数据包实际并未丢失仅因延迟晚到。cvoid tcp_process_tlp_ack(struct sock *sk, u32 ack, int flag){struct tcp_sock *tp tcp_sk(sk);if (before(ack, tp-snd_una))return;if (flag FLAG_ECE)tcp_rate_check_app_limited(sk);if (after(ack, tp-snd_una)) {if (tp-tlp_high_seq after(ack, tp-tlp_high_seq)) {tp-tlp_high_seq 0;tcp_try_keep_open(sk);tcp_try_to_open(sk, flag);}}}FRTO 状态机在 tcp_process_tlp_ack 和 tcp_frto_response 中实现。RTO 触发后 tcp_write_timer_handler 将 ca_state 置为 TCP_CA_Loss然后发送 skb 并递增 retrans_out。若之后收到 ACK 发现 snd_una 前进了FLAG_SND_UNA_ADVANCEDFRTO 的第一阶段判定为 possibly spurious。此时调用 tcp_try_keep_open 尝试恢复为 TCP_CA_Open 状态。若第二个 ACK 继续前进且 SACK 块无异常则 FRTO 最终解除 Loss 状态并撤销 cwnd 减半。FRTO 与 SACK 交互的边界条件如果启用了 SACKFRTO 在第二阶段检查收到的 SACK block 是否覆盖了未重传的数据。若 SACK 确认的数据在重传之前已被接收方收到则判定为 spurious RTO撤销重传标记并恢复 cwnd。若 SACK 确认了重传后的新数据则 RTO 真实继续执行 cwnd 减半。cint tcp_frto_response(struct sock *sk, int flag){struct tcp_sock *tp tcp_sk(sk);if (flag FLAG_ECE)return tcp_enter_cwr(sk);if (tp-frto_counter 2) {if (!(flag FLAG_DATA_ACKED))return tcp_enter_loss(sk);tp-frto_counter 0;tcp_try_to_open(sk, flag);tcp_moderate_cwnd(tp);if (tcp_sk_is_tlp(sk))tcp_schedule_loss_probe(sk);}return 0;}frto_counter 跟踪 FRTO 状态机状态0 表示未激活1 表示第一轮探测可能虚假2 表示第二轮确认确认虚假后恢复。frto_counter 在 tcp_process_loss 中递减该函数是 tcp_ack 处理 Loss 状态的入口。如果在 FRTO 第二轮之前收到重复 ACKFRTO 可被提前终止——这是 RFC 5682 第 4 节规定的 option。性能关键点tcp_retransmit_skb 中 skb_clone 分配 skb 使用的 gfp_mask 是 GFP_ATOMIC因为重传在定时器软中断TIMER_SOFTIRQ上下文中执行不能睡眠。若 GFP_ATOMIC 分配失败__tcp_retransmit_skb 返回 -ENOMEMtcp_xmit_retransmit_queue 中止循环剩余未被重传的 skb 在下一个定时器触发时重试。该延迟会进一步加剧 RTO backoff 指数放大。在内存压力场景下此路径会与 tcp_memory_pressure 交互当系统内存紧张时tcp_write_xmit 中的 sk_stream_alloc_skb 同样返回 ENOMEMwrite_queue 中的 skb 无法追加发送完全冻结直到内存回收。