tcp_autocork_compound怎样Compound

联启 网络工具 17

本文目录导读:

tcp_autocork_compound怎样Compound-第1张图片-电脑手机工具软件下载 - 免费实用工具合集 | 联启科技

  1. 机制概述
  2. “Compound” 的代码协同过程(基于 Linux 内核实现)
  3. 关键协同技术:S/G DMA 和 GSO
  4. 场景复现:Redis 或 gRPC
  5. 总结:tcp_autocorking 如何 Compound

“tcp_autocorking” 和 “TCP 小包的 compound(合并/聚合)” 是现代 Linux 内核网络协议栈(TCP/IP 栈)中用来提升小数据包发送效率(特别是对于像 Redis、Memcached、HTTP/2 的 gRPC 请求等场景)的两个重要机制。

核心目的:减少网络包的数量,降低 CPU 开销和网络传输次数。

下面从代码实现和协作机制的角度,解释它们是如何 “compound”(协同工作/聚合)的。

机制概述

  • tcp_autocorking:这是一个内核标志(sysctl 参数 net.ipv4.tcp_autocorking,默认开启),它允许 TCP 在发送完一个数据包后,不立即清空发送缓存,而是将后续的小数据“黏附”在同一个 skb(socket buffer)上,继续等待填满。
  • TCP Small Queues (TSQ):这是另一个关联机制,用于限制每个 socket 在 Qdisc(队列规则)和驱动队列中的未确认字节数,防止单个连接填满缓冲区,它和 autocorking 配合得很好。
  • Nagle 算法:这是经典的合并算法(延迟发送等待 ACK),但 autocorking 比 Nagle 更激进,它在发送第一个包后立即开始合并,而不是等 ACK。

“Compound” 的代码协同过程(基于 Linux 内核实现)

当用户态应用(如 Redis)执行 write()send() 发送小数据时,内核的 TCP 传输层会进行如下操作:

步骤 1:数据到达 tcp_sendmsg

假设应用连续发送了两个 1KB 的小数据包。

  1. 第一次 send()

    • 内核分配一个新的 skb(sk_stream_alloc_skb)。
    • 将 1KB 数据拷贝进去。
    • 调用 tcp_push 尝试发送。
    • tcp_autocorking 检查:如果满足条件(当前 socket 处于 cork 状态,或者刚发送了一个包且还有更多数据可发),内核会不立即刷新
    • 关键点tcp_autocorking 允许这个 skb 保留在发送队列中,即使它没有被填满(而普通模式下,小包会直接发送出去),内核会设置一个“强制 cork”标志(icsk_forced_cork)。
  2. 第二次 send()(紧接着第一次,毫秒级内):

    • 内核检查发送队列的尾部 skb,发现它只有 1KB,还能再放数据。
    • Compound 行为:内核不会分配新 skb,而是直接将新来的 1KB 追加到已有的那个 skb 中(调用 skb_putpskb_expand_head 扩大数据区)。
    • 效果:原来的 1KB 包和 1KB 新包合并成了一个 2KB 的 skb,即使它们来自不同的 send 调用。

步骤 2:触发发送的条件

合并后的 skb 会在以下任一情况被真正推送到网卡:

  • 填满了一个 MSS(最大分段大小,通常是 1460 字节):两个 1KB 加起来 2KB,超过了 MSS,内核会进行分段(GSO/TSO 分段),发送第一个 1460 字节的分段,剩下的部分继续合并。
  • 应用调用了 tcp_push 且满足不出队列条件:应用设置了 TCP_CORK(完全 cork)或 MSG_MORE 标志,然后调用 send() 并最终关闭。
  • 定时器超时:如果应用长时间不发新数据,autocorking 不会无限等待,内核会在一段时间后(如 1ms 或受 TCP 延迟确认影响)强制推送,这被称为“哑巴时间片”。

