2. 实现网卡驱动的中断处理

任务说明

本案例为虚拟网卡驱动添加中断处理能力。目标是在驱动中注册中断处理函数,模拟硬件产生接收中断,并在中断上下文中完成数据包接收与上送协议栈的完整路径。通过本案例,你将理解内核中断注册机制、下半部(tasklet)的使用,以及NAPI在接收路径中的角色。

驱动框架与中断设计

我们基于一个虚拟网卡驱动(类似veth但更简化)实现中断处理。驱动维护一个环形缓冲区(ring buffer)模拟硬件接收队列。当有“数据包到达”时,通过调用自定义函数vnet_rx_irq_trigger()模拟硬件中断信号,该函数内部调用generic_handle_irq()或直接调用注册的中断处理函数(为简化,我们直接调用handler)。实际硬件中,中断控制器会调用驱动注册的handler,这里我们模拟该行为。

中断处理函数运行在中断上下文,不能睡眠,因此我们将实际的数据包处理工作放入tasklet(下半部)中执行。tasklet在软中断上下文运行,可以调用内核内存分配函数(如netdev_alloc_skb)。接收到的数据包通过netif_receive_skb()上送协议栈。

代码实现

// vnet_interrupt.c
#include <linux/module.h>
#include <linux/netdevice.h>
#include <linux/etherdevice.h>
#include <linux/interrupt.h>
#include <linux/skbuff.h>
#include <linux/ip.h>
#include <linux/udp.h>
#include <linux/if_ether.h>

#define VNET_QUEUE_SIZE  16
#define VNET_IRQ_NUM     27   // 虚拟中断号

struct vnet_priv {
    struct net_device *dev;
    struct tasklet_struct rx_tasklet;
    spinlock_t lock;
    struct sk_buff *rx_queue[VNET_QUEUE_SIZE];
    int rx_head, rx_tail;
    int irq;
};

static int vnet_ring_push(struct vnet_priv *priv, struct sk_buff *skb)
{
    unsigned long flags;
    int next = (priv->rx_head + 1) % VNET_QUEUE_SIZE;
    spin_lock_irqsave(&priv->lock, flags);
    if (next == priv->rx_tail) {
        spin_unlock_irqrestore(&priv->lock, flags);
        return -ENOSPC;  // 队列满
    }
    priv->rx_queue[priv->rx_head] = skb;
    priv->rx_head = next;
    spin_unlock_irqrestore(&priv->lock, flags);
    return 0;
}

static struct sk_buff *vnet_ring_pop(struct vnet_priv *priv)
{
    struct sk_buff *skb;
    unsigned long flags;
    spin_lock_irqsave(&priv->lock, flags);
    if (priv->rx_head == priv->rx_tail) {
        spin_unlock_irqrestore(&priv->lock, flags);
        return NULL;
    }
    skb = priv->rx_queue[priv->rx_tail];
    priv->rx_tail = (priv->rx_tail + 1) % VNET_QUEUE_SIZE;
    spin_unlock_irqrestore(&priv->lock, flags);
    return skb;
}

// 下半部:在软中断上下文处理数据包
static void vnet_rx_tasklet(unsigned long data)
{
    struct vnet_priv *priv = (struct vnet_priv *)data;
    struct sk_buff *skb;
    while ((skb = vnet_ring_pop(priv)) != NULL) {
        skb->dev = priv->dev;
        skb->protocol = eth_type_trans(skb, priv->dev);
        netif_receive_skb(skb);
        priv->dev->stats.rx_packets++;
        priv->dev->stats.rx_bytes += skb->len;
    }
}

// 中断处理函数(上半部)
static irqreturn_t vnet_irq_handler(int irq, void *dev_id)
{
    struct net_device *dev = dev_id;
    struct vnet_priv *priv = netdev_priv(dev);
    // 模拟硬件:读取中断状态寄存器,确认是RX中断
    // 实际硬件会禁用该中断源直到处理完成,这里简化
    tasklet_schedule(&priv->rx_tasklet);
    return IRQ_HANDLED;
}

// 模拟硬件产生接收中断(外部可调用)
void vnet_rx_irq_trigger(struct net_device *dev, struct sk_buff *skb)
{
    struct vnet_priv *priv = netdev_priv(dev);
    if (vnet_ring_push(priv, skb) == 0) {
        // 触发中断:实际硬件会置位中断寄存器并通知中断控制器
        // 这里直接调用handler模拟中断上下文
        vnet_irq_handler(priv->irq, dev);
    } else {
        dev_kfree_skb_any(skb);
        dev->stats.rx_dropped++;
    }
}
EXPORT_SYMBOL(vnet_rx_irq_trigger);

