news 2026/7/21 18:15:09

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

作者头像

张小明

前端开发工程师

1.2k 24
文章封面图
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 标识数据边界和控制信息。

```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 标志互斥。

版权声明: 本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若内容造成侵权/违法违规/事实不符,请联系邮箱:809451989@qq.com进行投诉反馈,一经查实,立即删除!
网站建设 2026/7/21 18:13:48

5步搞定Android设备Root:Magisk终极指南从入门到精通

5步搞定Android设备Root:Magisk终极指南从入门到精通 【免费下载链接】Magisk The Magic Mask for Android 项目地址: https://gitcode.com/GitHub_Trending/ma/Magisk 想要彻底掌控你的Android设备吗?厌倦了厂商限制无法安装某些应用&#xff1f…

作者头像 李华
网站建设 2026/7/21 18:12:53

Java面试1000+题全集(2026版),附详细答案解析,从JVM到微服务全覆盖

前言 金九银十招聘旺季马上就到了,不知道大家是否准备好了,面对金九银十的招聘旺季,如果没有精心准备那笔者认为那是对自己不负责任;就我们 Java 程序员来说,多数的公司总体上面试都是以自我介绍项目介绍项目细节/难点…

作者头像 李华
网站建设 2026/7/21 18:09:21

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游戏的高昂价格、封闭生态和缓慢更新…

作者头像 李华
网站建设 2026/7/21 18:08:34

Rope-Pearl:三步实现电影级人脸替换,让创意不再受技术限制

Rope-Pearl:三步实现电影级人脸替换,让创意不再受技术限制 【免费下载链接】Rope GUI-focused roop 项目地址: https://gitcode.com/GitHub_Trending/ro/Rope 你是否曾想过为自己的视频作品添加惊艳的特效,却因复杂的技术门槛而却步&a…

作者头像 李华
网站建设 2026/7/21 18:08:27

3个步骤掌握CatchAdmin:从零搭建企业级后台管理系统

3个步骤掌握CatchAdmin:从零搭建企业级后台管理系统 【免费下载链接】catch-admin CatchAdmin 是一个功能强大、易于扩展的现代化 PHP 后台管理系统。它采用前后端分离架构,CatchAdmin 集成了 Token 鉴权、权限管理、动态路由、动态表格、分页封装、资源…

作者头像 李华