Skip to content

网络设备驱动:原理与架构

网络驱动在内核中的位置

用户空间 socket()

    ▼ 协议栈(TCP/UDP/IP)

    ▼ 通用网络层
  net_device

    ▼ 网络设备驱动
  ndo_start_xmit()  ←── 发送
  netif_rx() / napi_schedule()  ──► 接收

    ▼ 硬件(以太网 MAC + PHY)

核心数据结构

net_device

c
struct net_device {
    char            name[IFNAMSIZ];   /* "eth0", "wlan0" */
    unsigned char   dev_addr[MAX_ADDR_LEN];  /* MAC 地址 */
    const struct net_device_ops *netdev_ops;
    const struct ethtool_ops    *ethtool_ops;
    unsigned int    mtu;
    unsigned int    flags;            /* IFF_UP, IFF_BROADCAST... */
    struct net_device_stats stats;    /* 统计计数器 */
    /* ... */
};

sk_buff(Socket Buffer)

网络数据包的核心载体:

c
struct sk_buff {
    struct sk_buff  *next, *prev;
    struct sock     *sk;
    struct net_device *dev;

    /* 数据指针 */
    unsigned char   *head;    /* 分配的内存起始 */
    unsigned char   *data;    /* 有效数据起始 */
    unsigned char   *tail;    /* 有效数据结束 */
    unsigned char   *end;     /* 分配的内存结束 */

    unsigned int    len;      /* 数据长度 */
    __be16          protocol; /* ETH_P_IP, ETH_P_ARP... */
    /* ... */
};

sk_buff 操作:

c
/* 在头部预留空间(添加协议头) */
skb_push(skb, sizeof(struct ethhdr));

/* 移除头部 */
skb_pull(skb, sizeof(struct ethhdr));

/* 在尾部添加数据 */
skb_put(skb, data_len);

/* 获取各层头部 */
struct ethhdr *eth = eth_hdr(skb);
struct iphdr  *ip  = ip_hdr(skb);

完整网络驱动框架

c
#include <linux/module.h>
#include <linux/netdevice.h>
#include <linux/etherdevice.h>
#include <linux/platform_device.h>
#include <linux/interrupt.h>

struct mynet_priv {
    struct net_device   *ndev;
    void __iomem        *base;
    int                  irq;
    struct napi_struct   napi;
    spinlock_t           tx_lock;
    struct sk_buff      *tx_skb;
};

/* ===== 发送路径 ===== */

static netdev_tx_t mynet_start_xmit(struct sk_buff *skb,
                                     struct net_device *ndev)
{
    struct mynet_priv *priv = netdev_priv(ndev);
    unsigned long flags;

    spin_lock_irqsave(&priv->tx_lock, flags);

    /* 检查硬件发送队列是否满 */
    if (tx_queue_full(priv)) {
        netif_stop_queue(ndev);   /* 停止上层继续发包 */
        spin_unlock_irqrestore(&priv->tx_lock, flags);
        return NETDEV_TX_BUSY;
    }

    /* 将 skb 数据写入硬件 TX FIFO 或 DMA 描述符 */
    write_to_hw_tx(priv, skb->data, skb->len);

    /* 更新统计 */
    ndev->stats.tx_packets++;
    ndev->stats.tx_bytes += skb->len;

    dev_kfree_skb(skb);   /* 发送后释放 skb */

    spin_unlock_irqrestore(&priv->tx_lock, flags);
    return NETDEV_TX_OK;
}

/* ===== 接收路径(NAPI) ===== */

/* NAPI poll 函数:在软中断上下文中批量收包 */
static int mynet_poll(struct napi_struct *napi, int budget)
{
    struct mynet_priv *priv = container_of(napi, struct mynet_priv, napi);
    struct net_device *ndev = priv->ndev;
    int received = 0;

    while (received < budget && rx_data_available(priv)) {
        struct sk_buff *skb;
        int pkt_len = get_rx_packet_len(priv);

        skb = netdev_alloc_skb_ip_align(ndev, pkt_len);
        if (!skb) {
            ndev->stats.rx_dropped++;
            break;
        }

        /* 从硬件读取数据到 skb */
        read_from_hw_rx(priv, skb_put(skb, pkt_len), pkt_len);

        skb->protocol = eth_type_trans(skb, ndev);
        ndev->stats.rx_packets++;
        ndev->stats.rx_bytes += pkt_len;

        napi_gro_receive(napi, skb);   /* 提交给协议栈(支持 GRO) */
        received++;
    }

    /* 如果本轮处理完所有包,退出 NAPI 轮询 */
    if (received < budget) {
        napi_complete_done(napi, received);
        enable_rx_interrupt(priv);   /* 重新开启接收中断 */
    }

    return received;
}

