ethtool 讀到的連結狀态
使用 ethtool 讀取網卡連結狀态的一個示例如下:
[email protected]:~$ 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 檢視鍊路狀态,輸出資訊截取如下:
[email protected]:~$ 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 寄存器資訊,有如下與鍊路狀态相關的資訊:
[email protected]:~$ 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 檢視鍊路狀态,截取主要資訊如下:
[email protected]:~$ 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 檢視寄存器資訊,相關内容如下:
[email protected]:~$ 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 檢視網卡資訊,輸出如下:
[email protected]:~$ sudo ifconfig ens37
ens37: flags=4098 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 卻沒有使用這個狀态,這兩者從邏輯上來說也不是在任何時候都會一緻的。
綜上所述,這個問題其實不是功能的問題,而是不了解功能的實作而誤判的問題。