linux内核数据包转发流程(三)网卡帧接收分析

【版权声明:转载请保留出处:blog.csdn.net/gentleliu。邮箱:shallnew*163.com】

每个cpu都有队列来处理接收到的帧,都有其数据结构来处理入口和出口流量,因此,不同cpu之间没有必要使用上锁机制,。此队列数据结构为softnet_data(定义在include/linux/netdevice.h中):

/*
 * Incoming packets are placed on per-cpu queues so that
 * no locking is needed.
 */
struct softnet_data
{
struct Qdisc *output_queue;
struct sk_buff_headinput_pkt_queue;//有数据要传输的设备列表
struct list_headpoll_list; //双向链表,其中的设备有输入帧等着被处理。
struct sk_buff*completion_queue;//缓冲区列表,其中缓冲区已成功传输,可以释放掉

struct napi_structbacklog;
};

此结构字段可用于传输和接收。换而言之,NET_RX_SOFTIRQ和NET_TX_SOFTIRQ软IRQ都引用此结构。入口帧会排入input_pkt_queue(NAPI有所不同)。

softnet_data是在net_dev_init函数中初始化的:

/*
 *       This is called single threaded during boot, so no need
 *       to take the rtnl semaphore.
 */
static int __init net_dev_init(void)
{
int i, rc = -ENOMEM;

......

/*
* Initialise the packet receive queues.
*/

for_each_possible_cpu(i) {
struct softnet_data *queue;

queue = &per_cpu(softnet_data, i);
skb_queue_head_init(&queue->input_pkt_queue);
queue->completion_queue = NULL;
INIT_LIST_HEAD(&queue->poll_list);

queue->backlog.poll = process_backlog;
queue->backlog.weight = weight_p;
queue->backlog.gro_list = NULL;
queue->backlog.gro_count = 0;
}

......

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

......
}

非NAPI设备驱动会为其所接收的每一个帧产生一个中断事件,在高流量负载下,会花掉大量时间处理中断事件,造成资源浪费。而NAPI驱动混合了中断事件和轮询,在高流量负载下其性能会比旧方法要好。

NAPI主要思想是混合使用中断事件和轮询,而不是仅仅使用中断事件驱动模型。当收到新的帧时,关中断,再一次处理完所有入口队列。从内核观点来看,NAPI方法因为中断事件少了,减少了cpu负载。

使用非NAPI的驱动程序的xx_rx()函数一般如下:

void xx_rx()
{
struct sk_buff *skb;

skb = dev_alloc_skb(pkt_len + 5);
if (skb != NULL) {
skb_reserve(skb, 2);/* Align IP on 16 byte boundaries */

/*memcpy(skb_put(skb, 2), pkt, pkt_len);*/ //copy data to skb

skb->protocol = eth_type_trans(skb, dev);
netif_rx(skb);
}
}

第一步是分配一个缓存区来保存报文。 注意缓存分配函数 (dev_alloc_skb) 需要知道数据长度。

第二步将报文数据被拷贝到缓存区; skb_put  函数更新缓存中的数据末尾指针并返回指向新建空间的指针。

第三步提取协议标识及获取其他信息。

最后调用netif_rx(skb)做进一步处理,该函数一般定义在net/core/dev.c中。

int netif_rx(struct sk_buff *skb)
{
struct softnet_data *queue;
unsigned long flags;

/* if netpoll wants it, pretend we never saw it */
if (netpoll_rx(skb))
return NET_RX_DROP;

if (!skb->tstamp.tv64)
net_timestamp(skb);

/*
* The code is rearranged so that the path is the most
* short when CPU is congested, but is still operating.
*/
local_irq_save(flags);
queue = &__get_cpu_var(softnet_data);

__get_cpu_var(netdev_rx_stat).total++;
if (queue->input_pkt_queue.qlen <= netdev_max_backlog) {//是否还有空间,netdev_max_backlog一般为300
//只有当新缓冲区为空时,才会触发软中断(napi_schedule()),如果缓冲区不为空,软中断已被触发,没有必要再去触发一次。
if (queue->input_pkt_queue.qlen) {
enqueue:
__skb_queue_tail(&queue->input_pkt_queue, skb);//这里是关键之处,将skb加入input_pkt_queue之中。
local_irq_restore(flags);
return NET_RX_SUCCESS;
}

napi_schedule(&queue->backlog);//触发软中断
goto enqueue;
}

__get_cpu_var(netdev_rx_stat).dropped++;
local_irq_restore(flags);

kfree_skb(skb);
return NET_RX_DROP;
}
EXPORT_SYMBOL(netif_rx);
static inline void napi_schedule(struct napi_struct *n)
{
	if (napi_schedule_prep(n))
		__napi_schedule(n);
}
void __napi_schedule(struct napi_struct *n)
{
	unsigned long flags;

	local_irq_save(flags);
	list_add_tail(&n->poll_list, &__get_cpu_var(softnet_data).poll_list);//将该设备加入轮询链表,等待该设备的帧被处理
	__raise_softirq_irqoff(NET_RX_SOFTIRQ);//最终触发软中断
	local_irq_restore(flags);
}
EXPORT_SYMBOL(__napi_schedule);

