Skip to content

网络设备驱动最佳实践

1. 始终使用 NAPI

高速网络必须使用 NAPI,避免中断风暴:

c
/* ✅ 正确:NAPI 模式 */
netif_napi_add(ndev, &priv->napi, my_poll);

/* 中断中只调度,不处理数据 */
static irqreturn_t my_isr(int irq, void *dev_id)
{
    disable_rx_irq(priv);
    napi_schedule(&priv->napi);
    return IRQ_HANDLED;
}

2. 正确设置 skb 的协议字段

c
/* eth_type_trans 自动解析以太网帧类型并设置 skb->protocol */
skb->protocol = eth_type_trans(skb, ndev);

/* 对于非以太网设备,手动设置 */
skb->protocol = htons(ETH_P_IP);

3. 使用 netdev_alloc_skb_ip_align

c
/* ✅ IP 对齐分配,避免非对齐访问性能损失 */
skb = netdev_alloc_skb_ip_align(ndev, pkt_len);

/* ❌ 不要用 dev_alloc_skb(已废弃) */

4. 统计计数器使用 per-CPU

c
struct mynet_priv {
    struct pcpu_sw_netstats __percpu *stats;
};

/* probe 中分配 */
priv->stats = netdev_alloc_pcpu_stats(struct pcpu_sw_netstats);

/* 更新统计(在 softirq 上下文中) */
u64_stats_update_begin(&stats->syncp);
stats->rx_packets++;
stats->rx_bytes += len;
u64_stats_update_end(&stats->syncp);

/* 实现 ndo_get_stats64 */
static void my_get_stats64(struct net_device *ndev,
                            struct rtnl_link_stats64 *s)
{
    struct mynet_priv *priv = netdev_priv(ndev);
    int cpu;

    for_each_possible_cpu(cpu) {
        const struct pcpu_sw_netstats *stats;
        u64 rx_packets, rx_bytes;
        unsigned int start;

        stats = per_cpu_ptr(priv->stats, cpu);
        do {
            start = u64_stats_fetch_begin(&stats->syncp);
            rx_packets = stats->rx_packets;
            rx_bytes   = stats->rx_bytes;
        } while (u64_stats_fetch_retry(&stats->syncp, start));

        s->rx_packets += rx_packets;
        s->rx_bytes   += rx_bytes;
    }
}

5. 发送队列管理

c
/* 发送队列满时停止上层 */
if (tx_ring_full(priv)) {
    netif_stop_queue(ndev);
    /* 设置内存屏障,确保停止队列对其他 CPU 可见 */
    smp_mb();
    /* 再次检查,避免竞态 */
    if (!tx_ring_full(priv))
        netif_wake_queue(ndev);
}

/* 发送完成中断中唤醒队列 */
if (netif_queue_stopped(ndev) && !tx_ring_full(priv))
    netif_wake_queue(ndev);

6. 链路状态上报

c
/* 链路 UP */
netif_carrier_on(ndev);

/* 链路 DOWN */
netif_carrier_off(ndev);

/* PHY 状态变化回调 */
static void my_phy_adjust_link(struct net_device *ndev)
{
    struct phy_device *phydev = ndev->phydev;

    if (phydev->link) {
        netif_carrier_on(ndev);
        netdev_info(ndev, "Link up: %d Mbps %s-duplex\n",
                    phydev->speed,
                    phydev->duplex == DUPLEX_FULL ? "full" : "half");
    } else {
        netif_carrier_off(ndev);
    }
}

7. 零拷贝发送(scatter-gather)

c
/* 声明支持 scatter-gather */
ndev->hw_features |= NETIF_F_SG;
ndev->features    |= NETIF_F_SG;

/* 在 ndo_start_xmit 中处理分散的 skb */
static netdev_tx_t my_xmit(struct sk_buff *skb, struct net_device *ndev)
{
    int nr_frags = skb_shinfo(skb)->nr_frags;
    /* 处理线性部分 */
    dma_map_single(dev, skb->data, skb_headlen(skb), DMA_TO_DEVICE);

    /* 处理分片 */
    for (int i = 0; i < nr_frags; i++) {
        skb_frag_t *frag = &skb_shinfo(skb)->frags[i];
        dma_map_page(dev, skb_frag_page(frag),
                     skb_frag_off(frag), skb_frag_size(frag),
                     DMA_TO_DEVICE);
    }
    return NETDEV_TX_OK;
}

褚成志的笔记