6

我发现 Linux 文档写错了,你发现了吗?

 2 years ago
source link: https://os.51cto.com/article/707415.html
Go to the source link to view the article. You can view the picture content, updated content and better typesetting reading experience. If the link is broken, please click the button below to view the snapshot at that time.
neoserver,ios ssh client
我发现 Linux 文档写错了,你发现了吗?-51CTO.COM
我发现 Linux 文档写错了,你发现了吗?
作者:小林coding 2022-04-26 06:43:12
netstat 工具在获取 TCP 连接的信息的时候,实际上是读取了 /proc/net/tcp 文件里的数据,而这个文件的数据是由内核由 net/ipv4/tcp_ipv4.c 文件中的 tcp4_seq_show() 函数打印的。

大家好,我是小林。

周末的时候,有位读者疑惑为什么 Linux man 手册中关于 netstat 命令中的 tcp listen 状态下的 Recv-Q 和 Send-Q 这两个信息的描述跟我的图解网络写的不一样?

我看了源码后,确认了这个 man 手册写的不对。没想到 Linux 的 man 手册也会出错。

首先,先给大家介绍下 netstat 命令。netstat 命令是查看网络状态很常见的 Linux 命令。比如,如果我们想查看系统中的进程监听了哪些 TCP 端口,则可以使用下面这个命令 netstat -napt:

810bdd428e967e1aca175864670b64986089f9.png

接下来,小林带大家分析,为什么我说 man 手册写错了 netstat 命令中 Recv-Q 和 Send-Q 的描述?

疑惑提出读者提出的疑惑:

d49b80a89ef15f4ea3345031e6ab6587c39f1d.png

a6c6ed0332314613b42491cf90a55ee9520d6b.jpg

