網卡
網卡工作在物理層和數據鏈路層,主要由PHY/MAC芯片、Tx/Rx FIFO、DMA等組成,其中網線通過變壓器接PHY芯片、PHY芯片通過MII接MAC芯片、MAC芯片接PCI總線
PHY芯片主要負責:CSMA/CD、模數轉換、編解碼、串並轉換
MAC芯片主要負責:
- 比特流和幀的轉換:7字節的前導碼Preamble和1字節的幀首定界符SFD
- CRC校驗
- Packet Filtering:L2 Filtering、VLAN Filtering、Manageability / Host Filtering
Intel的千兆網卡以82575/82576爲代表、萬兆網卡以82598/82599爲代表
收發包過程圖
ixgbe_adapter包含ixgbe_q_vector數組(一個ixgbe_q_vector對應一箇中斷),ixgbe_q_vector包含napi_struct
硬中斷函數把napi_struct加入CPU的poll_list,軟中斷函數net_rx_action()遍歷poll_list,執行poll函數
發包過程
1、網卡驅動創建tx descriptor ring(一致性DMA內存),將tx descriptor ring的總線地址寫入網卡寄存器TDBA
2、協議棧通過dev_queue_xmit()將sk_buff下送網卡驅動
3、網卡驅動將sk_buff放入tx descriptor ring,更新TDT
4、DMA感知到TDT的改變後,找到tx descriptor ring中下一個將要使用的descriptor
5、DMA通過PCI總線將descriptor的數據緩存區複製到Tx FIFO
6、複製完後,通過MAC芯片將數據包發送出去
7、發送完後,網卡更新TDH,啓動硬中斷通知CPU釋放數據緩存區中的數據包
Tx Ring Buffer
SW將sk_buff掛載到從next_to_use開始的N個descriptor,next_to_use += N,tail = next_to_use(寫網卡寄存器TDT)
HW使用DMA讀從head開始的M個descriptor的sk_buff,發送成功後回寫DD(Descriptor Done),head += M
SW將從next_to_clean的開始的L個sk_buff移出Tx Ring Buffer並清理,next_to_clean += L
注意:每次掛載完sk_buff後,tail和next_to_use指向同一個descriptor
收包過程
1、網卡驅動創建rx descriptor ring(一致性DMA內存),將rx descriptor ring的總線地址寫入網卡寄存器RDBA
2、網卡驅動爲每個descriptor分配sk_buff和數據緩存區,流式DMA映射數據緩存區,將數據緩存區的總線地址保存到descriptor
3、網卡接收數據包,將數據包寫入Rx FIFO
4、DMA找到rx descriptor ring中下一個將要使用的descriptor
5、整個數據包寫入Rx FIFO後,DMA通過PCI總線將Rx FIFO中的數據包複製到descriptor的數據緩存區
6、複製完後,網卡啓動硬中斷通知CPU數據緩存區中已經有新的數據包了,CPU執行硬中斷函數:
- NAPI(以e1000網卡爲例):e1000_intr() -> __napi_schedule() ->__raise_softirq_irqoff(NET_RX_SOFTIRQ)
- 非NAPI(以dm9000網卡爲例):dm9000_interrupt() -> dm9000_rx() -> netif_rx() ->napi_schedule() -> __napi_schedule() ->__raise_softirq_irqoff(NET_RX_SOFTIRQ)
7、ksoftirqd執行軟中斷函數net_rx_action():
- NAPI(以e1000網卡爲例):net_rx_action() -> e1000_clean() ->e1000_clean_rx_irq() -> e1000_receive_skb() -> netif_receive_skb()
- 非NAPI(以dm9000網卡爲例):net_rx_action() -> process_backlog() ->netif_receive_skb()
8、網卡驅動通過netif_receive_skb()將sk_buff上送協議棧
Rx Ring Buffer
SW向從next_to_use開始的N個descriptor補充sk_buff,next_to_use += N,tail = next_to_use(寫網卡寄存器RDT)
HW寫Frame到從head開始的M個descriptor的sk_buff,寫完後回寫EOP(End of Packet),head += M
SW將從next_to_clean開始的L個sk_buff移出Rx Ring Buffer並上送協議棧,next_to_clean += L,向從next_to_use開始的L個descriptor補充sk_buff,next_to_use += L,tail = next_to_use
注意:每次補充完sk_buff後,tail和next_to_use指向同一個sk_buff
中斷上下部
do_IRQ()
do_IRQ()是CPU處理硬中斷的總入口
// 在e1000_request_irq()中註冊硬中斷,中斷函數爲e1000_intr()
irq_handler_t handler = e1000_intr;
err = request_irq(adapter->pdev->irq, handler, irq_flags, netdev->name,
netdev);
// 在net_dev_init()中註冊軟中斷,中斷函數爲net_rx_action()
open_softirq(NET_RX_SOFTIRQ, net_rx_action);
// 在e1000_probe()中註冊napi的poll函數爲e1000_clean()
netif_napi_add(netdev, &adapter->napi, e1000_clean, 64);
// 在net_dev_init()中註冊非napi的poll函數爲process_backlog()
queue->backlog.poll = process_backlog;
netif_rx()
在netif_rx()中把skb加入CPU的softnet_data
int netif_rx(struct sk_buff *skb)
{
struct softnet_data *queue;
unsigned long flags;
/* if netpoll wants it, pretend we never saw it */
if (netpoll_rx(skb))
return NET_RX_DROP;
if (!skb->tstamp.tv64)
net_timestamp(skb);
/*
* The code is rearranged so that the path is the most
* short when CPU is congested, but is still operating.
*/
local_irq_save(flags);
queue = &__get_cpu_var(softnet_data); // 得到CPU的softnet_data
__get_cpu_var(netdev_rx_stat).total++;
if (queue->input_pkt_queue.qlen <= netdev_max_backlog) { // 若隊列長度不大於netdev_max_backlog
if (queue->input_pkt_queue.qlen) { // 若隊列長度非0,表示queue->backlog已被加入poll_list
enqueue:
__skb_queue_tail(&queue->input_pkt_queue, skb); // 將skb加入隊列尾部
local_irq_restore(flags);
return NET_RX_SUCCESS;
}
napi_schedule(&queue->backlog); // 調度queue->backlog
goto enqueue; // 將skb加入隊列尾部
}
__get_cpu_var(netdev_rx_stat).dropped++;
local_irq_restore(flags);
kfree_skb(skb);
return NET_RX_DROP;
}
原文鏈接:https://blog.csdn.net/hz5034/article/details/79794615?utm_source=copy