网卡状态变更,virtio-net检测
实现方案:
现在在amp模式下linux端有个真实的物理网卡eth0,有一个虚拟网卡virtio-net0后端,此时需要一种机制,将真实物理网卡的状态发送rtos的virtio-net0前端。这里使用register_netdevice_notifier机制,每个virtio-net设备都有自己的notifier,支持多个virtio-net设备实例。
这个实现的特点:
- 每个virtio-net设备独立管理自己的notifier,支持多实例
- 正确处理网卡引用计数(dev_get_by_name/dev_put)
- 可以通过carrier_notify_enabled动态控制状态同步
- 在probe时同步初始状态
- 在remove时正确清理资源
注意:
- 使用字符串保存物理网卡名称,而不是直接保存指针,否则可能会出现物理网卡还未初始化的情况。
- 不管物理网卡是否就绪都会注册notifier
- 在notifier回调中,当检测到目标网卡时才获取其设备指针
- 避免了在probe时因找不到网卡而无法注册notifier的问题
这里详细讲讲register_netdevice_notifier通知链。register_netdevice_notifier
是 Linux 网络子系统中的一个通知链(Notification Chain)机制,它允许内核模块注册一个回调函数来监听网络设备状态的变化。主要用途如下:
- 通知链的基本概念:
struct notifier_block {int (*notifier_call)(struct notifier_block *, unsigned long, void *);struct notifier_block *next;int priority;
};
- 主要事件类型:
// 一些重要的网络设备事件
#define NETDEV_UP 0x0001 // 网卡启用
#define NETDEV_DOWN 0x0002 // 网卡禁用
#define NETDEV_REBOOT 0x0003 // 网卡重启
#define NETDEV_CHANGE 0x0004 // 网卡状态改变
#define NETDEV_REGISTER 0x0005 // 网卡注册
#define NETDEV_UNREGISTER 0x0006 // 网卡注销
#define NETDEV_CHANGEMTU 0x0007 // MTU改变
#define NETDEV_CHANGEADDR 0x0008 // MAC地址改变
- 注册流程:
// 1. 定义通知块
struct notifier_block netdev_notifier = {.notifier_call = virtnet_netdev_event, // 回调函数
};// 2. 注册通知
register_netdevice_notifier(&netdev_notifier);// 3. 实现回调函数
static int virtnet_netdev_event(struct notifier_block *this,unsigned long event, void *ptr)
{struct net_device *dev = netdev_notifier_info_to_dev(ptr);switch(event) {case NETDEV_UP:// 处理网卡启用事件break;case NETDEV_DOWN:// 处理网卡禁用事件break;// ...}return NOTIFY_DONE;
}
- 工作原理:
- 当网络设备状态发生变化时,内核会遍历所有注册的通知块
- 按照优先级顺序调用每个通知块的 notifier_call 回调函数
- 回调函数可以返回:
- NOTIFY_OK: 事件处理成功
- NOTIFY_DONE: 事件与该模块无关
- NOTIFY_BAD: 事件处理失败
- 在你的代码中的应用:
// 注册通知监听物理网卡状态
vi->netdev_notifier.notifier_call = virtnet_netdev_event;
vi->carrier_notify_enabled = true;err = register_netdevice_notifier(&vi->netdev_notifier);
if (err) {dev_err(&vdev->dev, "Failed to register netdev notifier\n");return err;
}
当物理网卡状态发生变化时:
- UP事件: 更新虚拟网卡状态为启用,并通知前端
- DOWN事件: 更新虚拟网卡状态为禁用,并通知前端
- CHANGE事件: 根据物理网卡carrier状态更新虚拟网卡
- 注销流程:
// 在驱动卸载时需要注销通知
unregister_netdevice_notifier(&vi->netdev_notifier);
这种通知机制的优点:
- 解耦合 - 模块间通过事件通知而不是直接调用
- 灵活性 - 可以动态注册/注销监听器
- 实时性 - 能及时获得网络设备状态变化
- 可扩展 - 支持多个模块同时监听同一事件
在你的虚拟网卡驱动中,这个机制用于同步物理网卡和虚拟网卡的状态,确保虚拟网卡能正确反映物理网卡的工作状态。
结构体:
struct virtnet_info {...char eth_name[IFNAMSIZ]; // 保存物理网卡名称struct net_device *eth_dev; // 物理网卡指针(动态获取)struct notifier_block netdev_notifier;bool carrier_notify_enabled;}
初始化:
static int virtnet_probe(struct virtio_device *vdev)
{/* 在netdev注册后初始化carrier notify */printk("------------>carrier notify init\r\n");// 设置要监控的物理网卡名称,最好是设备树中定义的网卡名称,先写定strncpy(vi->eth_name, "eth0", IFNAMSIZ-1);vi->eth_name[IFNAMSIZ-1] = '\0';vi->eth_dev = NULL; // 初始时设为NULL// 初始化并注册notifiervi->netdev_notifier.notifier_call = virtnet_netdev_event;// 设置notifier为启用状态,最好是设备树中定义的状态是否启用vi->carrier_notify_enabled = true;err = register_netdevice_notifier(&vi->netdev_notifier);if (err) {dev_err(&vdev->dev, "Failed to register netdev notifier\n");goto free_unregister_netdev;}
通知链中的触发处理函数:
static int virtnet_netdev_event(struct notifier_block *this,unsigned long event, void *ptr)
{struct net_device *dev = netdev_notifier_info_to_dev(ptr);struct virtnet_info *vi = container_of(this, struct virtnet_info, netdev_notifier);struct virtio_net_config *config;int cpu = 2;u16 status;// 检查参数有效性if (!dev || !vi || !vi->dev)return NOTIFY_DONE;// 只关心指定物理网卡的事件if (strcmp(dev->name, vi->eth_name) != 0)return NOTIFY_DONE;// 如果找到匹配的网卡,保存其指针if (!vi->eth_dev) {vi->eth_dev = dev;dev_hold(dev); // 增加引用计数printk("------------>Found target network device: %s\n", dev->name);}if (!vi->carrier_notify_enabled)return NOTIFY_DONE;// 获取共享内存中的配置区域指针config = (struct virtio_net_config *)vi->vdev->config_ptr;switch (event) {case NETDEV_UP:// 物理网卡up时,更新carrier状态和共享内存状态netif_carrier_on(vi->dev);status = VIRTIO_NET_S_LINK_UP | VIRTIO_NET_S_ANNOUNCE;config->status = cpu_to_virtio16(vi->vdev, status);virtio_irq_trigger(virtio_irq_controller_get_default(), vi->vdev->irq, cpu);break;case NETDEV_DOWN:// 物理网卡down时,更新carrier状态和共享内存状态netif_carrier_off(vi->dev);status = VIRTIO_NET_S_ANNOUNCE; // 只设置ANNOUNCE标志config->status = cpu_to_virtio16(vi->vdev, status);virtio_irq_trigger(virtio_irq_controller_get_default(), vi->vdev->irq, cpu);break;case NETDEV_CHANGE:......break;default:return NOTIFY_DONE;}return NOTIFY_OK;
}