You need to enable JavaScript to run this app.
优惠活动
大模型
产品
解决方案
定价
更多

虚拟网卡ndo_start_xmit函数中如何按序发送第二个struct sk_buff

解决虚拟网卡中skb_second在skb_one之后完整走传输流程的方案

嘿,这个场景我在调试虚拟网卡驱动时刚好碰到过,咱们先拆解下你当前两种方法的问题,再给出可行的解决方案:

问题分析

  • 方法一:直接修改skb链表插入队列,确实能保证顺序,但跳过了网络栈中qdisc(队列调度器)的调度逻辑、设备状态检查、skb预处理(比如校验和计算、GSO分片)等关键环节,相当于绕过了虚拟网卡的完整传输流程,不符合内核网络栈的设计规范。
  • 方法二:直接调用dev_queue_xmit(skb_second)会让skb_second直接进入qdisc调度,但此时skb_one还在当前的ndo_start_xmit上下文里,内核会优先处理新提交的skb,导致顺序反转。

可行解决方案

方案一:利用qdisc队列插入尾部(推荐)

内核的qdisc本身就是负责管理发送顺序的组件,我们可以把skb_second插入到当前设备qdisc队列的尾部,让它跟随skb_one之后被调度,同时完整走传输流程:

static netdev_tx_t my_dev_start_xmit(struct sk_buff *skb_one, struct net_device *dev) {
    struct sk_buff *skb_second;
    struct Qdisc *q;
    struct sk_buff *to_free = NULL;
    int err;

    // 1. 先完成skb_one的正常处理(比如封装、校验和计算等)
    // ... 你的skb_one处理逻辑 ...

    // 2. 生成并初始化skb_second(绑定设备、设置协议等)
    skb_second = ...; // 你的skb_second生成逻辑
    skb_set_dev(skb_second, dev);
    skb_second->protocol = skb_one->protocol; // 保持和skb_one一致的协议
    // 其他必要的skb初始化...

    // 3. 获取当前设备的qdisc队列
    q = rcu_dereference_bh(dev->qdisc);

    // 4. 将skb_second插入qdisc队列尾部
    err = qdisc_enqueue(skb_second, q, &to_free);
    if (err == NET_XMIT_SUCCESS) {
        // 触发qdisc调度,确保skb_second被处理
        qdisc_run(q);
    } else {
        // 处理插入失败的情况,释放skb
        kfree_skb(skb_second);
    }

    // 处理to_free中的skb(如果有的话)
    if (to_free) {
        dev_kfree_skb_any(to_free);
    }

    // 5. 正常发送skb_one
    return dev_hard_start_xmit(skb_one, dev, NULL);
}

这种方式完全遵循内核网络栈的流程:skb_second会经过qdisc调度、再次进入ndo_start_xmit函数,而且因为是插入队列尾部,会严格在skb_one之后被发送。

方案二:使用tasklet延迟提交

如果觉得qdisc的操作有点复杂,也可以利用内核的tasklet(软中断任务)来延迟提交skb_second,确保它在skb_one发送完成后再走完整流程:

// 定义全局tasklet(或者绑定到设备私有数据)
static void my_xmit_tasklet(unsigned long data) {
    struct sk_buff *skb = (struct sk_buff *)data;
    dev_queue_xmit(skb);
}
DECLARE_TASKLET(my_dev_tasklet, my_xmit_tasklet, 0);

static netdev_tx_t my_dev_start_xmit(struct sk_buff *skb_one, struct net_device *dev) {
    struct sk_buff *skb_second;

    // 1. 处理并发送skb_one
    // ... 你的skb_one处理逻辑 ...
    netdev_tx_t ret = dev_hard_start_xmit(skb_one, dev, NULL);

    // 2. 生成并初始化skb_second
    skb_second = ...; // 你的skb_second生成逻辑
    skb_set_dev(skb_second, dev);
    // 其他必要的skb初始化...

    // 3. 用tasklet延迟提交skb_second
    my_dev_tasklet.data = (unsigned long)skb_second;
    tasklet_schedule(&my_dev_tasklet);

    return ret;
}

tasklet会在当前ndo_start_xmit流程结束后的软中断上下文执行,所以skb_second会在skb_one之后被提交,并且完整走dev_queue_xmit的所有流程。不过要注意:高负载下tasklet可能有微小延迟,但顺序是绝对保证的。

注意事项

  • 无论哪种方案,都要确保skb_second的引用计数正确,避免内存泄漏;
  • 如果你的虚拟网卡支持GSO、校验和卸载等特性,要给skb_second设置对应的skb标志位,和skb_one保持一致;
  • 方案一中的qdisc_enqueue可能会返回失败(比如队列满),一定要处理这种情况,及时释放skb_second。

内容的提问来源于stack exchange,提问作者Eknoes

相关产品推荐
方舟 Agent Plan

超全模态模型 × Harness 升级,最新支持 Deepseek-V4.1-Flash、GLM-5.3 系列、Doubao-Seedream-5.0-pro、Kimi-K3 (部分), 限时 9.9 元起

最近更新时间:2026.05.15 04:51:30