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 标识数据边界和控制信息。
```c
static 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_alloc,tcp_sendmsg 在 sk_stream_alloc_skb 失败时返回 -ENOMEM,此时 skb 未入队,数据可能部分写入到 cork 的 frags 中。
```c
static 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,重传队列头部保持不变。
```c
static 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 移除。
```c
int 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 标志互斥。
Linux tcp_write_queue_tail 发送队列管理与 tsq 处理
张小明
前端开发工程师
5步搞定Android设备Root:Magisk终极指南从入门到精通
5步搞定Android设备Root:Magisk终极指南从入门到精通 【免费下载链接】Magisk The Magic Mask for Android 项目地址: https://gitcode.com/GitHub_Trending/ma/Magisk 想要彻底掌控你的Android设备吗?厌倦了厂商限制无法安装某些应用?…
5步掌握Metroidvania-System地图编辑器:Godot银河战士游戏终极制作工具
5步掌握Metroidvania-System地图编辑器:Godot银河战士游戏终极制作工具 【免费下载链接】Metroidvania-System General-purpose framework for creating metroidvania games in Godot. 项目地址: https://gitcode.com/gh_mirrors/me/Metroidvania-System 你是…
Java面试1000+题全集(2026版),附详细答案解析,从JVM到微服务全覆盖
前言 金九银十招聘旺季马上就到了,不知道大家是否准备好了,面对金九银十的招聘旺季,如果没有精心准备那笔者认为那是对自己不负责任;就我们 Java 程序员来说,多数的公司总体上面试都是以自我介绍项目介绍项目细节/难点…
Beyond All Reason:开源RTS游戏的革命性解决方案
Beyond All Reason:开源RTS游戏的革命性解决方案 【免费下载链接】Beyond-All-Reason Main game repository for Beyond All Reason. 项目地址: https://gitcode.com/gh_mirrors/be/Beyond-All-Reason 你是否曾经为传统RTS游戏的高昂价格、封闭生态和缓慢更新…
Rope-Pearl:三步实现电影级人脸替换,让创意不再受技术限制
Rope-Pearl:三步实现电影级人脸替换,让创意不再受技术限制 【免费下载链接】Rope GUI-focused roop 项目地址: https://gitcode.com/GitHub_Trending/ro/Rope 你是否曾想过为自己的视频作品添加惊艳的特效,却因复杂的技术门槛而却步&a…
3个步骤掌握CatchAdmin:从零搭建企业级后台管理系统
3个步骤掌握CatchAdmin:从零搭建企业级后台管理系统 【免费下载链接】catch-admin CatchAdmin 是一个功能强大、易于扩展的现代化 PHP 后台管理系统。它采用前后端分离架构,CatchAdmin 集成了 Token 鉴权、权限管理、动态路由、动态表格、分页封装、资源…