Windows XP Windows 7 Windows 2003 Windows Vista Windows 綜合教學 Linux 系統教學
Windows 10 Windows 8 Windows 2008 Windows NT Windows Server 電腦軟體教學
您现在的位置: Windows教程網 >> Windows教程 >  >> Linux 系統教學 >> Linux 教學

Linux 驅動層數據接收全流程:中斷、NAPI 輪詢與 DMA 同步機制

編輯:Linux 教學

網卡數據接收路徑涉及硬件中斷、DMA 內存同步、NAPI 輪詢調度等核心機制。本文以 e100 驅動為例,完整追蹤從硬件接收中斷到 netif_receive_skb 入核的全流程。

DMA 接收基礎原理

現代網卡採用 DMA 直接將數據寫入主存:

  1. 驅動預先分配接收緩衝區(SKB),將物理地址編程給網卡 RX 環
  2. 網卡接收數據幀,DMA 引擎寫入對應 SKB 的 data 區域
  3. 接收完成後,網卡修改 RX 環指針,觸發中斷通知 CPU
  4. CPU 處理中斷,同步 DMA 緩存,提交 SKB 給協議棧

每個 SKB 存儲一個完整數據幀,DMA 通過鏈表串聯多個 SKB 形成接收環。

中斷處理入口:e100_intr

static irqreturn_t e100_intr(int irq, void *dev_id, struct pt_regs *regs)
{
    struct net_device *netdev = dev_id;
    struct nic *nic = netdev_priv(netdev);
    u8 stat_ack = readb(&nic->csr->scb.stat_ack);

    if (stat_ack == stat_ack_not_ours || stat_ack == stat_ack_not_present)
        return IRQ_NONE;

    // 1. 確認中斷:向硬件寫入 ACK
    writeb(stat_ack, &nic->csr->scb.stat_ack);

    // 2. 處理 RNR(接收無資源)狀態
    if (stat_ack & stat_ack_rnr) nic->ru_running = 0;

    // 3. 禁用中斷,啟動 NAPI 輪詢
    e100_disable_irq(nic);
    netif_rx_schedule(netdev);  // 將設備加入 CPU poll_list

    return IRQ_HANDLED;
}

關鍵點:中斷上半部極精簡

  • 僅確認中斷、禁用設備中斷、調度 NAPI
  • 不在中斷上下文處理具體數據,避免長時間佔用 CPU

NAPI 輪詢處理:e100_poll

static int e100_poll(struct net_device *netdev, int *budget)
{
    struct nic *nic = netdev_priv(netdev);
    unsigned int work_to_do = min(netdev->quota, *budget);
    unsigned int work_done = 0;
    int tx_cleaned;

    // 1. 接收清理:處理 DMA 數據,提交 SKB
    e100_rx_clean(nic, &work_done, work_to_do);

    // 2. 發送清理:回收已發送 SKB
    tx_cleaned = e100_tx_clean(nic);

    // 3. 判斷是否退出輪詢模式
    if ((!tx_cleaned && work_done == 0) || !netif_running(netdev)) {
        netif_rx_complete(netdev);  // 完成輪詢,重新啟用中斷
        e100_enable_irq(nic);
        return 0;
    }

    *budget -= work_done;
    netdev->quota -= work_done;
    return 1;  // 還有工作,保持輪詢
}

接收清理核心:e100_rx_clean → e100_rx_indicate

static inline void e100_rx_clean(struct nic *nic, unsigned int *work_done,
                                 unsigned int work_to_do)
{
    struct rx *rx;

    // 遍歷已接收完成的 RX 描述符
    for (rx = nic->rx_to_clean; rx->skb; rx = nic->rx_to_clean = rx->next) {
        if (e100_rx_indicate(nic, rx, work_done, work_to_do))
            break;  // 達到 budget 限制
    }

    // 重新填充空閒 RX 描述符
    for (rx = nic->rx_to_use; !rx->skb; rx = nic->rx_to_use = rx->next) {
        if (unlikely(e100_rx_alloc_skb(nic, rx))) break;
    }

    e100_start_receiver(nic);  // 重啟硬件接收單元
}

單個幀處理:e100_rx_indicate

static inline int e100_rx_indicate(struct nic *nic, struct rx *rx,
                                   unsigned int *work_done, unsigned int work_to_do)
{
    struct sk_buff *skb = rx->skb;
    struct rfd *rfd = (struct rfd *)skb->data;  // RFD 在 SKB 開頭
    u16 rfd_status, actual_size;

    if (unlikely(*work_done >= work_to_do)) return -EAGAIN;

    // 1. DMA 同步:讓 CPU 看到硬件寫入的數據
    pci_dma_sync_single_for_cpu(nic->pdev, rx->dma_addr,
        sizeof(struct rfd), PCI_DMA_FROMDEVICE);

    // 2. 讀取接收狀態
    rfd_status = le16_to_cpu(rfd->status);
    if (!(rfd_status & RFD_STAT_C)) return 0;  // 未接收完成

    // 3. 獲取實際數據長度,調整 SKB
    actual_size = le16_to_cpu(rfd->actual_size);
    skb_put(skb, actual_size);  // 移動 tail 指針

    // 4. 協議類型設置、校驗和提交
    skb->protocol = eth_type_trans(skb, nic->netdev);
    netif_receive_skb(skb);  // 進入協議棧

    (*work_done)++;
    return 0;
}

關鍵技術點

  1. DMA 同步:pci_dma_sync_single_for_cpu 確保 CPU 讀取到硬件寫入的最新數據,涉及緩存一致性操作。
  2. RFD 結構:接收幀描述符位於 SKB 開頭,包含狀態、長度、校驗和等信息,實際數據緊隨其後。
  3. SKB 復用:接收完成後直接提交原 SKB,避免數據拷貝;清理階段重新分配 SKB 填充空閒描述符。
  4. Budget 限制:每次輪詢處理上限由 netdev->quota 和系統 netdev_budget 共同約束,防止佔用過多 CPU。

總結

e100 接收路徑完美展示了 NAPI 機制的優勢:中斷上半部極輕量,輪詢下半部批量處理,DMA 零拷貝提交 SKB。理解 e100_intr → netif_rx_schedule → e100_poll → e100_rx_clean → netif_receive_skb 這一調用鏈,是掌握高性能網路驅動設計的基石。

Copyright © Windows教程網 All Rights Reserved