步骤 3:与 Nagle 的协同(对比)

  • Nagle:强制等待上一个包被 ACK 确认,才能发送下一个包(即使包很小),这会导致延迟(受 RTT 影响)。
  • Autocorking等待 ACK,它在发送完第一个小包后,会立即尝试将后续小数据追加到即将发送的 skb 的末尾,只要 skb 还没真正离开 TCP 层(被交给 IP 层),合并就可以进行。
  • Compound 效果:一个典型场景是“请求-响应”模式(如 HTTP 请求头)。

    Autocorking 可以把“请求头 + 请求体”合并成一个 skb,避免了 Nagle 导致的 ACK 等待延迟。

关键协同技术:S/G DMA 和 GSO

现代的 “compound” 不仅是逻辑上的追加,还利用了硬件的 Scatter-Gather DMA

  • 不复制数据:当第二次 send() 的数据到来时,内核不需要把两次的用户空间数据拷贝到一起,它只需在 skb 的 frag_listfrags 数组中新增一个页框指针(指向新数据)。
  • GSO (Generic Segmentation Offload):网络栈最终在把合并后的“巨型包”(Super Skb)交给驱动前,会通过 GSO 根据 MSS 把它分割成多个标准大小的 TCP 段,但分割前,它们共享同一个 skb 结构,降低了 skb 分配和管理的 CPU 开销。

场景复现:Redis 或 gRPC

以 Redis SET 命令为例(数据较小):

  1. 应用调用 write(sockfd, "*3\r\n$3\r\nSET\r\n$5\r\nmykey\r\n$5\r\nhello\r\n", 30)
  2. 内核 TCP 层得到这 30 字节。
  3. 如果不开启 autocorking:直接构建一个 30 字节的 TCP 包立即发送,这是一个小包。
  4. 开启 autocorking(默认开启):
    • 如果此时 socket 的发送队列尾部有一个刚发完但未清空(因为之前有数据量较大刚发完)的 skb 块,并且其剩余空间足够(比如有 1400 字节的空位),这 30 字节会直接追加到这个旧 skb 的末尾。
    • 这个 skb 会在填满到 MSS(1460)后,一次性发送出去;或者在定时超时后作为 30 字节的小包发送。
    • Compound 结果:3 个独立的 write() 调用产生的 30+50+40 = 120 字节,如果它们发生在极短时间内,最终可能只生成 1 个 120 字节的 skb,而不是 3 个 120 字节的小包单独发送。

tcp_autocorking 如何 Compound

特性 说明
底层机制 通过 skb->data_lenskb_shared_info 中的 frag_listfrags 数组,将多次 send() 的用户数据逻辑上合并到同一个 skb 结构内。
触发条件 sysctl net.ipv4.tcp_autocorking = 1(默认)。
当前 socket 的发送队列没有达到 sk->sk_wmem_alloc 的硬限制。
数据到达时,队列尾部 skb 未被完全发送至 IP 层。
何时合并 发送完成但 skb 未被释放期间,内核在 tcp_push_one__tcp_transmit_skb 之前,检查是否还有未被 tcp_write_xmit 清空的 skb。
何时拆分 当合并后的总大小超过 MSS 时,提交给 GSO(Generic Segmentation Offload)进行硬件/软件层面的大包拆分,但拆分前,它们共享一个 skb,降低了协议栈的处理次数。
性能收益 - 减少 CPU 利用率:减少链路层(Qdisc/驱动)的中断次数。
- 提高带宽利用率:减少小包导致的协议头开销(以太网头 14B + IP 头 20B + TCP 头 20B,小数据时开销比例高)。
- 降低延迟抖动(对比 Nagle):不等待 ACK,因此在“突发小数据 + 后跟大数据”场景下,延迟更低。

一句话总结tcp_autocorking 不是通过修改 TCP 选项实现的,而是通过内核在发送路径上的有条件的 skb 追加合并实现的,它把多次系统调用的数据“复合”成一个较大的内核数据结构块,然后一次性推送给驱动,从而实现了对小包的自主合并(compound),兼顾了低延迟(无 ACK 等待)和低开销(少发送次数)。

标签: 动cork

抱歉,评论功能暂时关闭!