網卡數據接收路徑涉及硬件中斷、DMA 內存同步、NAPI 輪詢調度等核心機制。本文以 e100 驅動為例,完整追蹤從硬件接收中斷到 netif_receive_skb 入核的全流程。
現代網卡採用 DMA 直接將數據寫入主存:
data 區域每個 SKB 存儲一個完整數據幀,DMA 通過鏈表串聯多個 SKB 形成接收環。
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;
}
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; // 還有工作,保持輪詢
}
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); // 重啟硬件接收單元
}
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;
}
pci_dma_sync_single_for_cpu 確保 CPU 讀取到硬件寫入的最新數據,涉及緩存一致性操作。netdev->quota 和系統 netdev_budget 共同約束,防止佔用過多 CPU。e100 接收路徑完美展示了 NAPI 機制的優勢:中斷上半部極輕量,輪詢下半部批量處理,DMA 零拷貝提交 SKB。理解 e100_intr → netif_rx_schedule → e100_poll → e100_rx_clean → netif_receive_skb 這一調用鏈,是掌握高性能網路驅動設計的基石。