至此中断的上半部完成,其他的工作交由下半部来实现。napi_schedule(&queue->backlog)函数将有等待的接收数据包的NIC链入softnet_data的poll_list队列,然后触发软中断,让下半部去完成数据的处理工作。

而是用NAPI设备的接受数据时直接触发软中断,不需要通过netif_rx()函数设置好接收队列再触发软中断。比如e100硬中断处理函数为:

static irqreturn_t e100_intr(int irq, void *dev_id)
{
	struct net_device *netdev = dev_id;
	struct nic *nic = netdev_priv(netdev);
	u8 stat_ack = ioread8(&nic->csr->scb.stat_ack);

	DPRINTK(INTR, DEBUG, "stat_ack = 0x%02X\n", stat_ack);

	if (stat_ack == stat_ack_not_ours ||	/* Not our interrupt */
	   stat_ack == stat_ack_not_present)	/* Hardware is ejected */
		return IRQ_NONE;

	/* Ack interrupt(s) */
	iowrite8(stat_ack, &nic->csr->scb.stat_ack);

	/* We hit Receive No Resource (RNR); restart RU after cleaning */
	if (stat_ack & stat_ack_rnr)
		nic->ru_running = RU_SUSPENDED;

	if (likely(napi_schedule_prep(&nic->napi))) {
		e100_disable_irq(nic);
		__napi_schedule(&nic->napi);//此处触发软中断
	}

	return IRQ_HANDLED;
}

在前面我们已经知道在net_dev_init()函数中注册了收报软中断函数net_rx_action(),当软中断被触发之后,该函数将被调用。

net_rx_action()函数为:

static void net_rx_action(struct softirq_action *h)
{
	struct list_head *list = &__get_cpu_var(softnet_data).poll_list;
	unsigned long time_limit = jiffies + 2;
	int budget = netdev_budget;
	void *have;

	local_irq_disable();

	while (!list_empty(list)) {
		struct napi_struct *n;
		int work, weight;

		/* If softirq window is exhuasted then punt.
		 * Allow this to run for 2 jiffies since which will allow
		 * an average latency of 1.5/HZ.
		 */
		if (unlikely(budget <= 0 || time_after(jiffies, time_limit)))//入口队列仍然有缓冲区,软IRQ再度被调度执行。
			goto softnet_break;

		local_irq_enable();

		/* Even though interrupts have been re-enabled, this
		 * access is safe because interrupts can only add new
		 * entries to the tail of this list, and only ->poll()
		 * calls can remove this head entry from the list.
		 */
		n = list_entry(list->next, struct napi_struct, poll_list);

		have = netpoll_poll_lock(n);

		weight = n->weight;

		/* This NAPI_STATE_SCHED test is for avoiding a race
		 * with netpoll‘s poll_napi().  Only the entity which
		 * obtains the lock and sees NAPI_STATE_SCHED set will
		 * actually make the ->poll() call.  Therefore we avoid
		 * accidently calling ->poll() when NAPI is not scheduled.
		 */
		work = 0;
		if (test_bit(NAPI_STATE_SCHED, &n->state)) {
			work = n->poll(n, weight);//执行poll函数,返回已处理的帧
			trace_napi_poll(n);
		}

		WARN_ON_ONCE(work > weight);

		budget -= work;

		local_irq_disable();

		/* Drivers must not modify the NAPI state if they
		 * consume the entire weight.  In such cases this code
		 * still "owns" the NAPI instance and therefore can
		 * move the instance around on the list at-will.
		 */
		if (unlikely(work == weight)) {//队列被清空。调用napi_complete()负责此事。
			if (unlikely(napi_disable_pending(n))) {
				local_irq_enable();
				napi_complete(n);
				local_irq_disable();
			} else
				list_move_tail(&n->poll_list, list);
		}

		netpoll_poll_unlock(have);
	}
out:
	local_irq_enable();

#ifdef CONFIG_NET_DMA
	/*
	 * There may not be any more sk_buffs coming right now, so push
	 * any pending DMA copies to hardware
	 */
	dma_issue_pending_all();
#endif

	return;

softnet_break:
	__get_cpu_var(netdev_rx_stat).time_squeeze++;
	__raise_softirq_irqoff(NET_RX_SOFTIRQ);
	goto out;
}

