linux硬件中断处理流程3----NAPI

1 NAPI简介

现在的的网卡为了提升性能,大部分已经已经使用NAPI的方式接受数据帧。linxu内核使用了struct napi_struct来管理NAPI设备的新特性和操作。系统受到数据包后,支持NAPI模式的网络设备会将网络设备的struct napi_struct数据结构的实例放到CPU的struct softnet_data数据结构的poll_list中。当网络子系统接受到软中断NET_RX_SOFTIRQ被指定时,会遍历CPU的Poll_list链表依次执行NAPI设备的poll方法,poll方法一次会从硬件缓冲区读取多个数据帧。所以struct napi_struct数据结构的设计应该考虑以下因素:

  • 在SMP系统中,设备的poll函数当前在那个CPU上执行时由网络设备的struct  napi_struct数据结构放在那个CPU的poll_list链表决定的。
  • 虽然NAPI设备一次可以读入多个数据帧,当时每个CPU输入队列的长度是有限的。这也决定poll函数一次杜甫数据帧的数量也要做限制。
  • NAPI的poll函数时在网络子系统的接收软件中断中执行的,由于软件中断不能执行太长的时间,一面占用CPU资源太久,影响其他任务的处理,这也决定了我们需要限制每个设备poll执行时间。
  • 当前以NAPI方式接收网络设备的是那个网络设备。
  • 当前NAPI设备的状态:poll函数是正在等待被调用执行,还是已执行完成等。

2 struct napi_struct

struct napi_struct {
	struct list_head	poll_list;
	unsigned long		state;
	int			weight;
	unsigned int		gro_count;
	int			(*poll)(struct napi_struct *, int);
#ifdef CONFIG_NETPOLL
	spinlock_t		poll_lock;
	int			poll_owner;
#endif
	struct net_device	*dev;
	struct sk_buff		*gro_list;
	struct sk_buff		*skb;
	struct list_head	dev_list;
	struct hlist_node	napi_hash_node;
	unsigned int		napi_id;
};

struct napi_struct数据结构中各数据域的含义如下:

  • poll_list:将网络设备的struct napi_struct数据结构实例链接到CPU的poll_list列表中的数据域。poll_list列表中的网络设备都是已经产生中断的网络设备,他们通知CPU收到了网络数据帧。而他们的poll函数等待在接收软件中断被调用执行,来读入网络数据帧。
  • state:poll_list列表中的NAPI网络设备,只由state变量中的NAPI_STATE_SCHED位管理;NAPI_STATE_SCHED标志置位的网络设,它的napi_struct数据结构实例就会加入到CPU的poll_list链表中,NAPI_STATE_SCHED为清除的设备就会从poll_list列表移除。
  • weight:定义了每个设备的poll函数在软中断执行期间poll函数可以从网络设备缓冲区读入的最大数据帧数,以太网默认是64,我们可以通过/proc/sys/core/dev_weitht文件读取或者修改这个值。
  • poll:网络设备的poll函数的指针
  • dev_list:内核的全局网络设备列表,该列表管理系统中所有网络设备的struct net_device数据结构实例。
  • *dev:内核的全局网络设备列表。该列表管理系统中所有网络设备的struct net_device数据结构实例。
  • *gro_list:数据帧被分割成片段后,所有分片数据Socket Buffer的列表。

3 NAPI硬件中断处理

NAPI的硬件中断主要工作就是将网络设备的napi实例添加到CPU的poll_list链表中等待调度,所以他的处理是要分成两部分的。我们以e1000网卡为实例,e1000的硬件中断注册调用e1000_request_irq完成,其中的硬件中断处理函数就是e1000_intr。

static int e1000_request_irq(struct e1000_adapter *adapter)
{
	struct net_device *netdev = adapter->netdev;
	irq_handler_t handler = e1000_intr; //硬件中断段处理函数
	int irq_flags = IRQF_SHARED;
	int err;

	err = request_irq(adapter->pdev->irq, handler, irq_flags, netdev->name,
	                  netdev);
	if (err) {
		e_err(probe, "Unable to allocate interrupt Error: %d\n", err);
	}

	return err;
}

硬件中断处理函数e1000_intr首先调用napi_schedule_prep判断数据域state的NAPI_STATE_SCHED是否设置, 如果已经设置说明设备已经在poll_list列表中。如果没有设置就__napi_schedule调用用__napi_schedule继续处理,并设置state的NAPI_STATE_SCHED位。

static irqreturn_t e1000_intr(int irq, void *data)
{
	struct net_device *netdev = data;
	struct e1000_adapter *adapter = netdev_priv(netdev);
	struct e1000_hw *hw = &adapter->hw;
	u32 icr = er32(ICR);

	if (unlikely((!icr)))
		return IRQ_NONE;  /* Not our interrupt */

	/* we might have caused the interrupt, but the above
	 * read cleared it, and just in case the driver is
	 * down there is nothing to do so return handled
	 */
	if (unlikely(test_bit(__E1000_DOWN, &adapter->flags)))  //判断网卡是否down
		return IRQ_HANDLED;

	if (unlikely(icr & (E1000_ICR_RXSEQ | E1000_ICR_LSC))) {
		hw->get_link_status = 1;
		/* guard against interrupt when we're going down */
		if (!test_bit(__E1000_DOWN, &adapter->flags))
			schedule_delayed_work(&adapter->watchdog_task, 1);
	}

	/* disable interrupts, without the synchronize_irq bit */
	ew32(IMC, ~0);
	E1000_WRITE_FLUSH();

	if (likely(napi_schedule_prep(&adapter->napi))) { //判断数据域state的NAPI_STATE_SCHED是否设置,   
		adapter->total_tx_bytes = 0;				   //如果已经设置说明设备已经在poll_list列表中
		adapter->total_tx_packets = 0;
		adapter->total_rx_bytes = 0;
		adapter->total_rx_packets = 0;
		__napi_schedule(&adapter->napi);//将设备的napi_struct添加到CPU的poll_list链表,并标记软中断NET_RX_SOFTIRQ
	} else {
		/* this really should not happen! if it does it is basically a
		 * bug, but not a hard error, so enable ints and continue
		 */
		if (!test_bit(__E1000_DOWN, &adapter->flags))
			e1000_irq_enable(adapter);
	}

	return IRQ_HANDLED;
}

__napi_schedule说先是关闭并保存中断,然后调用____napi_schedule继续处理,处理完后恢复中断。

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主要做两件事:

  • 将网络设备的napi_struct添加到CPU的poll_list队列中
  • 标记接收数据软中断NET_RX_SOFTIRQ
static inline void ____napi_schedule(struct softnet_data *sd,
				     struct napi_struct *napi)
{
	list_add_tail(&napi->poll_list, &sd->poll_list); //将软中断处理函数添加到CPU的poll_list
	__raise_softirq_irqoff(NET_RX_SOFTIRQ);			//标记软件中断
}

至此网络设备的硬件中断已经处理完成,接下来由软件中断中调用napi的poll函数从设备缓冲区拷贝数据帧到内核空间。

你可能感兴趣的:(协议栈,网络,通信,网络)