linux内核网络收包过程—硬中断与软中断

Posted 为了维护世界和平_

tags:

篇首语:本文由小常识网(cha138.com)小编为大家整理,主要介绍了linux内核网络收包过程—硬中断与软中断相关的知识,希望对你有一定的参考价值。

目录

硬中断处理

软中断处理


        数据通过网络发送过来

硬中断处理

  1. 数据帧首先到达网卡的接收队列,分配RingBuffer
  2. DMA把数据搬运到网卡关联的内存
  3. 网卡向CPU发起硬中断,通知CPU有数据
  4. 调用驱动注册的硬中断处理函数
  5. 启动NAPI,触发软中断

上一分析说到网卡硬中断注册的函数igb_msix_ring

static irqreturn_t igb_msix_ring(int irq, void *data)

	struct igb_q_vector *q_vector = data;

	/* Write the ITR value calculated from the previous interrupt. */
	igb_write_itr(q_vector);

	napi_schedule(&q_vector->napi);

	return IRQ_HANDLED;

igb_write_itr仅记录硬件中断频率

static inline void ____napi_schedule(struct softnet_data *sd,
				     struct napi_struct *napi)

	list_add_tail(&napi->poll_list, &sd->poll_list);
    //触发一个软中断NET_RX_SOFTIRQ
	__raise_softirq_irqoff(NET_RX_SOFTIRQ);
  • list_add_tail修改了napi的poll_list(双向链表,数据帧等着被处理),
  • 触发一个软中断NET_RX_SOFTIRQ
  • 网络包硬中断的工作到此结束。

软中断处理

判断softirq_pending标志

static int ksoftirqd_should_run(unsigned int cpu)

	return local_softirq_pending();

执行run_ksoftirqd->__do_softirq

asmlinkage __visible void __softirq_entry __do_softirq(void)


	while ((softirq_bit = ffs(pending))) 

		trace_softirq_entry(vec_nr);
		h->action(h);
		trace_softirq_exit(vec_nr);
		
		wakeup_softirqd();
	
...

调用action中断函数

static __latent_entropy void net_rx_action(struct softirq_action *h)

	struct softnet_data *sd = this_cpu_ptr(&softnet_data);
	unsigned long time_limit = jiffies +
		usecs_to_jiffies(netdev_budget_usecs);
	int budget = netdev_budget;

	for (;;) 
		struct napi_struct *n;

		n = list_first_entry(&list, struct napi_struct, poll_list);
        //变量sd,调用poll函数
		budget -= napi_poll(n, &repoll);

        //budget 与 time_limit控制退出
		if (unlikely(budget <= 0 ||
			     time_after_eq(jiffies, time_limit))) 
			sd->time_squeeze++;
			break;
		
	
核⼼逻辑是获取到当前 CPU变量 softnet_data ,对其 poll_list 进⾏遍历 , 然后执⾏到⽹卡驱动注册到的 poll 函数。
static int igb_poll(struct napi_struct *napi, int budget)


	if (q_vector->tx.ring)
		clean_complete = igb_clean_tx_irq(q_vector, budget);

	if (q_vector->rx.ring) 
		int cleaned = igb_clean_rx_irq(q_vector, budget);
	




static int igb_clean_rx_irq(struct igb_q_vector *q_vector, const int budget)

	while (likely(total_packets < budget)) 
		union e1000_adv_rx_desc *rx_desc;
		struct igb_rx_buffer *rx_buffer;
		unsigned int size;

		rx_buffer = igb_get_rx_buffer(rx_ring, size);


		igb_put_rx_buffer(rx_ring, rx_buffer);
		cleaned_count++;

		/* fetch next buffer in frame if non-eop */
		if (igb_is_non_eop(rx_ring, rx_desc))
			continue;

		/* verify the packet layout is correct */
		if (igb_cleanup_headers(rx_ring, rx_desc, skb)) 
			skb = NULL;
			continue;
		

		/* probably a little skewed due to removing CRC */
		total_bytes += skb->len;

		/* populate checksum, timestamp, VLAN, and protocol */
		igb_process_skb_fields(rx_ring, rx_desc, skb);

		napi_gro_receive(&q_vector->napi, skb);

		/* update budget accounting */
		total_packets++;
	

	return total_packets;

  1. 从ringbuf中取出数据skb;
  2. 收取完数据以后,对其进⾏⼀些校验
  3. 设置 sbk 变量的 timestamp, VLAN id, protocol 
gro_result_t napi_gro_receive(struct napi_struct *napi, struct sk_buff *skb)

	skb_gro_reset_offset(skb);

	ret = napi_skb_finish(napi, skb, dev_gro_receive(napi, skb));
	trace_napi_gro_receive_exit(ret);

napi_gro_receive函数代表的是⽹卡 GRO 特性,可以简单理解成把相关的⼩包合并成⼀个⼤包。

/* Pass the currently batched GRO_NORMAL SKBs up to the stack. */
static void gro_normal_list(struct napi_struct *napi)

	if (!napi->rx_count)
		return;
	netif_receive_skb_list_internal(&napi->rx_list);
	INIT_LIST_HEAD(&napi->rx_list);
	napi->rx_count = 0;


/* Queue one GRO_NORMAL SKB up for list processing. If batch size exceeded,
 * pass the whole batch up to the stack.
 */
static void gro_normal_one(struct napi_struct *napi, struct sk_buff *skb)

	list_add_tail(&skb->list, &napi->rx_list);
	if (++napi->rx_count >= gro_normal_batch)
		gro_normal_list(napi);


static gro_result_t napi_skb_finish(struct napi_struct *napi,
				    struct sk_buff *skb,
				    gro_result_t ret)

	switch (ret) 
	case GRO_NORMAL:
		gro_normal_one(napi, skb);
		break;
    ...

最终调用 gro_normal_list将数据发送到网络协议栈。

 参考

零声教育


以上是关于linux内核网络收包过程—硬中断与软中断的主要内容,如果未能解决你的问题,请参考以下文章

趣谈协议基础篇:图解Linux网络包接收过程

linux内核网络收包过程—UDP协议处理

linux网卡端口断开事件

DPDK 网卡收包流程

linux 内核网络发送技术栈

Linux Kernel TCP/IP Stack — 协议栈收包处理流程