数据包接收处理流程
- 系统启动时,在kernel/softirq.c文件中函数spawn_ksoftirq会调用smpboot_register_percpu_thread,这个函数位于kernel/smpboot.c文件中,他的作用就是为每一个cpu注册一个软中断的线程处理函数,而这个软中断处理线程函数名称为run_ksoftirqd
- 软中断线程入口函数run_ksoftirqd会检测softirq的pengding位是否置位,如果置位则调用__do_softirq();
- 位于文件kernel/softirq.c中的函数__do_softirq会根据pending位去找到注册软中断softirq的action函数,然后执行注册函数。
- 系统启动时,位于/net/core/dev.c文件中的net_dev_init函数将注册softirq,NET_RX_SOFTIRQ对应的软中断入口函数为net_rx_action
- 位于/net/core/dev.c文件中的net_rx_action函数会调用napi_poll函数,最终对调用到驱动的poll函数,这就是驱动接收数据包的入口函数。比如nxp的以太网驱动里边的lpc_eth_poll就是poll的入口
有了上面的描述,我们知道的软中断的入口,但是从数据包到达网卡之后怎么触发中断的呢?
- 网卡接收到数据包,网卡将DMA的数据写入RAM中
- 网卡向CPU触发硬中断
- CPU调用硬中断处理函数,这里我们以nxp的以太网卡驱动进行说明,内核版本4.1.35,文件名称:/driver/net/ethernet/nxp/lpc_eth.c,驱动程序入口函数lpc_eth_drv_probe会注册硬中断处理函数,注册代码如下: 这说明nxp以太网卡驱动的硬中断入口函数为__lpc_eth_interrupt.
- 硬中断处理函数清除硬中断的IRQ标志位,调用__net_schedule。实际的代码如下
再看下__napi_scheduletmp = readl(LPC_ENET_INTSTATUS(pldat->net_base)); /* Clear interrupts */ writel(tmp, LPC_ENET_INTCLEAR(pldat->net_base)); lpc_eth_disable_int(pldat->net_base); if (likely(napi_schedule_prep(&pldat->napi))) __napi_schedule(&pldat->napi);
从上面的函数可以看出__napi_schedule函数获取当前cpu的softnet_data,然后调用__raise_softirq_irqoff(NET_RX_SOFTIRQ)来触发软件中断。而函数__raise_softirq_irqoff(NET_RX_SOFTIRQ)实际上就是将NET_RX_SOFTIRQ对应的pending位置位使得软件中线程处理函数run_ksoftirqd能够捕获到,从而调度到驱动的poll函数,nxp驱动的poll函数为lpc_eth_poll,注册的位置也在驱动的入口函数lpc_eth_drv_probe中,注册的代码如下:/* Called with irq disabled */ static inline void ____napi_schedule(struct softnet_data *sd, struct napi_struct *napi) { list_add_tail(&napi->poll_list, &sd->poll_list); __raise_softirq_irqoff(NET_RX_SOFTIRQ); } void __napi_schedule(struct napi_struct *n) { unsigned long flags; local_irq_save(flags); ____napi_schedule(this_cpu_ptr(&softnet_data), n); local_irq_restore(flags); }
- 驱动的poll函数处理完成之后调用函数netif_receive_skb(skb)
- netif_receive_skb(skb)函数会判断RPS是否使能,如果处于使能状态,则调用函数enqueue_to_backlog将skb送入cpu队列,函数enqueue_to_backlog函数内部又会调用____napi_schedule函数重新触发软中断,不过这次软中断的action函数确是process_backlog函数,代码如下:
/* Schedule NAPI for backlog device * We can use non atomic operation since we own the queue lock */ if (!__test_and_set_bit(NAPI_STATE_SCHED, &sd->backlog.state)) { if (!rps_ipi_queued(sd)) ____napi_schedule(sd, &sd->backlog); }
这里sd->backlog就是net_dev_init函数中注入的process_backlog函数。
相反,如果没有使能RPS,则直接调用__netif_receive_skb(skb);最终调用__netif_receive_skb_core函数,进入协议栈处理。
-
参考文章
Monitoring and Tuning the Linux Networking Stack: Receiving Data