IP层实现2--gro

GRO(Generic Receive Offload)是一种用于提升网络接收速度的技术,它通过合并多个相关的报文减少协议栈处理的报文数量。在NAPI驱动的支持下,GRO在接收到数据后通过napi_gro_receive传递给上层协议,利用sk_buff->cb存储相关数据。然而,如果数据需要转发,GRO可能降低处理速度。GRO涉及到IP层和TCP层的处理函数,通过初始化napi_struct并注册到设备来启用。

摘要生成于 C知道 ,由 DeepSeek-R1 满血版支持, 前往体验 >

GRO(generic receive offload)。这是为了提高接收速度的一种机制。原理就是在接收端通过把多个相关的报文(比如TCP分段报文)组装成一个大的报文后再传送给协议栈进行处理。因为内核协议栈对报文的处理都是对报文头部进行处理,如果相关的多个报文合并后只有一个报文头,这样就减少了协议栈处理报文个数,加快协议栈对报文的处理速度。但如果包是被转,不需要使用GRO,这时使用GRO功能反而会降低处理速度。GRO功能和只是针对NAPI类型的驱动。

当支持NAPI时,网卡接收到数据后,通过调用napi_gro_receive,将数据交给上层协议,而在此过程中,会在每层协议中调用不同的回调函数。

实现时,是使用了分片,新的包被当作分片插入到旧包当中。

要支持GRO,首先要初始化napi_struct对象,并向设备进行注册:

[ net/core/dev.c ]

void netif_napi_add(struct net_device *dev, struct napi_struct *napi,
		    int (*poll)(struct napi_struct *, int), int weight)
{
	INIT_LIST_HEAD(&napi->poll_list);	// 将 napi->poll_list 初始化为空列表
	napi->gro_count = 0;
	napi->gro_list = NULL;
	napi->skb = NULL;
	napi->poll = poll;	// NAPI执行poll时的函数
	if (weight > NAPI_POLL_WEIGHT)	// 64
		pr_err_once("netif_napi_add() called with weight %d on device %s\n",
			    weight, dev->name);
	napi->weight = weight;	// 这里为16
	list_add(&napi->dev_list, &dev->napi_list);	// NAPI挂载到网络设备上,这样可以通过net_device访问到NAPI
	napi->dev = dev;	// NAPI相关联的网络设备
#ifdef CONFIG_NETPOLL
	spin_lock_init(&napi->poll_lock);
	napi->poll_owner = -1;
#endif
	set_bit(NAPI_STATE_SCHED, &napi->state); /* Poll is scheduled, 将NAPI关闭 */
}

GRO要跨过很多个协议层,在跨越的时候,sk_buff中的数据偏移(如data等)不能改变,所以使用sb_buff->cb,设计了一个napi_gro_cb用来记录和GRO相关的数据:
[ include/linux/netdevice.h ]

struct napi_gro_cb {
	/* Virtual address of skb_shinfo(skb)->frags[0].page + offset. */
	void *frag0;

	/* Length of frag0. */
	unsigned int frag0_len;

	/* This indicates where we are processing relative to skb->data. 
	 * 进行GRO处理中的数据偏移量,在整个过程中,skb->data的值是不能改变的,所以要用此值来保存偏移量
	 */
	int data_offset;

	/* This is non-zero if the packet cannot be merged with the new skb. */
	u16	flush;

	/* Save the IP ID here and check when we get to the transport layer */
	u16	flush_id;

	/* Number of segments aggregated. 
	 * 合并的包的个数
	 */
	u16	count;

	/* This is non-zero if the packet may be of the same flow. 
	 * 标记挂在napi->gro_list上的报文是否跟现在的报文进行匹配
	 * 每层的gro_receive都设置该标记位。接收到一个报文后,使用该报文和挂在napi->gro_list 上 的报文进行匹配
	 * 在上层只要考虑下层中被设为1的报文就可以了
	 */
	u8	same_flow;

	/* Free the skb? */
	u8	free;
#define NAPI_GRO_FREE		  1
#define NAPI_GRO_FREE_STOLEN_HEAD 2

	/* jiffies when first packet was created/queued */
	unsigned long age;

	/* Used in ipv6_gro_receive() */
	u16	proto;

	/* Used in udp_gro_receive */
	u16	udp_mark;

	/* used to support CHECKSUM_COMPLETE for tunneling protocols */
	__wsum	csum;

	/* used in skb_gro_receive() slow path */
	struct sk_buff *last;
};

GRO的入口在驱动中调用,如对于8139cp驱动,在cp_rx_skb函数中进行调用,调用的函数如下:

[ net/core/dev.c ]

gro_result_t napi_gro_receive(struct napi_struct *napi, struct sk_buff *skb)
{
	trace_napi_gro_receive_entry(skb);

	return napi_skb_finish(dev_gro_receive(napi, skb), skb);
}
EXPORT_SYMBOL(napi_gro_receive);

先看napi_skb_finish,它根据dev_gro_receive的返回值,作一些处理:

[ net/core/dev.c ]