由上可见,下半部的主要工作是遍历有数据帧等待接收的设备链表,对于每个设备,执行它相应的poll函数。

对非NAPI设备来说,poll函数在net_dev_init()函数中初始化为process_backlog()。

process_backlog()函数定义为:

static int process_backlog(struct napi_struct *napi, int quota)
{
	int work = 0;
	struct softnet_data *queue = &__get_cpu_var(softnet_data);
	unsigned long start_time = jiffies;

	napi->weight = weight_p;
	do {
		struct sk_buff *skb;

		local_irq_disable();
		skb = __skb_dequeue(&queue->input_pkt_queue);
		if (!skb) {
			__napi_complete(napi);
			local_irq_enable();
			break;
		}
		local_irq_enable();

		netif_receive_skb(skb);
	} while (++work < quota && jiffies == start_time);

	return work;
}

对NAPI设备来的说,驱动程序必须提供一个poll方法,poll 方法有下面原型:

int (*poll)(struct napi_struct *dev, int *budget);

在初始化时需要添加该方法:

netif_napi_add(netdev, &nic->napi, xx_poll, XX_NAPI_WEIGHT);

NAPI驱动 的 poll 方法实现一般如下(借用《Linux设备驱动程序》中代码,内核有点没对上,懒得去写了):

static int xx_poll(struct net_device *dev, int *budget)
{
    int npackets = 0, quota = min(dev->quota, *budget);
    struct sk_buff *skb;
    struct xx_priv *priv = netdev_priv(dev);
    struct xx_packet *pkt;

    while (npackets < quota && priv->rx_queue) {
        pkt = xx_dequeue_buf(dev);
        skb = dev_alloc_skb(pkt->datalen + 2);
        if (! skb) {

            if (printk_ratelimit())
                printk(KERN_NOTICE "xx: packet dropped\n"); priv->stats.rx_dropped++; xx_release_buffer(pkt); continue;
        }
        memcpy(skb_put(skb, pkt->datalen), pkt->data, pkt->datalen);
        skb->dev = dev;
        skb->protocol = eth_type_trans(skb, dev);
        skb->ip_summed = CHECKSUM_UNNECESSARY; /* don‘t check it */
        netif_receive_skb(skb);

        /* Maintain stats */
        npackets++;
        priv->stats.rx_packets++;
        priv->stats.rx_bytes += pkt->datalen;
        xx_release_buffer(pkt);

    }
    /* If we processed all packets, we‘re done; tell the kernel and reenable ints */
    *budget -= npackets;
    dev->quota -= npackets;
    if (! priv->rx_queue) {

        netif_rx_complete(dev);
        xx_rx_ints(dev, 1);
        return 0;

    }
    /* We couldn‘t process everything. */
    return 1;

}

NAPI驱动提供自己的poll函数和私有队列。

不管是非NAPI或NAPI,他们的poll函数最后都会调用netif_receive_skb(skb)来处理接收到的帧。该函数会想各个已注册的协议例程发送一个skb,之后数据进入Linux内核协议栈处理。

linux内核数据包转发流程(三)网卡帧接收分析

时间: 2024-10-03 11:53:06

linux内核数据包转发流程(三)网卡帧接收分析的相关文章

linux内核数据包转发流程(一):网络设备驱动

