我發(fā)現(xiàn) Linux 文檔寫錯(cuò)了,你發(fā)現(xiàn)了嗎?
大家好,我是小林。
周末的時(shí)候,有位讀者疑惑為什么 Linux man 手冊(cè)中關(guān)于 netstat 命令中的 tcp listen 狀態(tài)下的 Recv-Q 和 Send-Q 這兩個(gè)信息的描述跟我的圖解網(wǎng)絡(luò)寫的不一樣?
我看了源碼后,確認(rèn)了這個(gè) man 手冊(cè)寫的不對(duì)。沒(méi)想到 Linux 的 man 手冊(cè)也會(huì)出錯(cuò)。
首先,先給大家介紹下 netstat 命令。netstat 命令是查看網(wǎng)絡(luò)狀態(tài)很常見的 Linux 命令。比如,如果我們想查看系統(tǒng)中的進(jìn)程監(jiān)聽了哪些 TCP 端口,則可以使用下面這個(gè)命令 netstat -napt:
接下來(lái),小林帶大家分析,為什么我說(shuō) man 手冊(cè)寫錯(cuò)了 netstat 命令中 Recv-Q 和 Send-Q 的描述?
疑惑提出讀者提出的疑惑:
我先給大家翻譯一下,man 手冊(cè)(https://man7.org/linux/man-pages/man8/netstat.8.html)是怎么說(shuō)的:
- Recv-Q:如果 TCP 連接狀態(tài)處于 Established,Recv-Q 的數(shù)值表示接收緩沖區(qū)中還沒(méi)拷貝到應(yīng)用層的數(shù)據(jù)大小;如果 TCP 連接狀態(tài)處于 Listen 狀態(tài),Recv-Q 的數(shù)值表示當(dāng)前 syn 半連接隊(duì)列的大小(自內(nèi)核版本 2.6.18 起)
- Send-Q:如果 TCP 連接狀態(tài)處于 Established,Send-Q的數(shù)值表示發(fā)送緩沖區(qū)中已發(fā)送但未被確認(rèn)的數(shù)據(jù)大小;如果 TCP 連接狀態(tài)處于 Listen 狀態(tài),Send-Q 的數(shù)值表示 syn 半連接隊(duì)列的容量(自內(nèi)核版本 2.6.18 起)。
而我通過(guò)查閱內(nèi)核 2.6.18 版本的源碼,得到的結(jié)論如下:
- Recv-Q:如果 TCP 連接狀態(tài)處于 Established,Recv-Q 的數(shù)值表示接收緩沖區(qū)中還沒(méi)拷貝到應(yīng)用層的數(shù)據(jù)大小;如果 TCP 連接狀態(tài)處于 Listen 狀態(tài),Recv-Q 的數(shù)值表示當(dāng)前 syn 半連接隊(duì)列的大小 當(dāng)前全連接隊(duì)列的大小;
- Send-Q:如果 TCP 連接狀態(tài)處于 Established,Send-Q的數(shù)值表示發(fā)送緩沖區(qū)中已發(fā)送但未被確認(rèn)的數(shù)據(jù)大小;如果 TCP 連接狀態(tài)處于 Listen 狀態(tài),Send-Q 的數(shù)值表示 syn 半連接隊(duì)列的容量
上面被我劃掉的部分,就是我與 man 手冊(cè)差異的地方。
什么是 TCP 半連接隊(duì)列和全鏈接隊(duì)列?
在 TCP 三次握手的時(shí)候,Linux 內(nèi)核會(huì)維護(hù)兩個(gè)隊(duì)列,分別是:
- 半連接隊(duì)列,也稱 SYN 隊(duì)列;
- 全連接隊(duì)列,也稱 accept 隊(duì)列;
服務(wù)端收到客戶端發(fā)起的 SYN 請(qǐng)求后,內(nèi)核會(huì)把該連接存儲(chǔ)到半連接隊(duì)列,并向客戶端響應(yīng) SYN+ACK,接著客戶端會(huì)返回 ACK,服務(wù)端收到第三次握手的 ACK 后,內(nèi)核會(huì)把連接從半連接隊(duì)列移除,然后創(chuàng)建新的完全的連接,并將其添加到全連接隊(duì)列,等待進(jìn)程調(diào)用 accept 函數(shù)時(shí)把連接取出來(lái)。
如果你想知道 TCP 半連接和全連接溢出會(huì)發(fā)生什么?可以看看這篇文章:TCP 半連接隊(duì)列和全連接隊(duì)列滿了會(huì)發(fā)生什么?又該如何應(yīng)對(duì)?
源碼分析
netstat 工具在獲取 TCP 連接的信息的時(shí)候,實(shí)際上是讀取了 /proc/net/tcp 文件里的數(shù)據(jù),而這個(gè)文件的數(shù)據(jù)是由內(nèi)核由 net/ipv4/tcp_ipv4.c 文件中的 tcp4_seq_show() 函數(shù)打印的。
所以,我們直接看 tcp4_seq_show() 函數(shù)是根據(jù)什么信息打印出 Recv-Q 和 Send-Q 的數(shù)據(jù)。
有一個(gè)網(wǎng)站可以在線看 Linux 內(nèi)核代碼:https://elixir.bootlin.com/,每個(gè)內(nèi)核版本的代碼都有,平常我都是在這里看。
這次,我們選擇內(nèi)核版本為 2.6.18 查看 tcp4_seq_show() 函數(shù)的實(shí)現(xiàn),如下:
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 連接狀態(tài)為 ESTABLISHED 和 LISTENING 時(shí)打印的信息,所以接下來(lái)看 get_tcp4_sock 函數(shù)。
get_tcp4_sock 函數(shù)中,打印信息的代碼如下:
我在圖中標(biāo)紅了兩行代碼,這兩行代碼分別是 Recv-Q 和 Send-Q 的數(shù)據(jù)。
我單獨(dú)把這兩行代碼抽了出來(lái):
// Send-Q 打印的數(shù)據(jù)
tp->write_seq - tp->snd_una,
//Recv-Q 打印的數(shù)據(jù)
(sp->sk_state == TCP_LISTEN) ? sp->sk_ack_backlog : (tp->rcv_nxt - tp->copied_seq),
可以看到, 不管 TCP 連接狀態(tài)是什么, Send-Q 都是發(fā)送緩沖區(qū)中已發(fā)送但未被確認(rèn)的數(shù)據(jù)大小。
然后針對(duì) Recv-Q ,在 TCP 連接狀態(tài)為 LISTEN 時(shí),打印的是 sk_ack_backlog 的值。
那 sk_ack_backlog 的值代表什么意思呢?
下面這個(gè)是判斷全連接隊(duì)列是否溢出的函數(shù):
可以得知,sk_ack_backlog 其實(shí)是當(dāng)前全連接隊(duì)列的大小,也就是經(jīng)歷三次握手后等待被應(yīng)用層 accpet() 的連接的數(shù)量。
所以,從上面的源碼分析過(guò),得到的結(jié)論如下:
- netstat 命令中的 Recv-Q:如果 TCP 連接狀態(tài)處于 Established,Recv-Q 的數(shù)值表示接收緩沖區(qū)中還沒(méi)拷貝到應(yīng)用層的數(shù)據(jù)大小;如果 TCP 連接狀態(tài)處于 Listen 狀態(tài),Recv-Q 的數(shù)值表示當(dāng)前全連接隊(duì)列的大小;
- netstat 命令中的 Send-Q:表示發(fā)送緩沖區(qū)中已發(fā)送但未被確認(rèn)的數(shù)據(jù)大小(不管 TCP 是 Listen 狀態(tài)還是 Established 狀態(tài)都表示這個(gè)意思);
好了,至此就分析完了。
最后
看到這,大家肯定會(huì)說(shuō):小林你太強(qiáng)了吧,為什么對(duì) Linux 內(nèi)核源碼那么熟,這都能分析出來(lái)。
其實(shí),我并沒(méi)有熟讀過(guò) Linux 內(nèi)核源碼啦,其實(shí)只要大家有好奇心,其實(shí)你也能分析出來(lái)。
我也是通過(guò)網(wǎng)上的資料,一點(diǎn)一點(diǎn)分析出來(lái)的,并不是直接就在內(nèi)核源碼里查,不然那真是大海撈針。
我是這樣一步一步查資料分析的:
- 先網(wǎng)上查下 netstat 源碼,看是根據(jù)什么信息打印 Send-Q 和 Recv-Q,然后看到網(wǎng)上有人說(shuō)是讀 /proc/net/tcp 這個(gè)文件;
- 接著,就網(wǎng)上查 /proc/net/tcp 這個(gè)文件是怎么打印的,然后看到網(wǎng)上有人說(shuō)是由 net/ipv4/tcp_ipv4.c 文件中的 tcp4_seq_show() 函數(shù)打印的;
- 最后,再自己去看 tcp4_seq_show 函數(shù)的實(shí)現(xiàn),這個(gè)函數(shù)的代碼也不多,就幾十行,所以很容易就分析出來(lái)了。
你看,其實(shí)我也是通過(guò)「搜索」一步一步分析出來(lái)的,其實(shí)并沒(méi)有什么難度。