网络设备、内核与你!¶
简介¶
以下是关于网络设备的随机文档集合。
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_device
的 needs_free_netdev
和 priv_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 在禁用中断的情况下调用。