天天看点

linux kernel 接收网络数据包处理流程

数据包接收处理流程

  1. 系统启动时,在kernel/softirq.c文件中函数spawn_ksoftirq会调用smpboot_register_percpu_thread,这个函数位于kernel/smpboot.c文件中,他的作用就是为每一个cpu注册一个软中断的线程处理函数,而这个软中断处理线程函数名称为run_ksoftirqd
  2. 软中断线程入口函数run_ksoftirqd会检测softirq的pengding位是否置位,如果置位则调用__do_softirq();
  3. 位于文件kernel/softirq.c中的函数__do_softirq会根据pending位去找到注册软中断softirq的action函数,然后执行注册函数。
  4. 系统启动时,位于/net/core/dev.c文件中的net_dev_init函数将注册softirq,NET_RX_SOFTIRQ对应的软中断入口函数为net_rx_action
  5. 位于/net/core/dev.c文件中的net_rx_action函数会调用napi_poll函数,最终对调用到驱动的poll函数,这就是驱动接收数据包的入口函数。比如nxp的以太网驱动里边的lpc_eth_poll就是poll的入口

有了上面的描述,我们知道的软中断的入口,但是从数据包到达网卡之后怎么触发中断的呢?

  1. 网卡接收到数据包,网卡将DMA的数据写入RAM中
  2. 网卡向CPU触发硬中断
  3. CPU调用硬中断处理函数,这里我们以nxp的以太网卡驱动进行说明,内核版本4.1.35,文件名称:/driver/net/ethernet/nxp/lpc_eth.c,驱动程序入口函数lpc_eth_drv_probe会注册硬中断处理函数,注册代码如下: 这说明nxp以太网卡驱动的硬中断入口函数为__lpc_eth_interrupt.
  4. 硬中断处理函数清除硬中断的IRQ标志位,调用__net_schedule。实际的代码如下
    tmp = 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
    /* 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);
    }
               
    从上面的函数可以看出__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中,注册的代码如下:
  5. 驱动的poll函数处理完成之后调用函数netif_receive_skb(skb)
  6. 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