网络数据包接收流程

1. 网络数据包接收流程简述

        典型的以太网卡网络包接收流程如下:

        1.网络包通过物理介质传到接收端的phy芯片;

        2.phy芯片通过RGMII协议传到MAC芯片rx queue fifo中;

        3.MAC芯片通过专用DMA将网络包搬运到网卡驱动程序预先分配好的rx ringbuffer中,当一个网络包搬运完后,给CPU触发中断;

        4.CPU响应网卡中断(同时关网卡dma中断),执行网卡驱动程序的中断处理函数,触发NET_RX软中断;

        5.NET_RX软中断中通过napi_poll接口轮询调用网卡的接收函数将数据从rx ringbuffer中搬运到网络协议栈中处理,取空rx ringbuffer后使能网卡dma中断;

        6.网络协议栈层层处理后(网络接口层--->网络层--->传输层),将数据放到socket接收缓冲区;

        7.用户态通过read/recv系列接口从socket接收缓冲区中取走数据

2. 触发网卡硬中断前      

1.网卡interface up时,会为每个rx queue在system memory中申请dma ring buffer。
2.初始化网卡寄存器,包括dma/mtl/mac/mmc,启动dma传输;
3.申请网卡中断;
4.启动queue;

3. 响应网卡硬中断

        在网卡中断处理函数中,检查网卡的中断状态寄存器,检查到有RX interrupt时,会先清该中断,关闭网卡dma中断,在raise NET_RX的软中断后退出,实际的收包工作在软中断中处理。

4. 网络软中断定义

        软中断通过open_softirq函数(定义在kernel/softirq.c文件中)来注册的。open_softirq注册一个软中断处理函数,即在软中断向量表softirq_vec数组中添加新的软中断处理action函数。

        我们可以从start_kernel函数开始,该函数定义在init/main.c中。会调用softirq_init(),该函数会调用open_softirq函数来注册相关的软中断,但是并没有注册网络相关的软中断:

void __init softirq_init(void)
{
	int cpu;
 
	for_each_possible_cpu(cpu) {
		per_cpu(tasklet_vec, cpu).tail =
			&per_cpu(tasklet_vec, cpu).head;
		per_cpu(tasklet_hi_vec, cpu).tail =
			&per_cpu(tasklet_hi_vec, cpu).head;
	}
	open_softirq(TASKLET_SOFTIRQ, tasklet_action);
	open_softirq(HI_SOFTIRQ, tasklet_hi_action);
 }

        那么网络相关的软中断在哪里呢?其也是在startup_kernel函数中的中,调用链路如下:

startup_kernel->rest_init->kernel_init->kernel_init_freeable->do_basic_setup();

        而do_basic_setup函数会进行驱动设置。会通过调用net_dev_init函数。net_dev_init函数(定义在net/core/dev.c),最注册软中断,如下:

static int __init net_dev_init(void)
{
	int i, rc = -ENOMEM;
 
	BUG_ON(!dev_boot_phase);
 
	if (dev_proc_init())
		goto out;
 
	if (netdev_kobject_init())
		goto out;
 
	INIT_LIST_HEAD(&ptype_all);
	for (i = 0; i < PTYPE_HASH_SIZE; i++)
		INIT_LIST_HEAD(&ptype_base[i]);
 
	INIT_LIST_HEAD(&offload_base);
 
	if (register_pernet_subsys(&netdev_net_ops))
		goto out;
 
	for_each_possible_cpu(i) {
		struct work_struct *flush = per_cpu_ptr(&flush_works, i);
		struct softnet_data *sd = &per_cpu(softnet_data, i);
 
		INIT_WORK(flush, flush_backlog);
 
		skb_queue_head_init(&sd->input_pkt_queue);
		skb_queue_head_init(&sd->process_queue);
		INIT_LIST_HEAD(&sd->poll_list);
		sd->output_queue_tailp = &sd->output_queue;
		
#ifdef CONFIG_RPS
		sd->csd.func = rps_trigger_softirq;
		sd->csd.info = sd;
		sd->cpu = i;
#endif
 
		sd->backlog.poll = process_backlog;
		sd->backlog.weight = weight_p;
	}
 
	dev_boot_phase = 0;
 
	if (register_pernet_device(&loopback_net_ops))
		goto out;
 
	if (register_pernet_device(&default_device_ops))
		goto out;
 
	open_softirq(NET_TX_SOFTIRQ, net_tx_action);//注册网络发送的软中断,关联net_tx_action函数
	open_softirq(NET_RX_SOFTIRQ, net_rx_action);//注册网络接收的软中断,关联net_rx_action函数
 
	rc = cpuhp_setup_state_nocalls(CPUHP_NET_DEV_DEAD, "net/dev:dead",
				       NULL, dev_cpu_dead);
	WARN_ON(rc < 0);
	rc = 0;
out:
	return rc;
}
 
//软中断注册
void open_softirq(int nr, void (*action)(struct softirq_action *))
{
	softirq_vec[nr].action = action;
}
 
//软中断向量表
static struct softirq_action softirq_vec[NR_SOFTIRQS] __cacheline_aligned_in_smp;

5. NET_RX软中断执行流程

        在调度到ksoftirqd/x线程处理NET_RX的软中断时,以stmmac网卡驱动为例,有如下的调用关系:

net_rx_action
   napi_poll
     stmmac_napi_poll_rx /*网卡驱动注册的rx napi回调*/
       stmmac_rx /*实际接收数据的函数*/
      skb_copy_to_linear_data   /*将数据包从rx ringbuffer中拷贝到skb结构体中*/
      napi_gro_receive /*网络接口层处理数据包*/
       dev_gro_receive 
       napi_skb_finish
         netif_receive_skb_internal
           deliver_skb   /*将数据送到网络层*/
           ip_rcv /*网络层IP协议核心函数*/
            ip_rcv_core
            ip_rcv_finish  /* 处理netfiler和iptables规则*/
              ip_local_deliver_finish /*将数据送到传输层*/
                  udp_rcv /*根据协议调用传输层回调,以下以UDP协议为例*/
                    udp_queue_rcv_skb /*校验udp数据*/
                       __udp_queue_rcv_skb /*将网络包送到socket接收队列中*/
                   sk_data_ready /*唤醒所有等待在该socket上的进程*/

猜你喜欢

转载自blog.csdn.net/qq_41076734/article/details/129088436