sock
中定义的结构体有sock.h
两个看起来非常相似的属性:
sk_wmem_alloc
,定义为“已提交的传输队列字节”sk_wmem_queued
,定义为“持久队列大小”
对我来说,这sk_wmem_alloc
是当前分配给发送队列的内存量。但那么,什么是sk_wmem_queued
?
参考
- 根据这个 StackOverflow 答案:
wmem_queued:在传输队列中排队并且尚未发送或尚未确认的套接字发送缓冲区使用的内存量。
- 该
ss
男子还给出了定义,但并没有真正启发我(我不明白IP层与此有什么关系):wmem_分配:发送数据包所用的内存(已发送到第3层) wmem_queued:为发送数据包分配的内存(尚未发送到第3层)
- 已经有人了在 LKML 上问了类似的问题,但没有得到答复
- 手册
sock_diag(7)
页也对这些属性有自己的定义:SK_MEMINFO_WMEM_ALLOC:发送队列中的数据量。 SK_MEMINFO_WMEM_QUEUED:由 TCP 排队但尚未发送的数据量。
所有这些定义都是不同的,并且没有一个清楚地解释_alloc
和_queued
变体如何不同。
答案1
我给 Linux 网络堆栈的贡献者 Eric Dumazet 发了一封电子邮件,答案如下:
sk_wmem_alloc
跟踪 skb 排队的字节数后传输堆栈:qdisc 层和 NIC TX 环形缓冲区。如果 TCP 写入队列中有 1 MB 数据尚未发送(cwnd 限制),
sk_wmem_queue
则约为 1 MB,但sk_wmem_alloc
约为 0
用于理解这三种类型的队列(套接字缓冲区、qdisc 队列和设备队列)的一个非常好的文档是这篇文章(相当长)。简而言之,套接字首先将数据包直接推送到 qdisc 队列,然后将它们转发到设备队列。当 qdisc 队列已满时,套接字开始在自己的写入队列中缓冲数据。
网络堆栈将数据包直接放入排队规则中,或者如果队列已满,则将数据包推回到上层(例如套接字缓冲区)
所以基本上:是套接字缓冲区 ( )sk_wmem_queues
使用的内存,而是 qdisc 和设备队列中的数据包使用的内存。sock.sk_write_queue
sk_wmem_alloc
答案2
TL;DR:让我们相信手册页:-)。他们说数据可以在两个不同的地方排队。所以如果你想知道总内存使用量,你需要将两个值相加。
免责声明:我的断言是从消息灵通的来源得出的结论,但我没有经测试这。另外,这实在是太长了,你可能不想读它。
Google 搜索sk_wmem_alloc
返回有关 TCP 小队列 (TSQ) 介绍的著作。
- TCP 小队列,LWN.net 2012。
- tcp:TCP 小队列,内核提交(代码更改+描述)。
下层队列本身是由两个不同的队列组成的。首先是 qdisc(排队规则)[*],然后是设备内部的队列。
在 TSQ 代码中,“sk->sk_wmem_alloc [不允许]增长超过给定限制,在给定时间 qdisc/dev 层中的每个 tcp 套接字[默认情况下]允许不超过 ~128KB。”
=>sk_wmem_alloc
必须包括至少qdisc 和 dev 层。
TSQ 代码发挥了很大作用。 “我不再有 4 MB 的积压在 qdisc 中由单个 [批量发送者]”。而且,“双方套接字自动调整不再使用 4 MB”。
4MB从哪里来?这不是这里使用的 qdisc 的限制 - 它要大得多。该测试使用“标准”FIFO qdisc,默认为 1000 个数据包。 4MB / 1000 就是 4K,但这不是标准数据包大小。该测试使用标准最大 TSO 数据包大小:64K。回答:
Linux 2.6.17 现在具有发送方和接收方自动调整功能以及 4 MB 默认最大窗口大小。
目前 Linux 中实现的本质上是 Semke '98 中描述的,但没有最大-最小公平共享。
--https://wiki.geant.org/display/public/EK/TCP+Buffer+Auto+Tuning
查一下,自动调整非常粗略地“遵循 2 * 带宽 * 延迟的经验法则”。它是使用“cwnd [拥塞窗口]:一个现有的 TCP 变量(针对单个连接)来实现的,它估计可用的带宽延迟乘积 [BDP],以确定要保持传输的适当数据量。”
最后,“传输中”的数据总量受到 TCP 缓冲区大小的限制。这是因为 TCP 是一个可靠的协议。即使在物理传输数据包之后,我们也需要在缓冲区中保留数据的副本。我们需要能够重新发送它,以防数据包在传输过程中丢失。我们必须继续存储数据,直到接收者告诉我们数据安全到达。
这解释了 TSQ 的不同结果。
首先,发送方在 qdisc+device 中建立一个小队列。 TCP 逐渐增加其窗口,以探测路径上的空闲容量。它发送更多,因此建立了一个更大的队列。如果 TCP 检测到数据包被丢弃,那么它就会后退。但 qdisc 仍有空间容纳更多数据包,因此没有理由丢弃任何数据包。这个循环将持续下去,直到达到极限......
使用 TSQ,单个 TCP 发送方不会将 qdisc+设备队列增长到超过 128K。但如果没有 TSQ,则可以达到 4MB 的限制。
=> 看看这里的手册页是如何理解的。
如果没有 TSQ,sk_wmem_queued
则会达到最大 4MB。 sk_wmem_alloc
减去 BDP 的其余部分,将达到 4MB。
所述结果来自本地测试,具有非常短的物理传输延迟。
如果我们增加延迟(或带宽),BDP 就会增加。 尽管 TSQ 限制为 128K,sk_wmem_queued
但仍可达到 4MB 。sk_wmem_alloc
[*] 路由器上使用高级 qdisc 功能,例如对不同数据包进行优先级排序。但所有Linux系统都会在qdisc层对一些数据包进行排队。最初,这是一个固定数量数据包(“fifo”)的队列。在许多现代系统上,默认值现在是“fq_codel”。 “codel”通过传输时间自适应地限制排队。设备速度越慢,允许增长的队列就越小。 “fq_codel”还尝试提供更公平的共享,并允许非批量发件人跳过批量发件人,以提高响应能力。
早期笔记
这两个变量之一似乎与 UDP 最相关。另一个似乎与 TCP 最相关。我查了一下用途sock_writeable()( sk_wmem_alloc
) 与sk_stream_is_writeable()( sk_wmem_queued
)。
当然,除了UDP和TCP之外,还有更多的套接字协议。查看代码表明差异与实现“可靠性”有关。
这可以解释为什么称为 DCCP 的协议(数据报拥塞控制协议)正在使用名为 :-P 的功能。 DCCP 是一个“可靠”的协议。看sk_stream_is_writeable()
dccp_poll()。
如果它与处理丢弃的数据包有关,那也可以解释为什么我在流套接字 sk_stream_is_writeable()
的实现中没有找到对任何地方的调用。数据报在传输过程中不会丢失。AF_LOCAL
AF_LOCAL
TCP 和 UDP 之间的区别在以下方面尤其明显net/sunrpc/xprtsock.c
:
/**
* xs_udp_write_space - callback invoked when socket buffer space
* becomes available
* @sk: socket whose state has changed
*
* Called when more output buffer space is available for this socket.
* We try not to wake our writers until they can make "significant"
* progress, otherwise we'll waste resources thrashing kernel_sendmsg
* with a bunch of small requests.
*/
static void xs_udp_write_space(struct sock *sk)
{
read_lock_bh(&sk->sk_callback_lock);
/* from net/core/sock.c:sock_def_write_space */
if (sock_writeable(sk))
xs_write_space(sk);
read_unlock_bh(&sk->sk_callback_lock);
}
/**
* xs_tcp_write_space - callback invoked when socket buffer space
* becomes available
* @sk: socket whose state has changed
*
* Called when more output buffer space is available for this socket.
* We try not to wake our writers until they can make "significant"
* progress, otherwise we'll waste resources thrashing kernel_sendmsg
* with a bunch of small requests.
*/
static void xs_tcp_write_space(struct sock *sk)
{
read_lock_bh(&sk->sk_callback_lock);
/* from net/core/stream.c:sk_stream_write_space */
if (sk_stream_is_writeable(sk))
xs_write_space(sk);
read_unlock_bh(&sk->sk_callback_lock);
}
最后说明
sock_writeable()
和的定义sk_wmem_alloc
表明“套接字缓冲区”是一个谎言。
在上面的第一部分中,我们清楚地识别了 TCP 套接字缓冲区,与 qdisc+设备队列不同。这都是真的。但 TCP 是一种特殊情况,是一种可靠的(“流”)协议。
也可以看看man sendmsg
-
ENOBUFS
网络接口的输出队列已满。这通常表明接口已停止发送,但也可能是由暂时拥塞引起的。 (通常情况下,这在 Linux 中不会发生。当设备队列溢出时,数据包只是默默地丢弃。)
这里的确切措辞没有提到 UDP 套接字缓冲区。 UDP 套接字没有专用的发送缓冲区。我们只是将数据包直接填充到 qdisc 中。如果sk_wmem_alloc
超过“发送缓冲区大小”,sendmsg()
调用将阻塞(等待)。但如果 qdisc 没有空间,数据包将被默默丢弃。