static gro_result_t napi_skb_finish(gro_result_t ret, struct sk_buff *skb)
{
	switch (ret) {
	case GRO_NORMAL:	// 没有经过GRO处理,提交给上层协议
		if (netif_receive_skb_internal(skb))
			ret = GRO_DROP;
		break;

	case GRO_DROP:	// 丢弃
		kfree_skb(skb);
		break;

	case GRO_MERGED_FREE:	// skb被合并,skb可以被删除
		if (NAPI_GRO_CB(skb)->free == NAPI_GRO_FREE_STOLEN_HEAD)
			kmem_cache_free(skbuff_head_cache, skb);
		else
			__kfree_skb(skb);
		break;

	case GRO_HELD:	// skb被加入到gro_list中
	case GRO_MERGED:	// skb合并完成
		break;
	}

	return ret;
}
对GRO的处理主要是通过dev_gro_receive来进行的:

[ net/core/dev.c ]

static enum gro_result dev_gro_receive(struct napi_struct *napi, struct sk_buff *skb)
{
	struct sk_buff **pp = NULL;
	struct packet_offload *ptype;
	__be16 type = skb->protocol;	// 网络层协议
	struct list_head *head = &offload_base;	// 每个包类型都会注册自己的packet_offload,用来处理GRO的各个操作 
	int same_flow;
	enum gro_result ret;

	/* 设备是否支持GRO
	 * NETIF_F_GRO : Generic receive offload 
	 * netpoll_rx_on 只在配置了 CONFIG_NETPOLL 才有用,否则返回false
	 */
	if (!(skb->dev->features & NETIF_F_GRO) || netpoll_rx_on(skb))
		goto normal;	// 不作处理

	if (skb_is_gso(skb) || skb_has_frag_list(skb))	// 已经进行过了GRO
		goto normal;	// 不作处理

	skb_gro_reset_offset(skb);	// 初始化和GRO相关的数据
	gro_list_prepare(napi, skb);	// 查看是否有包与skb是同一个流
	NAPI_GRO_CB(skb)->csum = skb->csum; /* Needed for CHECKSUM_COMPLETE */

	rcu_read_lock();
	list_for_each_entry_rcu(ptype, head, list) {	// 遍历所有的包类型表
		if (ptype->type != type || !ptype->callbacks.gro_receive)	// 类型不同或是没有定义GRO函数
			continue;

		skb_set_network_header(skb, skb_gro_offset(skb));	// 设置网络层头部(0)
		skb_reset_mac_len(skb);	// 重置MAC地址长度(0)
		NAPI_GRO_CB(skb)->same_flow = 0;	// 不在同一个流
		NAPI_GRO_CB(skb)->flush = 0;	// 可以合并
		NAPI_GRO_CB(skb)->free = 0;	// 不能释放
		NAPI_GRO_CB(skb)->udp_mark = 0;

		pp = ptype->callbacks.gro_receive(&napi->gro_list, skb);	// 调用包类型的GRO函数
		break;
	}
	rcu_read_unlock();

	if (&ptype->list == head)
		goto normal;	// 不作处理

	same_flow = NAPI_GRO_CB(skb)->same_flow;	// 是否在同一个流里
	ret = NAPI_GRO_CB(skb)->free ? GRO_MERGED_FREE : GRO_MERGED;	// skb是要被删除还是被合并了

	if (pp) {	// 包是经过合并的
		struct sk_buff *nskb = *pp;	// 合并后的包

		// 将合并过的包从napi->gro_list中移除
		*pp = nskb->next;
		nskb->next = NULL;
		napi_gro_complete(nskb); // 将合并过的包提交给上层
		napi->gro_count--; // napi中的包的数量减1
	}

	if (same_flow)	// skb在同一个流里
		goto ok;

	if (NAPI_GRO_CB(skb)->flush)	// skb不能被合并
		goto normal;

	/* 没有合并的包被挂在napi->gro_list中
	 */
	if (unlikely(napi->gro_count >= MAX_GRO_SKBS)) {	// napi中的包的数量>=8
		struct sk_buff *nskb = napi->gro_list;	// GRO列表

		/* locate the end of the list to select the 'oldest' flow */
		while (nskb->next) {
			pp = &nskb->next;
			nskb = *pp;
		}
		 
		// 将napi->gro_list中的最后一个包移除
		*pp = NULL;
		nskb->next = NULL;
		napi_gro_complete(nskb); // 将移除的包提交给上层
	} else {
		napi->gro_count++;	// napi中包的数量数加1
	}
	NAPI_GRO_CB(skb)->count = 1;	// skb合并过的次数设为1
	NAPI_GRO_CB(skb)->age = jiffies;	// skb被合并的时间
	NAPI_GRO_CB(skb)->last = skb;	// last指向gro_list的最后一个对象(skb后面会挂到gro_list后面)
	skb_shinfo(skb)->gso_size = skb_gro_len(skb);	// skb负载数据的大小
	skb->next = napi->gro_list;	// skb挂入gro_list中
	napi->gro_list = skb;
	ret = GRO_HELD;	// skb被挂入gro_list中

	/* 不支持GRO
	 * skb进行过GRO操作
	 * &ptype->list == head
	 * skb不能被合并
	 */
pull:
	if (skb_headlen(skb) < skb_gro_offset(skb)) {	// 在分片中
		int grow = skb_gro_offset(skb) - skb_headlen(skb);	// skb中负载数据的偏移 - 主buffer的长度

		BUG_ON(skb->end - skb->tail
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值