本文主要注解skb_gro_receive函数,并画出示意图,便于理解。该函数是把前面已经判断过属于同一条流的skb合并到之前已经聚合过的skb上,把要合并的数据payload整理到已经合并的skb上去,下面根据代码逻辑画出4组示意图。
int skb_gro_receive(struct sk_buff **head, struct sk_buff *skb)
{
struct skb_shared_info *pinfo, *skbinfo = skb_shinfo(skb);
unsigned int offset = skb_gro_offset(skb);
unsigned int headlen = skb_headlen(skb);//线性区域长度
unsigned int len = skb_gro_len(skb);//data部分的长度
struct sk_buff *lp, *p = *head;//已经合并的报文的首地址
unsigned int delta_truesize;
if (unlikely(p->len + len >= 65536))
return -E2BIG;
lp = NAPI_GRO_CB(p)->last;
pinfo = skb_shinfo(lp);
//headlen是skb的线性区域长度,offset是payload的偏移,如果headlen<offset说明还有一部分头部在非线性区域,也说明数据部分都在非线性区域
if (headlen <= offset) {
skb_frag_t *frag;
skb_frag_t *frag2;
int i = skbinfo->nr_frags;//skb的frags的个数
int nr_frags = pinfo->nr_frags + i;//合并之后的总的frags的个数
if (nr_frags > MAX_SKB_FRAGS) //如果nr_frags大于最大的限制,那么直接merge
goto merge;
offset -= headlen;//此时的offset表示,在非线性区域,头部的长度
pinfo->nr_frags = nr_frags;//更新nr_frags
skbinfo->nr_frags = 0;//skb的nr_frags置0
frag = pinfo->frags + nr_frags;//指向已经合并的skb的最后一个frags[]
frag2 = skbinfo->frags + i;//指向待合并的skb的最后一个frags[]
do {
*--frag = *--frag2;//已经合并的skb的frags[]指针指向待合并的skb的frags[]指针(先--,再赋值)
} while (--i);
frag->page_offset += offset;//frag[0]需要操作一下offset的问题,把头部的部分剔除
skb_frag_size_sub(frag, offset);
/* all fragments truesize : remove (head size + sk_buff) */
delta_truesize = skb->truesize -
SKB_TRUESIZE(skb_end_offset(skb));
skb->truesize -= skb->data_len;
skb->len -= skb->data_len;
skb->data_len = 0;
NAPI_GRO_CB(skb)->free = NAPI_GRO_FREE;
goto done;
} else if (skb->head_frag) {
int nr_frags = pinfo->nr_frags;
skb_frag_t *frag = pinfo->frags + nr_frags;//此flow合并后的最后一个分片包的skb_frag_t结构体执针
struct page *page = virt_to_head_page(skb->head);//获取skb->head所在的page
unsigned int first_size = headlen - offset; //data部分的长度
unsigned int first_offset;
if (nr_frags + 1 + skbinfo->nr_frags > MAX_SKB_FRAGS) //合并后的frag数目大于最大的frag(17)数目直接merge
goto merge;
first_offset = skb->data -
(unsigned char *)page_address(page) +
offset; //获取到payload到本page的offset
pinfo->nr_frags = nr_frags + 1 + skbinfo->nr_frags;//合并后分片的数量
frag->page.p = page;//页数据的首地址
frag->page_offset = first_offset;//payload部分在该页的偏移
skb_frag_size_set(frag, first_size);//frag->size是page中数据的长度
memcpy(frag + 1, skbinfo->frags, sizeof(*frag) * skbinfo->nr_frags);// skb如果有 分片数组直接拷贝到后面
/* We dont need to clear skbinfo->nr_frags here */
delta_truesize = skb->truesize - SKB_DATA_ALIGN(sizeof(struct sk_buff));//对齐
NAPI_GRO_CB(skb)->free = NAPI_GRO_FREE_STOLEN_HEAD;//置位,当前报文已经做过gro,待释放。
goto done;
}
merge:
delta_truesize = skb->truesize;
if (offset > headlen) {
unsigned int eat = offset - headlen;
skbinfo->frags[0].page_offset += eat;
skb_frag_size_sub(&skbinfo->frags[0], eat);
skb->data_len -= eat;
skb->len -= eat;
offset = headlen;
}
__skb_pull(skb, offset);
if (NAPI_GRO_CB(p)->last == p)
skb_shinfo(p)->frag_list = skb;
else
NAPI_GRO_CB(p)->last->next = skb;
NAPI_GRO_CB(p)->last = skb;
__skb_header_release(skb);
lp = p;
done:
NAPI_GRO_CB(p)->count++;
p->data_len += len; //非线性区域长度
p->truesize += delta_truesize;
p->len += len;
if (lp != p) {
lp->data_len += len;
lp->truesize += delta_truesize;
lp->len += len;
}
NAPI_GRO_CB(skb)->same_flow = 1;
return 0;
}
EXPORT_SYMBOL_GPL(skb_gro_receive);
(1)headlen>offset
把待合并的skb的page首地址和offset保存到已经合并的skb的frags[]中保存。如果待合并的skb在非线性区域中还有数据,那么直接调用函数:memcpy(frag + 1, skbinfo->frags, sizeof(*frag) * skbinfo->nr_frags) 把待合并的skb的非线性区域的数据拷贝到已经合并的skb的非线性区域中。
a.待合并的skb的payload部分全部存在非线性区域
b.payload一部分在线性区域,一部分在非线性区域,存在frags[]中,示意图如下:
(2)headlen <= offset
a.headlen < offset 说明待拷贝的skb还有一部分头部在非线性区frags[0].
b.headlen = offset,头部全部存在线性区域
若有问题欢迎各位大神指正,互相学习进步