static int vnet_open(struct net_device *dev)
{
    netif_start_queue(dev);
    return 0;
}

static int vnet_stop(struct net_device *dev)
{
    netif_stop_queue(dev);
    return 0;
}

static int vnet_init(struct net_device *dev)
{
    struct vnet_priv *priv = netdev_priv(dev);
    priv->dev = dev;
    priv->rx_head = priv->rx_tail = 0;
    spin_lock_init(&priv->lock);
    tasklet_init(&priv->rx_tasklet, vnet_rx_tasklet, (unsigned long)priv);
    priv->irq = VNET_IRQ_NUM;
    return 0;
}

static const struct net_device_ops vnet_ops = {
    .ndo_open = vnet_open,
    .ndo_stop = vnet_stop,
    .ndo_init = vnet_init,
};

static void vnet_setup(struct net_device *dev)
{
    ether_setup(dev);
    dev->netdev_ops = &vnet_ops;
}

static int __init vnet_init_module(void)
{
    struct net_device *dev;
    struct vnet_priv *priv;
    int ret;

    dev = alloc_netdev(sizeof(struct vnet_priv), "vnet0", NET_NAME_UNKNOWN, vnet_setup);
    if (!dev)
        return -ENOMEM;

    ret = register_netdev(dev);
    if (ret) {
        free_netdev(dev);
        return ret;
    }

    priv = netdev_priv(dev);
    ret = request_irq(priv->irq, vnet_irq_handler, IRQF_SHARED, "vnet", dev);
    if (ret) {
        unregister_netdev(dev);
        free_netdev(dev);
        return ret;
    }

    pr_info("vnet: registered with IRQ %d\n", priv->irq);
    return 0;
}

static void __exit vnet_cleanup_module(void)
{
    struct net_device *dev = dev_get_by_name(&init_net, "vnet0");
    if (dev) {
        struct vnet_priv *priv = netdev_priv(dev);
        free_irq(priv->irq, dev);
        tasklet_kill(&priv->rx_tasklet);
        unregister_netdev(dev);
        free_netdev(dev);
        dev_put(dev);
    }
}

module_init(vnet_init_module);
module_exit(vnet_cleanup_module);
MODULE_LICENSE("GPL");
MODULE_DESCRIPTION("Virtual NIC with interrupt handling");

中断处理流程

外部调用 vnet_rx_irq_trigger() 环形缓冲入队 vnet_ring_push() 中断上下文 vnet_irq_handler() tasklet_schedule() 软中断上下文 vnet_rx_tasklet() 出队 + 上送 netif_receive_skb() 进入内核协议栈 rx_packets++ / rx_bytes += len 统计更新 中断上下文 - 不能睡眠 - 不能调用 kmalloc(GFP_KERNEL) 软中断上下文 - 允许睡眠 - 可调用 netif_receive_skb()

编译与加载

# 编译
make -C /lib/modules/$(uname -r)/build M=$(pwd) modules

# 加载
sudo insmod vnet_interrupt.ko
dmesg | tail -5   # 应看到 "vnet: registered with IRQ 27"

# 查看接口
ip link show vnet0

验证方法

编写一个测试模块或使用vnet_rx_irq_trigger()函数注入数据包。由于该函数已导出,可在另一个模块中调用:

// test_inject.c (示例片段)
extern void vnet_rx_irq_trigger(struct net_device *dev, struct sk_buff *skb);

static int inject_packet(void)
{
    struct net_device *dev = dev_get_by_name(&init_net, "vnet0");
    struct sk_buff *skb;
    if (!dev) return -ENODEV;
    
    skb = alloc_skb(64, GFP_KERNEL);
    skb_put_data(skb, "Hello VNET IRQ", 14);
    skb_reset_mac_header(skb);
    
    vnet_rx_irq_trigger(dev, skb);
    dev_put(dev);
    return 0;
}

判定标准:

  1. 加载模块后dmesg无错误,ip link show vnet0能看到接口且状态为DOWN(未open)。
  2. 执行ip link set vnet0 up后,注入测试数据包,使用tcpdump -i vnet0应能捕获到注入的报文。
  3. 检查/sys/class/net/vnet0/statistics/rx_packets,数值应随注入次数递增。
  4. 卸载模块时无中断泄漏警告(free_irq成功)。

若在真实硬件环境中,中断号、寄存器操作需按硬件手册调整,本案例使用虚拟中断号27和软件触发方式,重点演示中断上下文与下半部的协作机制。