对于各种DMA、NAPI、RFS/RPS、SO_REUSEPORT,如果不了解底层,那么对于做应用层优化也只是空中楼阁。
本文以e1000驱动为例,试图理清网络驱动层的数据流、逻辑流。
关于网卡接收到以太流的DMA过程:
网卡DMA引擎在主存中为DMA开辟一段连续空间存放Buffer Descriptor(ptr/length/status),一致性映射(dma_alloc_cohrent)。
再开辟一段可以不连续的空间存放以太包,流式映射。
MAC接收以太流,放在网卡内部Rx FIFO中。
完整包到了以后遍历Buffer Descriptor,找到可用BD,更新status,拷贝完以太包,再产生中断。
CPU core处理中断,解除DMA映射(ptr=NULL),更新status,重新流式映射(dma_map_single,kmalloc)
驱动:
以e1000网卡为例(E1000_main.c):
驱动函数注册到struct pci_driver e1000_driver中,包括
e1000_probe() //这个函数使能相应的硬件,初始化并且注册新设备。
e1000_remove() //当从系统中删除一个设备或者热插扒设备被拔下时,PCI子系统会调用这个函数。
e1000_suspend() //这个函数在设备在休眠状态和激活状态之间切换时调用。
e1000_resume() //通过这个函数,设备驱动可以生成电源管理信号来激活或者关闭系统。
e1000_netpoll()
-|e1000_intr()
-|netif_rx_schedule(将poll_list挂到CPU)
-|__raise_softirq_irqoff(NET_RX_SOFTIRQ触发接收软中断)
e1000_probe()
-|e1000_sw_init() //初始化e1000私有数据,rx_buffer_len 2048Byte
-|netif_napi_add() //
e1000_open() //启动网卡,通过用户ifconfig up命令
-|e1000_setup_tx_resources() //分配tx资源(描述符,一致性DMA)
-|e1000_setup_rx_resources() //同上
-|e1000_up() //配置MAC地址、配置多播/混杂模式、vlan、发送/接收单元等等,都是操作寄存器
-|e1000_configure_tx() //配置寄存器
-|e1000_configure_rx()
-|e1000_alloc_rx_buffers() //为rx_ring中的每一个元素分配一个sk_buff,并为每个skb->data建立流式映射。
-|request_irq(irq, &e1000_intr) //注册中断号irq和中断服务程序
do_IRQ()
-|e1000_intr() //中断上半部
-|触发软中断 //NAPI
----------------------
-|依次调用软中断所有handler
-|net_rx_action中调用e1000的e1000_clean()-->e1000_clean_tx_irq()/e1000_clean_rx_irq()
e1000_clean() //e1000中断handler
-|e1000_clean_tx_irq()
-|e1000_unmap_and_free_tx_resource() //此时数据已经发送给网卡tx FIFO了,所以挨个回收DMA ring中的skb->data资源。但是,什么时候再映射?
-|e1000_clean_rx_irq()
-|pci_unmap_single() //对于已经copy以太包的skb->data,解除其DMA映射
-|skb_put() //删除以太帧尾4字节
-|netif_receive_skb() //把以太包交付给上层协议栈
-|e1000_alloc_rx_buffers() //重新映射流式DMA
关于上面的问题,什么时候再映射?
e1000的映射函数是e1000_tx_map(),被e1000_xmit_frame()调用,e1000_xmit_frame()是e1000的hard_start_xmit handler,通过发送报文主函数dev_queue_xmit()调用,而dev_queue_xmit()可能通过QoS层调用hard_start_xmit()或者不使用QoS直接调用hard_start_xmit()。