Linux内核网络协议栈4(基于Linux6.6)---数据包的网卡驱动收发包过程
一、网卡
网卡工作在物理层和数据链路层,主要由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、网卡驱动创建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。
五、发包过程
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。
七、中断上下部
7.1、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;
7.2、netif_rx()
在netif_rx()中把skb加入CPU的softnet_data
static int netif_rx_internal(struct sk_buff *skb)
{
int ret;
net_timestamp_check(READ_ONCE(netdev_tstamp_prequeue), skb);
trace_netif_rx(skb);
#ifdef CONFIG_RPS
if (static_branch_unlikely(&rps_needed)) {
struct rps_dev_flow voidflow, *rflow = &voidflow;
int cpu;
rcu_read_lock();
cpu = get_rps_cpu(skb->dev, skb, &rflow);
if (cpu < 0)
cpu = smp_processor_id();
ret = enqueue_to_backlog(skb, cpu, &rflow->last_qtail);
rcu_read_unlock();
} else
#endif
{
unsigned int qtail;
ret = enqueue_to_backlog(skb, smp_processor_id(), &qtail);
}
return ret;
}
/**
* __netif_rx - Slightly optimized version of netif_rx
* @skb: buffer to post
*
* This behaves as netif_rx except that it does not disable bottom halves.
* As a result this function may only be invoked from the interrupt context
* (either hard or soft interrupt).
*/
int __netif_rx(struct sk_buff *skb)
{
int ret;
lockdep_assert_once(hardirq_count() | softirq_count());
trace_netif_rx_entry(skb);
ret = netif_rx_internal(skb);
trace_netif_rx_exit(ret);
return ret;
}
EXPORT_SYMBOL(__netif_rx);