網路裝置、核心與您!

簡介

以下是關於網路裝置的文件的隨機集合。它面向驅動程式開發人員。

struct net_device 生命週期規則

即使在模組解除安裝後,網路裝置結構也需要持久存在,並且必須使用 alloc_netdev_mqs() 及其朋友分配。如果裝置已成功註冊,則將在 free_netdev() 的最後一次使用時釋放它。這是乾淨地處理病態情況所必需的(例如:rmmod mydriver </sys/class/net/myeth/mtu

alloc_netdev_mqs() / alloc_netdev() 為驅動程式私有資料保留額外的空間,這些空間在網路裝置釋放時會被釋放。如果單獨分配的資料附加到網路裝置 (netdev_priv()),則由模組退出處理程式來釋放它。

有兩組 API 用於註冊 struct net_device。第一組可以在未持有 rtnl_lock 的正常上下文中使用:register_netdev(), unregister_netdev()。第二組可以在已經持有 rtnl_lock 時使用:register_netdevice(), unregister_netdevice(), free_netdevice().

簡單驅動程式

大多數驅動程式(尤其是裝置驅動程式)在未持有 rtnl_lock 的上下文中處理 struct net_device 的生命週期(例如,驅動程式探測和刪除路徑)。

在這種情況下,struct net_device 註冊是使用 register_netdev()unregister_netdev() 函式完成的。

int probe()
{
  struct my_device_priv *priv;
  int err;

  dev = alloc_netdev_mqs(...);
  if (!dev)
    return -ENOMEM;
  priv = netdev_priv(dev);

  /* ... do all device setup before calling register_netdev() ...
   */

  err = register_netdev(dev);
  if (err)
    goto err_undo;

  /* net_device is visible to the user! */

err_undo:
  /* ... undo the device setup ... */
  free_netdev(dev);
  return err;
}

void remove()
{
  unregister_netdev(dev);
  free_netdev(dev);
}

請注意,在呼叫 register_netdev() 之後,該裝置在系統中可見。使用者可以開啟它並立即開始傳送/接收流量,或者執行任何其他回撥,因此所有初始化必須在註冊之前完成。

unregister_netdev() 關閉裝置並等待所有使用者完成操作。 struct net_device 本身的記憶體可能仍然被 sysfs 引用,但對該裝置的所有操作都將失敗。

free_netdev() 可以在 unregister_netdev() 返回時或者 register_netdev() 失敗時呼叫。

RTNL 下的裝置管理

在已經持有 rtnl_lock 的上下文中註冊 struct net_device 需要格外小心。在這些情況下,大多數驅動程式都希望使用 struct net_deviceneeds_free_netdevpriv_destructor 成員來釋放狀態。

rtnl_lock 下處理 netdev 的示例流程

static void my_setup(struct net_device *dev)
{
  dev->needs_free_netdev = true;
}

static void my_destructor(struct net_device *dev)
{
  some_obj_destroy(priv->obj);
  some_uninit(priv);
}

int create_link()
{
  struct my_device_priv *priv;
  int err;

  ASSERT_RTNL();

  dev = alloc_netdev(sizeof(*priv), "net%d", NET_NAME_UNKNOWN, my_setup);
  if (!dev)
    return -ENOMEM;
  priv = netdev_priv(dev);

  /* Implicit constructor */
  err = some_init(priv);
  if (err)
    goto err_free_dev;

  priv->obj = some_obj_create();
  if (!priv->obj) {
    err = -ENOMEM;
    goto err_some_uninit;
  }
  /* End of constructor, set the destructor: */
  dev->priv_destructor = my_destructor;

  err = register_netdevice(dev);
  if (err)
    /* register_netdevice() calls destructor on failure */
    goto err_free_dev;

  /* If anything fails now unregister_netdevice() (or unregister_netdev())
   * will take care of calling my_destructor and free_netdev().
   */

  return 0;

err_some_uninit:
  some_uninit(priv);
err_free_dev:
  free_netdev(dev);
  return err;
}

如果設定了 struct net_device.priv_destructor,它將在 unregister_netdevice() 之後由核心在某個時間呼叫,如果 register_netdevice() 失敗,它也會被呼叫。可以持有或不持有 rtnl_lock 呼叫回撥。

沒有顯式的建構函式回撥,驅動程式在分配私有 netdev 狀態後和註冊之前“構造”它。

設定 struct net_device.needs_free_netdev 使核心在 unregister_netdevice() 之後,當對裝置的所有引用都消失時自動呼叫 free_netdevice()。它僅在成功呼叫 register_netdevice() 後生效,因此如果 register_netdevice() 失敗,驅動程式負責呼叫 free_netdev()

free_netdev() 在 unregister_netdevice() 之後或 register_netdevice() 失敗時,可以在錯誤路徑上安全地呼叫。 netdev (取消)註冊過程的部分發生在釋放 rtnl_lock 後,因此在這些情況下,free_netdev() 將推遲部分處理,直到釋放 rtnl_lock

從 struct rtnl_link_ops 產生的裝置永遠不應直接釋放 struct net_device

.ndo_init 和 .ndo_uninit

.ndo_init.ndo_uninit 回撥在 net_device 註冊和取消註冊期間,在 rtnl_lock 下呼叫。驅動程式可以使用它們,例如,當它們的初始化過程的部分需要在 rtnl_lock 下執行時。

.ndo_init 在裝置在系統中可見之前執行,.ndo_uninit 在裝置關閉後取消註冊期間執行,但其他子系統可能仍然具有對 netdevice 的未完成引用。

MTU

每個網路裝置都有一個最大傳輸單元。 MTU 不包括任何鏈路層協議開銷。上層協議不得將套接字緩衝區 (skb) 傳遞給裝置以傳輸超過 mtu 的資料。 MTU 不包括鏈路層標頭開銷,因此例如在乙太網上,如果使用標準 MTU 1500 位元組,則實際 skb 將包含最多 1514 位元組,因為乙太網標頭。裝置應允許 4 位元組 VLAN 標頭。

分段解除安裝 (GSO, TSO) 是此規則的例外。上層協議可能會將一個大的套接字緩衝區傳遞給裝置傳輸例程,並且裝置將根據當前的 MTU 將其分解為單獨的資料包。

MTU 是對稱的,適用於接收和傳輸。裝置必須能夠接收至少 MTU 允許的最大尺寸資料包。網路裝置可以使用 MTU 作為調整接收緩衝區大小的機制,但裝置應允許帶有 VLAN 標頭的資料包。使用 1500 位元組的標準乙太網 mtu,裝置應允許最多 1518 位元組的資料包(1500 + 14 標頭 + 4 標籤)。裝置可以:丟棄、截斷或傳遞超大尺寸的資料包,但最好丟棄超大尺寸的資料包。

struct net_device 同步規則

ndo_open

同步:rtnl_lock() 訊號量。此外,如果驅動程式實現了佇列管理或整形器 API,則會使用 netdev 例項鎖。上下文:程序

ndo_stop

同步:rtnl_lock() 訊號量。此外,如果驅動程式實現了佇列管理或整形器 API,則會使用 netdev 例項鎖。上下文:程序 注意:保證 netif_running() 為 false

ndo_do_ioctl

同步:rtnl_lock() 訊號量。

這僅由網路子系統在內部呼叫,而不是由使用者空間呼叫 ioctl 呼叫,就像在 linux-5.14 之前一樣。

ndo_siocbond

同步:rtnl_lock() 訊號量。此外,如果驅動程式實現了佇列管理或整形器 API,則會使用 netdev 例項鎖。上下文:程序

由繫結驅動程式用於 SIOCBOND 系列 ioctl 命令。

ndo_siocwandev

同步:rtnl_lock() 訊號量。此外,如果驅動程式實現了佇列管理或整形器 API,則會使用 netdev 例項鎖。上下文:程序

由 drivers/net/wan 框架用於處理帶有 if_settings 結構的 SIOCWANDEV ioctl。

ndo_siocdevprivate

同步:rtnl_lock() 訊號量。此外,如果驅動程式實現了佇列管理或整形器 API,則會使用 netdev 例項鎖。上下文:程序

這用於實現 SIOCDEVPRIVATE ioctl 助手。不應將這些新增到新驅動程式中,因此請勿使用。

ndo_eth_ioctl

同步:rtnl_lock() 訊號量。此外,如果驅動程式實現了佇列管理或整形器 API,則會使用 netdev 例項鎖。上下文:程序

ndo_get_stats

同步:RCU(可以與統計資訊更新路徑併發呼叫)。上下文:原子(不能在 RCU 下休眠)

ndo_start_xmit

同步:__netif_tx_lock 自旋鎖。

當驅動程式設定 dev->lltx 時,這將在不持有 netif_tx_lock 的情況下呼叫。在這種情況下,驅動程式必須在需要時自行鎖定。那裡的鎖定也應該正確地防止 set_rx_mode。警告:不建議使用 dev->lltx。不要將其用於新驅動程式。

上下文:停用 BH 的程序或 BH(計時器),

將透過 netconsole 停用中斷來呼叫。

返回程式碼

  • NETDEV_TX_OK 一切正常。

  • NETDEV_TX_BUSY 無法傳輸資料包,稍後重試 通常是一個錯誤,意味著驅動程式中的佇列啟動/停止流量控制已損壞。注意:驅動程式不得將 skb 放入其 DMA 環中。

ndo_tx_timeout

同步:netif_tx_lock 自旋鎖;所有 TX 佇列已凍結。上下文:停用 BH 注意:保證 netif_queue_stopped() 為 true

ndo_set_rx_mode

同步:netif_addr_lock 自旋鎖。上下文:停用 BH

ndo_setup_tc

TC_SETUP_BLOCKTC_SETUP_FT 在 NFT 鎖下執行(即沒有 rtnl_lock 也沒有裝置例項鎖)。如果驅動程式實現了佇列管理或整形器 API,則其餘的 tc_setup_type 型別會在 netdev 例項鎖下執行。

上面列表中未指定的大多數 ndo 回撥都在 rtnl_lock 下執行。此外,如果驅動程式實現了佇列管理或整形器 API,則也會使用 netdev 例項鎖。

struct napi_struct 同步規則

napi->poll
同步

napi->state 中的 NAPI_STATE_SCHED 位。裝置驅動程式的 ndo_stop 方法將在所有 NAPI 例項上呼叫 napi_disable(),這將對 NAPI_STATE_SCHED napi->state 位執行睡眠輪詢,等待所有掛起的 NAPI 活動停止。

上下文

softirq 將透過 netconsole 停用中斷來呼叫。

netdev 例項鎖

從歷史上看,所有網路控制操作都由一個稱為 rtnl_lock 的單一全域性鎖保護。目前正在努力用每個網路名稱空間的單獨鎖來替換此全域性鎖。此外,單個 netdev 的屬性越來越多地受到每個 netdev 鎖的保護。

對於實現整形或佇列管理 API 的裝置驅動程式,所有控制操作都將在 netdev 例項鎖下執行。驅動程式還可以透過將 request_ops_lock 設定為 true 來顯式請求在操作期間持有例項鎖。程式碼註釋和文件將操作在例項鎖下呼叫的驅動程式稱為“ops locked”。另請參閱 struct net_devicelock 成員的文件。

將來,可以選擇讓單個驅動程式選擇不使用 rtnl_lock,而是直接在 netdev 例項鎖下執行其控制操作。

鼓勵裝置驅動程式儘可能依賴例項鎖。

對於需要與核心堆疊互動的(主要是軟體)驅動程式,有兩組介面:dev_xxx/netdev_xxxnetif_xxx (例如,dev_set_mtunetif_set_mtu)。 dev_xxx/netdev_xxx 函式處理獲取例項鎖本身,而 netif_xxx 函式假定驅動程式已經獲取了例項鎖。

struct net_device_ops

對於大多數驅動程式,呼叫 ndos 時不持有例項鎖。

“Ops locked” 驅動程式的大多數 ndos 將在例項鎖下呼叫。

struct ethtool_ops

ndos 類似,例項鎖僅對選定的驅動程式持有。對於“ops locked” 驅動程式,所有 ethtool 操作都應在例項鎖下呼叫,沒有例外。

struct netdev_stat_ops

對於“ops locked”驅動程式,在例項鎖下呼叫“qstat”操作,對於所有其他驅動程式,在 rtnl_lock 下呼叫。

struct net_shaper_ops

在持有 netdev 例項鎖的同時呼叫所有 net shaper 回撥。可以持有或不持有 rtnl_lock

請注意,支援 net shapers 會自動啟用“ops locking”。

struct netdev_queue_mgmt_ops

在持有 netdev 例項鎖的同時呼叫所有佇列管理回撥。可以持有或不持有 rtnl_lock

請注意,支援 struct netdev_queue_mgmt_ops 會自動啟用“ops locking”。

通知程式和 netdev 例項鎖

對於實現整形或佇列管理 API 的裝置驅動程式,某些通知程式 (enum netdev_cmd) 在 netdev 例項鎖下執行。

以下 netdev 通知程式始終在例項鎖下執行:* NETDEV_XDP_FEAT_CHANGE

對於具有鎖定操作的裝置,目前只有以下通知程式在鎖下執行:* NETDEV_CHANGE * NETDEV_REGISTER * NETDEV_UP

以下通知程式在沒有鎖的情況下執行:* NETDEV_UNREGISTER

對其餘通知程式沒有明確的期望。不在列表上的通知程式可以在有或沒有例項鎖的情況下執行,甚至可能使用和不使用鎖從不同的程式碼路徑呼叫相同的通知程式型別。目標是最終確保所有(或大多數,帶有一些記錄在案的例外)通知程式都在例項鎖下執行。每當您對從通知程式持有的鎖進行顯式假設時,請擴充套件此文件。

NETDEV_INTERNAL 符號名稱空間

作為 NETDEV_INTERNAL 匯出的符號只能在網路核心和僅透過主網路列表和樹流動的驅動程式中使用。請注意,反之則不然,NETDEV_INTERNAL 之外的大多數符號也不希望被 netdev 之外的隨機程式碼使用。符號可能缺少指定,因為它們早於名稱空間,或者僅僅是由於疏忽。