ethtool 讀到的鏈接狀態
使用 ethtool 讀取網卡鏈接狀態的一個示例如下:
longyu@longyu-pc:~$ sudo /sbin/ethtool ens37
[sudo] password for longyu:
Settings for ens37:
Supported ports: [ TP ]
Supported link modes: 10baseT/Half 10baseT/Full
100baseT/Half 100baseT/Full
1000baseT/Full
Supported pause frame use: No
Supports auto-negotiation: Yes
Supported FEC modes: Not reported
Advertised link modes: 10baseT/Half 10baseT/Full
100baseT/Half 100baseT/Full
1000baseT/Full
......
Link detected: yes
上面的示例中,最後一行中的 Link detected 表示鏈路的狀態,爲 yes 表示鏈路 up,爲 no 表示鏈路 down。目前鏈路爲 up 狀態。同時上面的輸出中也表明此網卡支持自動協商。
自動協商用於網卡端口與對端協商連接速度和雙工模式,通過協商確定兩端能夠達到的最大連接速度與兩端都支持的雙工模式,主要與 phy 有關。
通過搜索,我發現瞭如下鏈接:
下面的信息來於 以太網自動協商的原理 這篇博客。
千兆光口自協商過程:
1.兩端都設置爲自協商模式
雙方互相發送/C/碼流,如果連續接收到3個相同的/C/碼且接收到的碼流和本端工作方式相匹配,則返回給對方一個帶有Ack應答的/C/碼,對端接收到Ack信息後,認爲兩者可以互通,設置端口爲UP狀態
2.一端設置爲自協商,一端設置爲強制
自協商端發送/C/碼流,強制端發送/I/碼流,強制端無法給對端提供本端的協商信息,也無法給對端返回Ack應答,故自協商端DOWN。但是強制端本身可以識別/C/碼,認爲對端是與自己相匹配的端口,所以直接設置本端端口爲UP狀態
3.兩端均設置爲強制模式
雙方互相發送/I/碼流,一端接收到/I/碼流後,認爲對端是與自己相匹配的端口,直接設置本端端口爲UP狀態
閱讀上面的信息可以發現,當兩端都設置爲自協商模式時,自協商成功後兩端的端口狀態都爲 UP;當一端設置自協商,一端設置強制時,自協商時設置自協商模式的端口狀態會變爲 DOWN,設置強制端的端口狀態會變爲 UP;當兩端均設置爲強制模式時,進行自協商會使兩端端口都變爲 UP 狀態。
對端口執行自協商
有了這個基礎我們來進行下面的操作,這裏我使用的網卡型號如下:
02:05.0 Ethernet controller: Intel Corporation 82545EM Gigabit Ethernet Controller (Copper) (rev 01)
這款網卡在我的系統中對應的 netdev 接口的名字爲 ens37。
1. 執行 sudo ifconfig ens37 down 命令將網卡設定爲 down
ethotool 查看鏈路狀態,輸出信息截取如下:
longyu@longyu-pc:~$ sudo ethtool ens37
[sudo] password for longyu:
Settings for ens37:
Supported ports: [ TP ]
Supported link modes: 10baseT/Half 10baseT/Full
100baseT/Half 100baseT/Full
1000baseT/Full
Supported pause frame use: No
Supports auto-negotiation: Yes
.........
Link detected: no
Link detected 爲 no 表示鏈路爲 down 狀態。
ethtool dump 寄存器信息,有如下與鏈路狀態相關的信息:
longyu@longyu-pc:~$ sudo ethtool -d ens37 | grep -i 'Link'
Link reset: reset
Set link up: 1
Link up: link config
Link speed: 1000Mb/s
Link State: Down
Force Link Good: disabled
此時 Link State 爲 Down 與上面的 Link detected : no 一致。
2. 執行 sudo ethtool -s ens37 autoneg on 進行自協商
ethtool 查看鏈路狀態,截取主要信息如下:
longyu@longyu-pc:~$ sudo ethtool ens37
Settings for ens37:
Supported ports: [ TP ]
Supported link modes: 10baseT/Half 10baseT/Full
100baseT/Half 100baseT/Full
1000baseT/Full
Supported pause frame use: No
Supports auto-negotiation: Yes
......
Link detected: no
Link detected: no 表示鏈路爲 down 的狀態。
ethtool -d 查看寄存器信息,相關內容如下:
longyu@longyu-pc:~$ sudo ethtool -d ens37 | grep -i 'Link'
Link reset: reset
Set link up: 1
Link up: link config
Link speed: 1000Mb/s
Link State: Up
Force Link Good: disabled
注意這裏 Link State 狀態變爲 UP,這表明自協商成功。根據上文中引用的千兆光口自協商的過程,同時注意到我們的網卡支持自協商,我們用 ethtool -s 命令打開網卡的自協商功能,這之後 phy 的狀態變爲 UP 表明自協商成功,這與千兆光口自協商過程的第一種類型一致,兩端都支持並開啓了自協商,協商成功後兩端的 phy 狀態都變爲了 UP。
這時候 ethtool 直接查看網卡,Link detected 顯示爲 no 表明鏈路狀態爲 down,爲什麼不是 UP 呢?
這裏 phy 的狀態由 DOWN 變爲 UP 這是自協商成功的結果。這個是可以解釋的。自協商的目的就是爲了確定連接速度、雙工模式這些配置,而這些配置都是要在 phy up 的狀態下才有作用。
這時候直接使用 ifconfig 查看網卡信息,輸出如下:
longyu@longyu-pc:~$ sudo ifconfig ens37
ens37: flags=4098<BROADCAST,MULTICAST> mtu 1500
ether 00:0c:29:5e:ba:35 txqueuelen 1000 (Ethernet)
RX packets 1154 bytes 672588 (656.8 KiB)
RX errors 0 dropped 0 overruns 0 frame 0
TX packets 136 bytes 16616 (16.2 KiB)
TX errors 0 dropped 0 overruns 0 carrier 0 collisions 0
上面的輸出表明網卡的鏈路狀態爲 down,這與 ethtool 命令查看到的網卡狀態爲 no 是一致的。
ethtool: Link detected: no 是怎樣檢測的?
這裏我使用 ethtool-4.19 的源代碼進行分析。
首先是 ethtool 中設定 ETHTOOL_GLINK 命令,調用 ioctl 函數獲取鏈路狀態。相關代碼如下:
edata.cmd = ETHTOOL_GLINK;
err = send_ioctl(ctx, &edata);
if (err == 0) {
fprintf(stdout, " Link detected: %s\n",
edata.data ? "yes":"no");
allfail = 0;
} else if (errno != EOPNOTSUPP) {
perror("Cannot get link status");
}
edata.cmd 中填充的是 ethtool 中的子命令,屬於 SIOCETHTOOL 下面的子命令。send_ioctol 函數的源碼如下:
注意這裏的子命令通過 ctx->ifr 來傳遞給 ioctl。
int send_ioctl(struct cmd_context *ctx, void *cmd)
{
ctx->ifr.ifr_data = cmd;
return ioctl(ctx->fd, SIOCETHTOOL, &ctx->ifr);
}
這之後 ioctl 會進行分發,由 ioctl 到 sock_ioctl 到 dev_ioctl 到 dev_ethtool 適配層。dev_ethtool 適配層相關函數在內核路徑下的 net/core/ethtool.c 文件中。
dev_ethtool 函數是一個大的分發函數,通過 switch 來將不同的 ethtool 子命令分發到不同的子函數調用之上。子函數裏面的核心邏輯在於調用網卡內核接口 net_device 中註冊的 ethtool_ops 虛函數表中的函數。
上面 Link detected 中使用的 ethtool 子命令爲 ETHTOOL_GLINK,在 dev_ethtool 函數中被分發到 ethtool_get_link 子函數。相關代碼如下:
case ETHTOOL_GLINK:
rc = ethtool_get_link(dev, useraddr);
break;
ethtool_get_link 子函數的核心在於下面這行代碼:
edata.data = netif_running(dev) && dev->ethtool_ops->get_link(dev);
netif_running 函數在內核頭文件路徑 include/linux/netdevice.h 中定義,它通過檢測 netdev 結構體中 state 變量的 __LINK_STATE_START 位來確定接口是否 running。
static inline bool netif_running(const struct net_device *dev)
{
return test_bit(__LINK_STATE_START, &dev->state);
}
瞭解了 ethtool_get_link 子函數的部分代碼,我們可以發現,上文中提到的在網卡接口 down 的狀態下進行自協商後,phy 的狀態變爲 UP,而 ethtool 輸出的 Link detected 項爲 no 的情況是正常的現象。
此時 ifconfig 顯示的網卡狀態不是 RUNNING,netif_running 將會返回 false,&& 語句之後的讀取硬件寄存器中保存的鏈路狀態的操作將被忽略,edata.data 將會返回 false,對應 ethtool 中 Link detected 項的輸出爲 no。
dev->ethtool_ops->get_link(dev) vs rte_eth_link_get
dev->ethtool_ops->get_link(dev)
最終是通過訪問網卡中的寄存器來獲取鏈路狀態。
dpdk 中的 rte_eth_link_get 函數根據 lsc 中斷是否開啓,有兩種不同的處理方式。
-
lsc 中斷使能
原子讀取 dev 結構體中的 eth_link 成員。這個成員只能在 interrupt host 線程中被更新。用戶註冊的 lsc 中斷回調函數也是在 interrupt host 線程中被調用的,可以在 lsc 中斷回調函數中改變 eth_link 的值。
-
lsc 中斷關閉
調用 dev_ops 中實現的 link_update 函數,該函數通過直接訪問網卡寄存器來獲取鏈路的最新狀態。
在 lsc 中斷關閉的情況下,rte_eth_link_get 與 dev->ethtool-ops->get_link(dev) 最終都是通過訪問網卡寄存器來確定鏈路狀態的。
至於說 ethtool 讀到的狀態與 dpdk 讀到的網卡狀態不一致,這是指 ethtool 中的 Link detected 中檢測到的鏈路狀態與 rte_eth_link_get 函數的輸出不同。
從上面的分析中我們可以發現,ethtool 中的 Link detected 輸出 yes 的必備條件還有 netif_running 返回 true,而 rte_eth_link_get 卻沒有使用這個狀態,這兩者從邏輯上來說也不是在任何時候都會一致的。
綜上所述,這個問題其實不是功能的問題,而是不瞭解功能的實現而誤判的問題。