Linux tcp_write_queue_tail 发送队列管理与 tsq 处理

发布时间:2026/7/21 18:15:16

Linux tcp_write_queue_tail 发送队列管理与 tsq 处理 Linux tcp_write_queue_tail 发送队列管理与 tsq 处理tcp_write_queue_tail 是 TCP 发送队列sk_write_queue的操作宏定义在 include/net/tcp.h 中。sk_write_queue 是 struct sock 的 sk_write_queue 字段类型为 struct sk_buff_head管理所有已构造但尚未完全确认的 TCP 报文段。队列中的每个 skb 使用 tcp_skb_cb-seq、tcp_skb_cb-end_seq 以及 tcp_skb_cb-tcp_flags 标识数据边界和控制信息。cstatic inline struct sk_buff *tcp_write_queue_tail(const struct sock *sk){return skb_peek_tail(sk-sk_write_queue);}static inline struct sk_buff *tcp_write_queue_head(const struct sock *sk){return skb_peek(sk-sk_write_queue);}static inline struct sk_buff *tcp_send_head(const struct sock *sk){return skb_peek(sk-sk_write_queue);}tcp_send_head 返回发送队列头部即最早未被确认的 skb而 tcp_write_queue_tail 返回队列尾部——即最新被追加的 skb。两者的差异在 GSO 分段和 Nagle 算法中至关重要tcp_write_xmit 循环遍历时从 tcp_send_head 开始每次发送后如果整个 skb 被确认则调用 tcp_advance_send_head 前移队列头部。若 tcp_write_queue_head tcp_write_queue_tail仅一个 skb被完全发送并确认队列变空tcp_send_head(NULL) 作为写关闭信号。skb 追加操作通过 __skb_queue_tail 或 skb_queue_tail 完成。用户进程调用 tcp_sendmsg 经过 tcp_push_one 或 tcp_write_xmit 的分段逻辑后新 skb 附着到队列尾部。这里的关键约束skb-truesize 累积计入 sk-sk_wmem_queued 和 sk-sk_forward_alloctcp_sendmsg 在 sk_stream_alloc_skb 失败时返回 -ENOMEM此时 skb 未入队数据可能部分写入到 cork 的 frags 中。cstatic int tcp_push_one(struct sock *sk, unsigned int mss_now, unsigned int nonagle){struct tcp_sock *tp tcp_sk(sk);struct sk_buff *skb tcp_send_head(sk);if (!skb)return 0;tcp_mark_push(tp, skb);__skb_queue_tail(sk-sk_write_queue, skb);...return tcp_write_xmit(sk, mss_now, nonagle, 0, GFP_KERNEL);}write_queue 的管理涉及三个关键尾指针操作tcp_write_queue_tail(sk) 用于获取最后一个 skb 以在其后追加新数据tcp_fragment 分割时同样需要尾部插入tcp_write_queue_next(sk, skb) 和 tcp_write_queue_prev(sk, skb) 用于遍历。遍历过程在 tcp_sacktag_write_queue 和 tcp_clean_rtx_queue 中大量使用。当 tcp_write_xmit 循环发送时每次调用 tcp_transmit_skb 之前检查是否触发了 TSQ 限速。TSQ 在 tcp_write_xmit 内部的判断通过 tcp_small_queue_check 完成。若触发TSQ_THROTTLED 位置位且发送中断。此时 __tcp_transmit_skb 尚未移除 skb重传队列头部保持不变。cstatic void tcp_tsq_handler(struct sock *sk){struct tcp_sock *tp tcp_sk(sk);unsigned long flags smp_load_acquire(sk-sk_tsq_flags);if (flags TCPF_TSQ_DEFERRED) {bh_lock_sock(sk);if (!sock_owned_by_user(sk)) {if (tp-lost_out tp-retrans_out)tcp_write_xmit(sk, tcp_current_mss(sk), tp-nonagle,0, GFP_ATOMIC);tcp_push_pending_frames(sk);}bh_unlock_sock(sk);}}tsq 的 deferred 处理通过 tcp_release_cb 被调用。当发送路径因 TSQ 被限速并且用户进程持有 lock_sock 时skb 释放后的 tcp_wfree 将 TSQ_DEFERRED 置位。随后用户进程在 release_sock 中调用 tcp_release_cb触发 tcp_tsq_handler 重新尝试发送。这里是 TSQ 的核心竞争点tcp_wfree 运行在 NET_TX_SOFTIRQ 上下文而 tcp_release_cb 运行在进程上下文两者对 sk_tsq_flags 的 test_and_set_bit 操作通过原子位操作保证可见性。tcp_push_pending_frames 在发送队列尾部处理 PSH 标志和紧急数据。该函数检查 tcp_send_head(sk) 是否为 NULL若不为空则调用 tcp_write_xmit 继续发送。在 tp-packets_out 为 0 但 write_queue 非空即所有数据已发送但未确认时__tcp_push_pending_frames 只做极简的 wmem 检查后返回。超时重传对 write_queue 尾部的影响tcp_retransmit_skb 不从队列中移除 skb而是克隆后重新提交给 IP 层。skb-sk 指针维持指向原 socket但 cloned skb 的 destructor 由 tcp_wfree 处理。原始 skb 保留在 write_queue 中直到被 tcp_clean_rtx_queue 移除。cint tcp_retransmit_skb(struct sock *sk, struct sk_buff *skb, int segs){struct tcp_sock *tp tcp_sk(sk);int err __tcp_retransmit_skb(sk, skb);if (err 0) {tp-retrans_out tcp_skb_pcount(skb);...if (TCP_SKB_CB(skb)-seq tp-snd_una) {if (tp-lost_out)tp-lost_out - tcp_skb_pcount(skb);}}...return err;}write_queue 中存在一个边界情况TCP_SKB_CB(skb)-tcp_flags TCPHDR_SYN 出现在三次握手阶段的 SYN/ACK 重传。此时 skb 处于 sk_write_queue 中但 writeset即 tp-write_seq尚未初始化完全tcp_retransmit_skb 的检查路径要求验证 sk-sk_state TCP_SYN_SENT 或 TCP_SYN_RECV。另一个边界条件是 tcp_write_queue_tail 与 tcp_collapse 的交互。当发送缓冲区积压过多小包tcp_collapse 将相邻 skb 合并以降低 sk_buff 管理开销。tcp_collapse 从 write_queue 中移除多个 skb 并合并成一个重新追加到尾部。这要求操作持有 write_queue 的自旋锁且如果 collapse 发生在 BH 上下文的 tcp_ack 中不能与用户态的 tcp_sendmsg 并发操作 write_queue——通常通过 sk-sk_lock 的 owned_by_user 标志互斥。

相关新闻