我發現 Linux 文件寫錯了

小林coding發表於2022-04-25

作者:小林coding

圖解計算機基礎網站:https://xiaolincoding.com

大家好,我是小林。

週末的時候,有位讀者疑惑為什麼 Linux man 手冊中關於 netstat 命令中的 tcp listen 狀態下的 Recv-Q 和 Send-Q 這兩個資訊的描述跟我的圖解網路寫的不一樣?

我看了原始碼後,確認了這個 man 手冊寫的不對。沒想到 Linux 的 man 手冊也會出錯。

首先,先給大家介紹下 netstat 命令。netstat 命令是檢視網路狀態很常見的 Linux 命令。

比如,如果我們想檢視系統中的程式監聽了哪些 TCP 埠,則可以使用下面這個命令: netstat -napt

接下來,小林帶大家分析,為什麼我說 man 手冊寫錯了 netstat 命令中 Recv-Q 和 Send-Q 的描述?

疑惑提出

讀者提出的疑惑: https://man7.org/linux/man-pages/man8/netstat.8.html 我先給大家翻譯一下,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 函式時把連線取出來。

如果你想知道 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/,每個核心版本的程式碼都有,平常我都是在這裡看。

這次,我們選擇核心版本為 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 函式中,列印資訊的程式碼如下: 我在圖中標紅了兩行程式碼,這兩行程式碼分別是 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 的值代表什麼意思呢?

下面這個是判斷全連線佇列是否溢位的函式: 可以得知,sk_ack_backlog 其實是當前全連線佇列的大小,也就是經歷三次握手後等待被應用層 accpet() 的連線的數量。

所以,從上面的原始碼分析過,得到的結論如下:

  • netstat 命令中的 Recv-Q:如果 TCP 連線狀態處於 Established,Recv-Q 的數值表示接收緩衝區中還沒拷貝到應用層的資料大小;如果 TCP 連線狀態處於 Listen 狀態,Recv-Q 的數值表示當前全連線佇列的大小
  • netstat 命令中的 Send-Q:表示傳送緩衝區中已傳送但未被確認的資料大小(不管 TCP 是 Listen 狀態還是 Established 狀態都表示這個意思);

好了,至此就分析完了。

最後

看到這,大家肯定會說:小林你太強了吧,為什麼對 Linux 核心原始碼那麼熟,這都能分析出來

其實,我並沒有熟讀過 Linux 核心原始碼啦,其實只要大家有好奇心,其實你也能分析出來。

我也是通過網上的資料,一點一點分析出來的,並不是直接就在核心原始碼裡查,不然那真是大海撈針。

我是這樣一步一步查資料分析的:

  • 先網上查下 netstat 原始碼,看是根據什麼資訊列印 Send-Q 和 Recv-Q,然後看到網上有人說是讀 /proc/net/tcp 這個檔案;
  • 接著,就網上查 /proc/net/tcp 這個檔案是怎麼列印的,然後看到網上有人說是由 net/ipv4/tcp_ipv4.c 檔案中的 tcp4_seq_show() 函式列印的;
  • 最後,再自己去看 tcp4_seq_show 函式的實現,這個函式的程式碼也不多,就幾十行,所以很容易就分析出來了。

你看,其實我也是通過「搜尋」一步一步分析出來的,其實並沒有什麼難度。

只是我比較細節一點。

相關文章