[版权声明:转载请保留出处:blog.csdn.net/gentleliu.邮箱:shallnew*163.com] 网卡驱动为每个新的接口在一个全局的网络设备列表里插入一个数据结构.每个接口由一个结构 net_device 项来描述, 它在 <linux/netdevice.h> 里定义.该结构必须动态分配. 进行这种分配的内核函数是 alloc_netdev, 它有下列原型: struct net_device *alloc_netdev(int sizeof_priv, const ch

linux内核数据包转发流程(二)中断

[版权声明:转载请保留出处:blog.csdn.net/gentleliu.邮箱:shallnew*163.com] 内核在处理2层数据包之前,必须先处理中断系统,设立中断系统,才有可能每秒处理成千的帧. 当收到一个帧时,驱动程序会代表内核指示设备产生一个硬件中断,内核将中断其他的活动,然后调用一个驱动程序所注册的处理函数,以满足设备的需要.当事件是接收到一个帧时,处理函数就会把该帧排入队列某处,然后通知内核. 使用轮询技术会轻易浪费掉很多系统资源,因为内核会持续去读取检查是否有有帧的到来.但使

linux内核数据包转发流程(二):中断

[版权声明:转载请保留出处:blog.csdn.net/gentleliu.邮箱:shallnew*163.com] 内核在处理2层数据包之前,必须先处理中断系统.设立中断系统,才有可能每秒处理成千的帧. 当收到一个帧时,驱动程序会代表内核指示设备产生一个硬件中断,内核将中断其它的活动,然后调用一个驱动程序所注冊的处理函数,以满足设备的须要.当事件是接收到一个帧时,处理函数就会把该帧排入队列某处,然后通知内核. 使用轮询技术会轻易浪费掉非常多系统资源,由于内核会持续去读取检查是否有有帧的到来.

LINUX下的远端主机登入 校园网络注册 网络数据包转发和捕获

第一部分:LINUX 下的远端主机登入和校园网注册 校园网内目的主机远程管理登入程序 本程序为校园网内远程登入,管理功能,该程序分服务器端和客户端两部分:服务器端为remote_server_udp.py 客户端分为单播客户端和广播客户端: 单播客户端client_unicast.py 广播客户端client_broadcast.py 1.单播客户端为根据net.info文件中的网络记录遍历目标网段中的所有IP,向其发送UDP封包. net.info中记录了目标网络中的一个样例IP和目标网段的子

linux下打开关闭数据包转发

Linux下默认是禁止数据包转发的,但在某些特殊场合需要使用这一功能,所谓转发即当主机拥有多于一块的网卡时,其中一块收到数据包,根据数据包的目的ip地址将包发往本机另一网卡,该网卡根据路由表继续发送数据包.这通常就是路由器所要实现的功能. 使能数据转发功能: echo 1 > /proc/sys/net/ipv4/ip_forward 禁止数据转发功能: echo 0 > /proc/sys/net/ipv4/ip_forward 版权声明:本文为博主原创文章,未经博主允许不得转载.

Linux内核网络报文简单流程

转:http://blog.csdn.net/adamska0104/article/details/45397177 Linux内核网络报文简单流程2014-08-12 10:05:09 分类: Linux linux下的网卡驱动中通常会提供类似XXX_rx的接收函数 该函数处理与具体硬件相关的寄存器操作 包括中断检查,数据状态检查,错误检查等 在确认有数据抵达后读取数据或从DMA的接收环中获取数据地址 XXX_rx函数以skb为元数据结构组织报文数据 随后调用内核接口函数netif_rx或n

[转帖]Linux内核为大规模支持100Gb/s网卡准备好了吗?并没有

Linux内核为大规模支持100Gb/s网卡准备好了吗?并没有 之前用 千兆的机器 下载速度 一般只能到 50MB 左右 没法更高 万兆的话 可能也就是 200MB左右的速度 很难更高 不知道后续的服务器 会不会 能够提升一下 之前坐着说到了 120nm 的时间 发送一个包 记得CPU的指令周期是 1-3nm左右 个内存的时间差不多了 不知道RDMA等的方式 可不可能完成相应的高吞吐量的处理. 原作者博客 https://blog.csdn.net/zhoukejun/article/detai

Linux网络 - 数据包的接收过程【转】

转自:https://segmentfault.com/a/1190000008836467 本文将介绍在Linux系统中,数据包是如何一步一步从网卡传到进程手中的. 如果英文没有问题,强烈建议阅读后面参考里的两篇文章,里面介绍的更详细. 本文只讨论以太网的物理网卡,不涉及虚拟设备,并且以一个UDP包的接收过程作为示例. 本示例里列出的函数调用关系来自于kernel 3.13.0,如果你的内核不是这个版本,函数名称和相关路径可能不一样,但背后的原理应该是一样的(或者有细微差别) 网卡到内存 网卡

[转]Linux网络 - 数据包的接收过程

转, 原文: https://segmentfault.com/a/1190000008836467 ----------------------------------------------------------------------------------------------------------------- 本文将介绍在Linux系统中,数据包是如何一步一步从网卡传到进程手中的. 如果英文没有问题,强烈建议阅读后面参考里的两篇文章,里面介绍的更详细. 本文只讨论以太网的物理网