我先给大家翻译一下,man 手册(https://man7.org/linux/man-pages/man8/netstat.8.html)是怎么说的:

  • Recv-Q:如果 TCP 连接状态处于 Established,Recv-Q 的数值表示接收缓冲区中还没拷贝到应用层的数据大小;如果 TCP 连接状态处于 Listen 状态,Recv-Q 的数值表示当前 syn 半连接队列的大小(自内核版本 2.6.18 起)
  • Send-Q:如果 TCP 连接状态处于 Established,Send-Q的数值表示发送缓冲区中已发送但未被确认的数据大小;如果 TCP 连接状态处于 Listen 状态,Send-Q 的数值表示 syn 半连接队列的容量(自内核版本 2.6.18 起)。

而我通过查阅内核 2.6.18 版本的源码,得到的结论如下:

  • Recv-Q:如果 TCP 连接状态处于 Established,Recv-Q 的数值表示接收缓冲区中还没拷贝到应用层的数据大小;如果 TCP 连接状态处于 Listen 状态,Recv-Q 的数值表示当前 syn 半连接队列的大小 当前全连接队列的大小;
  • Send-Q:如果 TCP 连接状态处于 Established,Send-Q的数值表示发送缓冲区中已发送但未被确认的数据大小;如果 TCP 连接状态处于 Listen 状态,Send-Q 的数值表示 syn 半连接队列的容量

上面被我划掉的部分,就是我与 man 手册差异的地方。

什么是 TCP 半连接队列和全链接队列?

在 TCP 三次握手的时候,Linux 内核会维护两个队列,分别是:

  • 半连接队列,也称 SYN 队列;
  • 全连接队列,也称 accept 队列;

服务端收到客户端发起的 SYN 请求后,内核会把该连接存储到半连接队列,并向客户端响应 SYN+ACK,接着客户端会返回 ACK,服务端收到第三次握手的 ACK 后,内核会把连接从半连接队列移除,然后创建新的完全的连接,并将其添加到全连接队列,等待进程调用 accept 函数时把连接取出来。

05e55489344079501c6483a4f945b5d6eef5e7.png

如果你想知道 TCP 半连接和全连接溢出会发生什么?可以看看这篇文章:TCP 半连接队列和全连接队列满了会发生什么?又该如何应对?

netstat 工具在获取 TCP 连接的信息的时候,实际上是读取了 /proc/net/tcp 文件里的数据,而这个文件的数据是由内核由 net/ipv4/tcp_ipv4.c 文件中的 tcp4_seq_show() 函数打印的。

所以,我们直接看 tcp4_seq_show() 函数是根据什么信息打印出 Recv-Q 和 Send-Q 的数据。

有一个网站可以在线看 Linux 内核代码:https://elixir.bootlin.com/,每个内核版本的代码都有,平常我都是在这里看。

44c993906655f232e2a9628495a1cc0ce66e9a.png

这次,我们选择内核版本为 2.6.18 查看 tcp4_seq_show() 函数的实现,如下:

static int tcp4_seq_show(struct seq_file *seq, void *v)
{
 .....

 switch (st->state) {
 case TCP_SEQ_STATE_LISTENING:
 case TCP_SEQ_STATE_ESTABLISHED:
  get_tcp4_sock(v, tmpbuf, st->num);
  break;
 .......
 }
 ...
 return 0;
}

我们只分析 tcp 连接状态为 ESTABLISHED 和 LISTENING 时打印的信息,所以接下来看 get_tcp4_sock 函数。

get_tcp4_sock 函数中,打印信息的代码如下:

391e45648cf1aa0dd09798277270f4628afb6a.jpg

我在图中标红了两行代码,这两行代码分别是 Recv-Q 和 Send-Q 的数据。

我单独把这两行代码抽了出来:

// Send-Q 打印的数据
tp->write_seq - tp->snd_una,

//Recv-Q 打印的数据
(sp->sk_state == TCP_LISTEN) ? sp->sk_ack_backlog : (tp->rcv_nxt - tp->copied_seq),

可以看到, 不管 TCP 连接状态是什么, Send-Q 都是发送缓冲区中已发送但未被确认的数据大小。

然后针对 Recv-Q ,在 TCP 连接状态为 LISTEN 时,打印的是 sk_ack_backlog 的值。

那 sk_ack_backlog 的值代表什么意思呢?

下面这个是判断全连接队列是否溢出的函数:

693f35103e9a73a56a14141a7b242d07645124.jpg

可以得知,sk_ack_backlog 其实是当前全连接队列的大小,也就是经历三次握手后等待被应用层 accpet() 的连接的数量。

所以,从上面的源码分析过,得到的结论如下:

  • netstat 命令中的 Recv-Q:如果 TCP 连接状态处于 Established,Recv-Q 的数值表示接收缓冲区中还没拷贝到应用层的数据大小;如果 TCP 连接状态处于 Listen 状态,Recv-Q 的数值表示当前全连接队列的大小;
  • netstat 命令中的 Send-Q:表示发送缓冲区中已发送但未被确认的数据大小(不管 TCP 是 Listen 状态还是 Established 状态都表示这个意思);

c185dda98391744dc0d6344da5e139d9035b52.png

好了,至此就分析完了。

看到这,大家肯定会说:小林你太强了吧,为什么对 Linux 内核源码那么熟,这都能分析出来。

其实,我并没有熟读过 Linux 内核源码啦,其实只要大家有好奇心,其实你也能分析出来。

我也是通过网上的资料,一点一点分析出来的,并不是直接就在内核源码里查,不然那真是大海捞针。

我是这样一步一步查资料分析的:

  • 先网上查下 netstat 源码,看是根据什么信息打印 Send-Q 和 Recv-Q,然后看到网上有人说是读 /proc/net/tcp 这个文件;
  • 接着,就网上查 /proc/net/tcp 这个文件是怎么打印的,然后看到网上有人说是由 net/ipv4/tcp_ipv4.c 文件中的 tcp4_seq_show() 函数打印的;
  • 最后,再自己去看 tcp4_seq_show 函数的实现,这个函数的代码也不多,就几十行,所以很容易就分析出来了。

你看,其实我也是通过「搜索」一步一步分析出来的,其实并没有什么难度。


About Joyk


Aggregate valuable and interesting links.
Joyk means Joy of geeK