数据结构论坛

注册

 

发新话题 回复该主题

LinuxTCP网络数据接收流程NAPI [复制链接]

1#

走读Linux(5.0.1)源码,理解TCP网络数据接收和读取工作流程(NAPI)。

要搞清楚数据的接收和读取流程,需要梳理这几个角色之间的关系:网卡(本文:e),主存,CPU,网卡驱动,内核,应用程序。

1.简述

2.总流程

3.要点

3.4.1.网卡与驱动交互

3.4.2.ringbuffer

3.1.网卡驱动

3.2.NAPI

3.3.中断

3.4.DMA

1.简述

简述数据接收处理流程。

网卡(NIC)接收数据。网卡通过DMA方式将接收到的数据写入主存。网卡通过硬中断通知CPU处理主存上的数据。网卡驱动(NICdriver)启用软中断,消费主存上的数据。内核(TCP/IP)协议层处理数据,将数据缓存到对应的socket上。应用程序读取对应socket上已接收的数据。

图片来源:《图解TCP_IP》

2.总流程

网卡驱动注册到内核,方便内核与网卡进行交互。内核启动网卡,为网卡工作分配资源(ringbuffer)和注册硬中断处理e_intr。网卡(NIC)接收数据。网卡通过DMA方式将接收到的数据写入主存(步骤2内核通过网卡驱动将DMA内存地址信息写入网卡寄存器,使得网卡获得DMA内存信息)。网卡触发硬中断,通知CPU已接收数据。CPU收到网卡的硬中断,调用对应的处理函数e_intr。网卡驱动函数先禁止网卡中断,避免频繁硬中断,降低内核的工作效率。网卡驱动将napi_struct.poll_list挂在softnet_data.poll_list上,方便后面软中断调用napi_struct.poll获取网卡数据。然后启用NET_RX_SOFTIRQ-net_rx_action内核软中断。内核软中断线程消费网卡DMA方式写入主存的数据。内核软中断遍历softnet_data.poll_list,调用对应的napi_struct.poll-e_clean读取网卡DMA方式写入主存的数据。e_clean遍历ringbuffer通过dma_sync_single_for_cpu接口读取DMA方式写入主存的数据,并将数据拷贝到e_copybreak创建的skb包。网卡驱动读取到skb包后,需要将该包传到网络层处理。在这过程中,需要通过GRO(Genericreceiveoffload)接口:napi_gro_receive进行处理,将小包合并成大包,然后通过__netif_receive_skb将skb包交给TCP/IP协议逐层处理,最后将skb包追加到socket.sock.sk_receive_queue队列,等待应用处理;如果read/epoll_wait阻塞等待读取数据,那么唤醒进程/线程。skb包需要传到网络层,如果内核开启了RPS(ReceivePackageSteering)功能,为了利用多核资源,(enqueue_to_backlog)需要将数据包负载均衡到各个CPU,那么这个skb包将会通过哈希算法,挂在某个cpu的接收队列上(softnet_data.input_pkt_queue),然后等待软中断调用softnet_data的napi接口process_backlog(softnet_data.backlog.poll)将接收队列上的数据包通过__netif_receive_skb交给网络层处理。网卡驱动读取了网卡写入的数据,并将数据包交给协议栈处理后,需要通知网卡已读(ringbuffer)数据的位置,将位置信息写入网卡RDT寄存器(writel(i,hw-hw_addr+rx_ring-rdt)),方便网卡继续往ringbuffer填充数据。网卡驱动重新设置允许网卡触发硬中断(e_irq_enable),重新执行步骤3。用户程序(或被唤醒)调用read接口读取socket.sock.sk_receive_queue上的数据并拷贝到用户空间。

3.要点

网卡PCI驱动,NAPI中断缓解技术,软硬中断,DMA内存直接访问技术。

源码结构关系。

要点关系。

3.1.网卡驱动

网卡是硬件,内核通过网卡驱动与网卡交互。

网卡e的intel驱动(e_driver)在linux目录:drivers/net/ethernet/intel/e

驱动注册(e_probe)到内核,启动网卡(e_open),为网卡分配系统资源,方便内核与网卡进行交互。

PCI是PeripheralComponentInterconnect(外设部件互连标准)的缩写,它是目前个人电脑中使用最为广泛的接口,几乎所有的主板产品上都带有这种插槽。

3.2.NAPI

NAPI(NewAPI)中断缓解技术,它是Linux上采用的一种提高网络处理效率的技术。一般情况下,网卡接收到数据,通过硬中断通知CPU进行处理,但是当网卡有大量数据涌入时,频繁中断使得网卡和CPU工作效率低下,所以系统采用了硬中断+软中断轮询(poll)技术,提升数据接收处理效率(详细流程请参考上面的总流程)。

