網卡驅動的發送路徑是 Linux 網路子系統性能的關鍵環節。本文以 Intel e100 驅動為例,深度剖析從 hard_start_xmit 入口到硬件命令提交的完整流程,重點解析環形發送緩衝區構建、DMA 映射與命令提交機制。
驅動啟動階段 e100_up() 調用 e100_alloc_cbs 構建發送環:
static int e100_alloc_cbs(struct nic *nic)
{
struct cb *cb;
unsigned int i, count = nic->params.cbs.count;
nic->cuc_cmd = cuc_start;
nic->cb_to_use = nic->cb_to_send = nic->cb_to_clean = NULL;
nic->cbs_avail = 0;
// 1. 分配一致性 DMA 內存(CPU 可訪問虛擬地址 + 總線物理地址)
nic->cbs = pci_alloc_consistent(nic->pdev,
sizeof(struct cb) * count, &nic->cbs_dma_addr);
if (!nic->cbs) return -ENOMEM;
// 2. 構建環形鏈表,初始化 DMA 地址與鏈接指針
for (cb = nic->cbs, i = 0; i < count; cb++, i++) {
cb->next = (i + 1 < count) ? cb + 1 : nic->cbs;
cb->prev = (i == 0) ? nic->cbs + count - 1 : cb - 1;
cb->dma_addr = nic->cbs_dma_addr + i * sizeof(struct cb);
cb->link = cpu_to_le32(nic->cbs_dma_addr + ((i+1)%count) * sizeof(struct cb));
cb->skb = NULL;
}
// 3. 初始化工作指針
nic->cb_to_use = nic->cb_to_send = nic->cb_to_clean = nic->cbs;
nic->cbs_avail = count;
return 0;
}
next/prev:CPU 端環形鏈表指針dma_addr:該 CB 的總線物理地址link:硬件端鏈接指針(指向下一個 CB 的物理地址)skb:關聯的 socket buffer 指針command:命令控制字(含 S-bit:暫停位)核心調用 dev->hard_start_xmit 進入驅動發送邏輯:
static int e100_xmit_frame(struct sk_buff *skb, struct net_device *netdev)
{
struct nic *nic = netdev_priv(netdev);
int err;
// 某些晶片需 workaround
if (nic->flags & ich_10h_workaround) {
e100_exec_cmd(nic, cuc_nop, 0);
udelay(1);
}
// 核心提交邏輯
err = e100_exec_cb(nic, skb, e100_xmit_prepare);
switch (err) {
case -ENOSPC: netif_stop_queue(netdev); break; // 佇列滿,停止上層佇列
case -ENOMEM: netif_stop_queue(netdev); return 1; // 硬錯誤
}
netdev->trans_start = jiffies;
return 0;
}
此函數完成 SKB 入環、DMA 映射、硬件命令提交:
static inline int e100_exec_cb(struct nic *nic, struct sk_buff *skb,
void (*cb_prepare)(struct nic *, struct cb *, struct sk_buff *))
{
struct cb *cb;
unsigned long flags;
int err = 0;
spin_lock_irqsave(&nic->cb_lock, flags);
if (unlikely(!nic->cbs_avail)) { err = -ENOMEM; goto err_unlock; }
// 1. 獲取可用 CB,推進使用指針
cb = nic->cb_to_use;
nic->cb_to_use = cb->next;
nic->cbs_avail--;
cb->skb = skb;
if (unlikely(!nic->cbs_avail)) err = -ENOSPC;
// 2. 調用準備函數:填充 CB、構建 DMA 映射
cb_prepare(nic, cb, skb);
// 3. 硬件命令提交協議:先置當前 CB S-bit,再清前一個 CB S-bit
cb->command |= cpu_to_le16(cb_s);
wmb(); // 內存屏障,確保寫入順序
cb->prev->command &= cpu_to_le16(~cb_s);
// 4. 處理待發送鏈:將 cb_to_send 推進至 cb_to_use
while (nic->cb_to_send != nic->cb_to_use) {
if (unlikely(e100_exec_cmd(nic, nic->cuc_cmd, nic->cb_to_send->dma_addr))) {
break; // 硬件忙,稍後重試
} else {
nic->cuc_cmd = cuc_resume;
nic->cb_to_send = nic->cb_to_send->next;
}
}
err_unlock:
spin_unlock_irqrestore(&nic->cb_lock, flags);
return err;
}
cb_to_use(生產者)、cb_to_send(消費者)、cb_to_clean(清理者)分離關注點,支援無鎖並發優化。wmb() 保證命令字寫入對硬件可見的順序性。while 循環盡可能一次性提交多個 CB,減少 MMIO 寫入開銷。static inline int e100_exec_cmd(struct nic *nic, u8 cmd, dma_addr_t dma_addr)
{
unsigned long flags;
unsigned int i;
int err = 0;
// ... 向硬件 SCB 寫入命令與 CB 物理地址,輪詢完成狀態 ...
}
此函數直接操作硬件控制寄存器(SCB),提交 CU(命令單元)命令,等待硬件響應。
e100 發送路徑展示了經典驅動設計模式:一致性 DMA 內存分配、環形緩衝區管理、雙指針生產消費模型、硬件協議位操作。理解這一流程,有助於掌握高性能網卡驅動的核心架構與優化思路。