网络设备、内核与你!

简介

以下是关于网络设备的随机文档集合。

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()

在 unregister_netdevice() 之后或当 register_netdevice() 失败时,在错误路径上调用 free_netdev() 是安全的。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

每个网络设备都有一个最大传输单元(Maximum Transfer Unit)。MTU 不包括任何链路层协议开销。上层协议不得将数据量超过 MTU 的套接字缓冲区 (skb) 传递给设备进行传输。MTU 不包括链路层报头开销,因此例如在以太网上,如果使用标准 MTU 为 1500 字节,则由于以太网报头,实际的 skb 将包含最多 1514 字节。设备还应允许 4 字节的 VLAN 报头。

分段卸载 (GSO, TSO) 是这条规则的例外。上层协议可能会将一个大的套接字缓冲区传递给设备发送例程,设备会根据当前的 MTU 将其拆分为单独的数据包。

MTU 是对称的,同时适用于接收和发送。设备必须能够接收至少 MTU 允许的最大尺寸的数据包。网络设备可以使用 MTU 作为调整接收缓冲区大小的机制,但设备应允许带有 VLAN 头部的数据包。对于标准的以太网 MTU 1500 字节,设备应允许最大 1518 字节的数据包(1500 + 14 字节头部 + 4 字节标记)。设备可以选择:丢弃、截断或传递超大尺寸的数据包,但最好是丢弃超大尺寸的数据包。

struct net_device 同步规则

ndo_open

同步:rtnl_lock() 信号量。上下文:进程

ndo_stop

同步:rtnl_lock() 信号量。上下文:进程 注意:netif_running() 保证为 false

ndo_do_ioctl

同步:rtnl_lock() 信号量。上下文:进程

这仅由网络子系统在内部调用,而不是像 linux-5.14 之前那样由用户空间调用 ioctl。

ndo_siocbond

同步:rtnl_lock() 信号量。上下文:进程

由 bonding 驱动程序用于 SIOCBOND 系列的 ioctl 命令。

ndo_siocwandev

同步:rtnl_lock() 信号量。上下文:进程

由 drivers/net/wan 框架用于处理带有 if_settings 结构的 SIOCWANDEV ioctl。

ndo_siocdevprivate

同步:rtnl_lock() 信号量。上下文:进程

用于实现 SIOCDEVPRIVATE ioctl 助手。不应将其添加到新的驱动程序中,所以请勿使用。

ndo_eth_ioctl

同步:rtnl_lock() 信号量。上下文:进程

ndo_get_stats

同步:rtnl_lock() 信号量,或 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

struct napi_struct 同步规则

napi->poll
同步

napi->state 中的 NAPI_STATE_SCHED 位。设备驱动程序的 ndo_stop 方法将对所有 NAPI 实例调用 napi_disable(),这将对 NAPI_STATE_SCHED napi->state 位进行休眠轮询,等待所有挂起的 NAPI 活动停止。

上下文

软中断将由 netconsole 在禁用中断的情况下调用。