举个:餐厅人少时,客户点菜,服务员可以一对一提供服务,客户点一个菜,服务员记录一下;但是人多了,服务员就忙不过来了,这时服务员可以为每张桌子提供一张菜单,客户慢慢看,选好菜了,就通知服务员处理,这样效率就高很多了。

3.3.中断

中断分上下半部。

上半部硬中断主要保存数据,网卡通过硬中断通知CPU有数据到来。下半部内核通过软中断处理接收的数据。

注册中断。

硬中断处理。

do_IRQ

--e_intr

--ew32IMC,~0

--__napi_schedule

--list_add_tailnapi-poll_list,sd-poll_list

--__raise_softirq_irqoffNET_RX_SOFTIRQ

软中断

789101__do_softirq

--net_rx_action

--napi_poll

--e_clean

--e_clean_rx_irq

--e_receive_skb

--napi_gro_receive

--__netif_receive_skb

--ip_rcv

--tcp_v4_rcv

--...

--process_backlog

--...

--__netif_receive_skb

--...

--e_irq_enable

3.4.DMA

DMA(DirectMemoryAccess)可以使得外部设备可以不用CPU干预,直接把数据传输到内存,这样可以解放CPU,提高系统性能。它是NAPI中断缓解技术,实现的重要一环。

3.4.1.网卡与驱动交互

系统通过ringbuffer环形缓冲区管理内存描述符,通过一致性DMA映射(dma_alloc_coherent)描述符(e_rx_desc)数组,方便CPU和网卡同步访问。环形缓冲区内存描述符指向的内存块(e_rx_buffer)通过DMA流式映射(dma_map_single),提供网卡写入。网卡接收到数据,写入网卡缓存。当网卡开始收到数据包后,通过DMA方式将数据拷贝到主存,并通过硬中断通知CPU。CPU接收到硬中断,禁止网卡再触发硬中断(虽然硬中断被禁止了,但是网卡可以继续接收数据,并将数据拷贝到主存),然后唤醒CPU软中断(NET_RX_SOFTIRQ-net_rx_action)。软中断从主存中读取处理网卡DMA方式写入的数据(skb),并将数据交给TCP/IP协议逐层处理。在有限的时间内一定数量的主存上的数据被处理完后,系统将空闲的(ringbuffer)内存描述符提供给网卡,方便网卡下次写入。重新开启网卡硬中断,走上述步骤3。

3.4.2.ringbuffer

例如:e网卡环形缓冲区(e_rx_ring)。

系统分配内存缓冲区,映射为DMA内存,提供网卡直接访问。

下图(图片来源:stackoverflow)简述了NIC–DMA–RAM三者关系。

ringbuffer数据结构。

工作流程。

ringbuffer偏移原理。e_rx_ring.desc指针指向了一个e_rx_desc数组,网卡和网卡驱动都通过这个数组进行读写数据。这个数组被称为环形缓冲区:通过数组下标遍历数组,下标指向数组末位后,重新指向数组第一个位置,看起来像个环形结构,——理解它需要些抽象思维;因为网卡和网卡驱动都操作它,所以每个对象都维护了自己的一套head和tail进行标识。

初始状态,下标都指向数组一个元素e_rx_ring.desc[0]。网卡接收到数据通过DMA方式拷贝到主存(e_rx_ring.desc-e_rx_buffer),如下图,NIC.RDH顺时针偏移,NIC.RDT到NIC.RDH的e_rx_desc-e_rx_buffer内存块都填充了接收数据。网卡驱动顺时针遍历ringbuffer,根据网卡更新的e_rx_ring.desc.status状态,读取e_rx_ring.desc指向的e_rx_buffer数据块,因为读取数据有时间限制(jiffies)和数据量限制(budget),网卡驱动不一定能一次性读取完成网卡写入主存的数据,所以最后读取的数据位置要进行记录,通过e_rx_ring.next_to_clean记录下一次要读取数据的位置。既然网卡驱动已经读取了数据,那么已读取的数据已经没用了,可以(清理)重新提供给网卡继续写入,那么需要把下次要清理的位置记录起来:e_rx_ring.next_to_use。但是这时候网卡还不知道驱动消费数据到哪个位置,那么驱动清理掉数据后,将已清理最后的位置(e_rx_ring.next_to_use-1)写入网卡寄存器RDT,告诉网卡,下次可以(顺时针)写入数据,从NIC.RDH到NIC.RDT。

分享 转发
TOP
发新话题 回复该主题