/* 中断处理:触发 NAPI 轮询 */
static irqreturn_t mynet_isr(int irq, void *dev_id)
{
    struct mynet_priv *priv = dev_id;
    u32 status = read_irq_status(priv);

    if (status & RX_INT) {
        disable_rx_interrupt(priv);   /* 关闭接收中断,避免风暴 */
        napi_schedule(&priv->napi);   /* 调度 NAPI poll */
    }

    if (status & TX_INT) {
        /* 发送完成,唤醒发送队列 */
        netif_wake_queue(priv->ndev);
    }

    clear_irq_status(priv, status);
    return IRQ_HANDLED;
}

/* ===== 设备控制 ===== */

static int mynet_open(struct net_device *ndev)
{
    struct mynet_priv *priv = netdev_priv(ndev);

    napi_enable(&priv->napi);
    enable_hardware(priv);
    netif_start_queue(ndev);
    return 0;
}

static int mynet_stop(struct net_device *ndev)
{
    struct mynet_priv *priv = netdev_priv(ndev);

    netif_stop_queue(ndev);
    disable_hardware(priv);
    napi_disable(&priv->napi);
    return 0;
}

static const struct net_device_ops mynet_ops = {
    .ndo_open        = mynet_open,
    .ndo_stop        = mynet_stop,
    .ndo_start_xmit  = mynet_start_xmit,
    .ndo_get_stats64 = mynet_get_stats64,
    .ndo_set_mac_address = eth_mac_addr,
    .ndo_validate_addr   = eth_validate_addr,
};

/* ===== probe ===== */

static int mynet_probe(struct platform_device *pdev)
{
    struct net_device *ndev;
    struct mynet_priv *priv;
    int ret;

    /* 分配 net_device(含私有数据) */
    ndev = alloc_etherdev(sizeof(*priv));
    if (!ndev)
        return -ENOMEM;

    priv = netdev_priv(ndev);
    priv->ndev = ndev;
    SET_NETDEV_DEV(ndev, &pdev->dev);

    /* 初始化 NAPI */
    netif_napi_add(ndev, &priv->napi, mynet_poll);

    ndev->netdev_ops = &mynet_ops;

    /* 读取 MAC 地址(从 EEPROM 或设备树) */
    eth_hw_addr_random(ndev);   /* 随机 MAC(开发用) */

    ret = register_netdev(ndev);
    if (ret) {
        netif_napi_del(&priv->napi);
        free_netdev(ndev);
        return ret;
    }

    platform_set_drvdata(pdev, ndev);
    netdev_info(ndev, "mynet registered\n");
    return 0;
}

NAPI 工作原理

NAPI(New API)解决了高速网络下中断风暴问题:

数据包到达

    ▼ 硬件中断
关闭 RX 中断

    ▼ napi_schedule()
软中断(NET_RX_SOFTIRQ)

    ▼ mynet_poll(budget=64)
批量处理最多 64 个包

    ├── 处理完所有包 → napi_complete() → 重新开启 RX 中断
    └── 达到 budget  → 下次软中断继续处理

ethtool 支持

c
static void mynet_get_drvinfo(struct net_device *ndev,
                               struct ethtool_drvinfo *info)
{
    strscpy(info->driver, "mynet", sizeof(info->driver));
    strscpy(info->version, "1.0", sizeof(info->version));
}

static const struct ethtool_ops mynet_ethtool_ops = {
    .get_drvinfo = mynet_get_drvinfo,
    .get_link    = ethtool_op_get_link,
};

/* 在 probe 中设置 */
ndev->ethtool_ops = &mynet_ethtool_ops;
bash
# 用户空间查询
ethtool eth0
ethtool -i eth0   # 驱动信息
ethtool -S eth0   # 统计信息

褚成志的笔记