linux-网络协议栈1--从中断到上送协议栈

本文介绍了Linux网络协议栈处理报文的过程,重点讨论了NAPI和非NAPI两种方式。在NAPI方式下,硬件中断触发软中断,通过轮训处理多个报文。软中断处理函数在禁止中断的状态下执行,处理完成后开启中断。非NAPI方式中,报文在中断处理前已拷贝到输入队列,软中断处理通用的输入队列。两种方式最终都会调用__netif_receive_skb进入上层协议栈处理。
摘要由CSDN通过智能技术生成

注: 内核代码是 4.9 版本

协议栈从报文接收说起,报文接收从网卡驱动说起。

两种方式,NAPI 和 非NAPI。

NAPI(New API) 是Linux内核针对网络数据传输做出的一个优化措施。
其目的是在大量数据传输时, 在收到硬件中断后,通过poll方式将传输过来的数据包统一处理, 通过禁止网络设备中断以减少硬件中断数量((Interrupt Mitigation),从而实现更高的数据传输。

其中要点:

1、硬件中断后开始处理报文。中断处理函数只是触发软中断准备接收报文;

2、软中断中通过pool方式处理报文。通过轮训的方式一次性处理多个报文;

3、禁止网络设备中断以减少硬件中断数量。同上,在软中断处理函数中,将禁止中断,处理完成后,开启中断,这样一次中断处理多个报文。

##先从NAPI方式说起

以 Inter 的 e1000 的驱动为例,e1000在加载驱动并做设备初始化时会调用 e1000_probe 函数,完成设备的部分初始化工作,重要的是设备的napi结构 和 poll函数是在这个函数中设置的:

static int e1000_probe(struct pci_dev *pdev, const struct pci_device_id *ent)
{
	struct net_device *netdev;
	struct e1000_adapter *adapter;
	struct e1000_hw *hw;
......

	// 为网卡创建网络设备对象 net_device 结构,并完成组册
	netdev = alloc_etherdev(sizeof(struct e1000_adapter));
	if (!netdev)
		goto err_alloc_etherdev;

	SET_NETDEV_DEV(netdev, &pdev->dev);
        // 设置网卡私有数据
	pci_set_drvdata(pdev, netdev);
	adapter = netdev_priv(netdev);
	adapter->netdev = netdev;
	adapter->pdev = pdev;
	adapter->msg_enable = netif_msg_init(debug, DEFAULT_MSG_ENABLE);
	adapter->bars = bars;
	adapter->need_ioport = need_ioport;

	hw = &adapter->hw;
	hw->back = adapter;

	err = -EIO;
        // 映射寄存器IO区域(后面拷贝报文)
	hw->hw_addr = pci_ioremap_bar(pdev, BAR_0);
	if (!hw->hw_addr)
		goto err_ioremap;

	......
        // 挂载网络设备操作接口
	netdev->netdev_ops = &e1000_netdev_ops;
	e1000_set_ethtool_ops(netdev);
	netdev->watchdog_timeo = 5 * HZ;
        // 初始化并挂载设备的NAPI接口,e1000_clean 是其poll函数,软中断中调用处理报文
        // adapter->napi 挂到 netdev->napi_list
	netif_napi_add(netdev, &adapter->napi, e1000_clean, 64);

	strncpy(netdev->name, pci_name(pdev), sizeof(netdev->name) - 1);

	......
}

还有个重要的函数是e1000的网卡中断处理函数,其是在上面 e1000_netdev_ops 中的ndo_open函数(网卡UP后会调用)中,调用的 e1000_request_irq注册的 e1000_intr 函数。

然后是NAPI的接收流程。
在网卡中断之前,数据包到达网卡之后,就通过DMA直接将数据从网卡拷贝到内存的环形缓冲区了,成为 ring buffer,和非NAPI不同。

中断处理函数,如果没有在运行的NAPI任务,调度一个新的NAPI任务,会调用通用的NAPI处理函数,__napi_schedule,将设备的napi 挂载到当前CPU的 softnet_data 的待轮训设备列表poll_list中,并触发软中断。
napi_schedule_prep 中会检查并设置 napi_struct的 NAPI_STATE_SCHED位,并在流程结束后(软中断处理完报文)调用 __napi_complete,清楚状态位。

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;
......
        // 如果没有在运行的NAPI任务,调度一个新的NAPI任务
	if (likely(napi_schedule_prep(&adapter->napi))) {
		adapter->total_tx_bytes = 0;
		adapter->total_tx_packets = 0;
		adapter->total_rx_bytes = 0;
		adapter->total_rx_packets = 0;
                // 调用通用的NAPI处理函数
		__napi_schedule(&adapter->napi);
	} 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;
}

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);
}

static inline void ____napi_schedule(struct softnet_data *sd,
				     struct napi_struct *napi)
{
        //  将设备的napi 挂载带了 sd 的poll_list中。
	list_add_tail(&napi->poll_list, &sd->poll_list);
       //  触发软中断
	__raise_softirq_irqoff(NET_RX_SOFTIRQ);
}

软中断接收处理函数是net_rx_action,在网络设备模块初始化时注册。

net_dev_init:

	open_softirq(NET_TX_SOFTIRQ, net_tx_action);
	open_softirq(NET_RX_SOFTIRQ, net_rx_action);

内核ksoftirqd%d(%d对应CPU的ID)内核线程用于处理CPU上的软中断。内核在初始化的时候,会在每个CPU上启动一个这样的内核线程用来处理每个CPU上的软中断。
最终会调用到上面注册的收发报的软中断处理函数。

// 注册
static struct smp_hotplug_thread softirq_threads = {
	.store			= &ksoftirqd,
	.thread_should_run	= ksoftirqd_should_run,
	.thread_fn		= run_ksoftirqd,
	.thread_comm		= "ksoftirqd/%u&
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值