在第一篇 sk_buff 整理笔记(一、数据结构)中已经对sk_buff的相关结构的常用成员字段进行了详细的分析,这里就不再赘述了。但前面的sk_buff结构成员字段对这篇sk_buff的克隆与拷贝非常重要,尤其是那几幅图,所以如果看此文时有不明白的地方,可以到第一篇中去查找下相关成员字段分析解释。根据前面已经说过sk_buff结构体是有三大块(其实是有四块结构体连接而成的,只是sk_buff数据区和分片结构体连在一起,可以当做一块来讲)结构体连成的。那复制拷贝的时候,这几块结构体复制的呢?首先来简要的说下几个复制拷贝函数对应的数据块拷贝情况。
skb_clone()函数:
首先要分析的就是单单克隆下sk_buff结构体,对sk_buff结构的数据区、分片结构体skb_shared_info、分片结构体数据区等结果进行共享。此方法通过skb_clone()函数来实现。
skb_clone()函数实现:
struct sk_buff *skb_clone(struct sk_buff *skb, gfp_t gfp_mask)函数关键代码分析:
{
struct sk_buff *n;
n = skb + 1;
if (skb->fclone == SKB_FCLONE_ORIG &&
n->fclone == SKB_FCLONE_UNAVAILABLE) {
atomic_t *fclone_ref = (atomic_t *) (n + 1);
n->fclone = SKB_FCLONE_CLONE;
atomic_inc(fclone_ref);
} else {
n = kmem_cache_alloc(skbuff_head_cache, gfp_mask);
if (!n)
return NULL;
kmemcheck_annotate_bitfield(n, flags1);
kmemcheck_annotate_bitfield(n, flags2);
n->fclone = SKB_FCLONE_UNAVAILABLE;
}
return __skb_clone(n, skb);
}
第01行代码:struct sk_buff *skb_clone(struct sk_buff *skb,gfp_t gfp_mask);这个函数的第一个参数是将要被克隆的skb结构体(后面用父skb来称),第二个参数是向内核申请分配内存的方式;返回的是新克隆出来的skb结构体(后面称其为子skb);
第05行代码:n = skb + 1;这个要连续到sk_buff整理笔记(三、内存申请和释放),因为要被克隆的父skb一般在skbuff_fclone_cache缓存池中申请,若申请成功,则返回一对skb内存空间(即是父子skb),且空间为连续的。所以n = skb + 1;表示的是把事先申请好的skb内存空间给将要返回的克隆结构n;
第06--07行代码:首先判断父skb是否是在skbuff_fclone_cache缓存池上分配的(确保返回了两个skb),然后判断子skb是否被克隆(如果被克隆了,则不能再用了)。
第08行代码:如果上面(第06和第07行代码)两个条件皆满足,则开始获取skb的引用计数器变量了。在内存申请和释放那篇讲过,在skbuff_fclone_cache缓存池上申请的skb,不仅会返回两个skb空间,而且还会在子skb后增加一个字节,用来存放skb的引用计数器的。这里正是从子skb后面提取出skb的引用计数器。
第09--10行代码:接着设置子skb的fclone,表明其是子skb,从父skb中克隆而来的;最后是增加skb的引用计数器的值,因为多了一个克隆的skb。这样做的目的是防止系统不知道还有其他克隆的skb也在共享sk_buff结构的数据区,而提前释放造成出错。
第12行代码:这行代码是在上面(第06和07行代码)那两个条件不都满足的情况下,再到skbuff_head_cache缓存池上去申请(因为还不能确定这个skb是否要被克隆,所以到skbuff_head_cache缓存池上去申请),最后做些设置操作就不细分析了。
在skb_clone()函数最后一行调用了_skb_clone(n,skb);其实skb_clone()函数实现的仅仅是克隆一个skb内存空间,而一些数据拷贝复制则是用_skb_clone()函数来完成。所以_skb_clone()函数主要是实现从父skb中把相关成员字段拷贝到子skb中去。
_skb_clone()函数实现:
static struct sk_buff *__skb_clone(struct sk_buff *n, struct sk_buff *skb)函数关键代码分析:
{
#define C(x) n->x = skb->x
n->next = n->prev = NULL;
n->sk = NULL;
__copy_skb_header(n, skb);
C(len);
C(data_len);
C(mac_len);
n->hdr_len = skb->nohdr ? skb_headroom(skb) : skb->hdr_len;
n->cloned = 1;
n->nohdr = 0;
n->destructor = NULL;
C(tail);
C(end);
C(head);
C(data);
C(truesize);
atomic_set(&n->users, 1);
atomic_inc(&(skb_shinfo(skb)->dataref));
skb->cloned = 1;
return n;
#undef C
}
第01行代码:struct sk_buff *_skb_clone(struct st_buff *n,struct sk_buff *skb);第一个参数:n是表示未填充好成员字段的子skb;第二个参数:skb是父skb;返回的是从父skb成员字段复制填充好的子skb;
第03行代码:#define C(x) n->x = skb->x 这是个关键性代码,用把父skb中某个成员字段复制到子skb中;
第05行代码:让前驱后继指针都为NULL,因为这是个单独的sk_buff结构体,没有在sk_buff链表上。
第06行代码:调用了_copy_skb_header(h,skb);这是用来复制成员变量的,可以自己看看源码,都是赋值操作,没什么难的。
第22行代码:调用atomic_set(&n->users,1);来设置子skb的引用计数器为1,表明还有另外一个skb(其实就是父skb),防止子skb释放时连同共享数据区也一起释放掉。
第24行代码:atomic_inc(&(skb_shinfo(skb)->dataref));这个简单的说就是,因为开始也讲过sk_buff的数据区和分片结构是一体的,连内存申请和释放都是一起的。而dataref是分片结构skb_shared_info中的一个 表示sk_buff的数据区和分片结构被多少skb共享的 成员字段。这里调用atomic_inc()函数让该引用计数器自增,表明克隆skb对sk_buff数据区和分片结构的共享引用。
第25行代码:skb->cloned = 1;表明这是个克隆的skb结构体。
第28行代码:#undef C 这行代码是和第03行代码对应的,第03行代码定义的是个宏,其作用域和变量不一样,宏是从定义的地方开始到代码块结束都是有效的。没有什么局部之分,所以到第28行函数结束时取消了第03行代码定义的宏的作用范围。下面看skb_clone()函数的实现原理图:
其实上面的方法:由skb_clone()函数克隆一个skb,然后共享其他数据。虽然可以提高效率,但是存在一个很大的缺陷,就是当有克隆skb指向共享数据区是,那么共享数据区的数据就不能被修改了。所以说如果只是让多个skb查看共享数据区内容,则可以用skb_clone()函数来克隆这几个skb出来,提高效率。但如果涉及到某个skb要修改sk_buff结构的数据区,则必须要用下面这几个函数来克隆拷贝出skb。
pskb_copy()函数:
开始时已经分析过,sk_buff结构可以分成三大块来处理。上面的skb_clone()函数已经处理了第一大块:sk_buff自身结构体,那么现在通过pskb_copy()函数来处理第二大块:不仅拷贝sk_buff结构体,还拷贝sk_buff结构体指针data所指向的数据区(当然这个数据区包括了分片结构体,因为内存分配时,这两个结构体都是一起分配的,现在如果要重新为数据区分配内存的话,那自然也是一起分配了),但是分片结构体中所指的数据区是共享的。
pskb_copy()函数实现:
struct sk_buff *pskb_copy(struct sk_buff *skb, gfp_t gfp_mask)函数关键代码分析:
{
struct sk_buff *n;
#ifdef NET_SKBUFF_DATA_USES_OFFSET
n = alloc_skb(skb->end, gfp_mask);
#else
n = alloc_skb(skb->end - skb->head, gfp_mask);
#endif
if (!n)
goto out;
skb_reserve(n, skb->data - skb->head);
skb_put(n, skb_headlen(skb));
skb_copy_from_linear_data(skb, n->data, n->len);
n->truesize += skb->data_len;
n->data_len = skb->data_len;
n->len = skb->len;
if (skb_shinfo(skb)->nr_frags) {
int i;
for (i = 0; i < skb_shinfo(skb)->nr_frags; i++) {
skb_shinfo(n)->frags[i] = skb_shinfo(skb)->frags[i];
get_page(skb_shinfo(n)->frags[i].page);
}
skb_shinfo(n)->nr_frags = i;
}
if (skb_has_frags(skb)) {
skb_shinfo(n)->frag_list = skb_shinfo(skb)->frag_list;
skb_clone_fraglist(n);
}
copy_skb_header(n, skb);
out:
return n;
}
第01行代码:pskb_copy(struct sk_buff,gfp_t gfp_mask);第一个参数,是将要被复制的skb;第二个参数,是内核内存申请时,内存分配的方式。返回的是复制好的子skb结构。
第04--08行代码:有条件编译,其本质是调用alloc_skb()函数来为sk_buff结构及数据区(包括分片结构)分配内存空间,具体的分配步骤请看sk_buff整理笔记(三、内存申请和释放)
第12行代码:skb_reserve(n, skb->data - skb->head); 是让分配到的子skb中的data指针和tail指针指向同一个位置,为了后面改变data指针和tail指针来存放协议信息。
第13行代码:skb_put(n,skb_headlen(skb));使tail指针向高地址偏移(len - data_len)即是协议信息和应用层数据的长度和。以便存储各层协议和应用层数据。
第14行代码:这是个内存拷贝的封装函数,就是从被拷贝的skb结构中的data指针指向的地方开始,偏移len(因为len = (data - tail) + data_len;所以这里本应该写成(data - tail)的,但考虑到此时分片结构数据区还没有数据,data_len为零。即是len = data-tail)个字节内容都拷贝到新复制到的skb结构体中去。即是:用被拷贝的skb中的数据区内容来为新拷贝的skb结构的数据区填充。
第16--18行代码:是一些长度的设置。n->truesize是表示所有长度:truesize = (data - tail) + sizeof(sk_buff) + data_len;因为内存分配是已经把data-tail和sizeof(sk_buff)长度都赋值给了truesize,所以现在只需要再加上分片结构的数据区数据长度就可以了。其他的长度只是对应拷贝下值就可以。
第20行代码:是判断分片结构数据区是否有数据,有的换就要作为共享数据进行处理。skb_shinfo(skb)宏其实就是skb->end;返回的是分片结构的开始位置。
第21--27行代码:是让新的skb结构中的分片结构体指针指向 开始被拷贝的skb结构中分片结构体指针,就是让新的skb结构中的分片结构指针指向共享的分片结构数据区。skb_shinfo(n)->nr_frags = i;则是为新的skb结构体中的分片结构nr_frags成员字段(表示有多少个分片结构数据区)赋值;
第30--33行代码:因为分片结构数据区有两种数据,这里暂时叫做链表数据和数组数据(其实是两种不同的数据类型)。上面第21--27行代码是处理数组类型的数据共享问题;而现在这几行代码则是处理链表类型的数据。首先是判断链表指针是否为NULL,然后就是为新skb结构的分片结构指针赋值等。
第35行代码:是复制一些结构体成员变量。首先呢是调用_copy_skb_header(new,old)函数来为新skb自身结构成员拷贝赋值,然后是对其数据区的一些成员变量来赋值。最后还为一些常用的分片结构赋值。
pskb_copy()函数实现其实不难,主要是分配skb及数据区内存----》对数据区拷贝赋值----》处理分片结构数据区内存----》为其他成员变量拷贝赋值。下面是pskb_copy()函数的原理图:
其实上面的skb_clone()函数和pskb_copy()函数就像高富帅和屌丝男一样:skb_clone()函数就是那种富二代、官二代、星二代(当然了,虽然有些人不是什么二代,但人家有干爹),想要什么他老爸早在开始的时候就已经准备好了(skb_clone()函数是使用父skb内存申请时准备好的skb内存空间);而pskb_copy()函数就不一样了,什么东西都必须自己去挣、去争(pskb_copy()函数是自己去调用skb_alloc()函数来申请)。如果挣不到或者争不到,那没办法直接game over。但是唯一值得高兴的是,pskb_copy()函数申请内存时,其实是可以选择:alloc_skb_fclone()函数来申请的,可以让自己孩子成为某二代嘛(因为skb_clone()函数这个二代用的空间就是从alloc_skb_fclone()函数申请时返回的子skb),当然了,这个得自己去封装了。内核选择的还是低调做法,用alloc_skb()函数去申请,自给自足,不让自己孩子成为某二代。
skb_copy()函数:
上面的pskb_copy()函数和skb_clone()函数类似:skb_clone()函数克隆出来的skb结构不能修改其共享数据区的数据,而pskb_copy()函数也是一样的,克隆出来的skb及数据区不能修改共享的分片结构数据区内容。所以如果想要修改分片结构数据区的内容,则必须要用skb_copy()函数来克隆skb结构体。skb_copy()函数是对skb结构体真正的完全复制拷贝。不仅是sk_buff结构体还有data指针指向的数据区(包括分片结构)以及分片结构中指针指向的数据区,都各自复制拷贝一份。
skb_copy()函数实现:
struct sk_buff *skb_copy(const struct sk_buff *skb, gfp_t gfp_mask)函数关键代码分析:
{
struct sk_buff *n;
#ifdef NET_SKBUFF_DATA_USES_OFFSET
n = alloc_skb(skb->end + skb->data_len, gfp_mask);
#else
n = alloc_skb(skb->end - skb->head + skb->data_len, gfp_mask);
#endif
if (!n)
return NULL;
skb_reserve(n, headerlen);
skb_put(n, skb->len);
if (skb_copy_bits(skb, -headerlen, n->head, headerlen + skb->len))
BUG();
copy_skb_header(n, skb);
return n;
}
如果看了pskb_copy()函数的实现会发现:skb_copy()函数的实现和pskb_copy()函数的实现非常相似。可以看下pskb_copy()函数的关键代码分析,而第15行代码是调用了
skb_copy_bits(skb,-headerlen,n->head,headerlen + skb->len)函数来对函数区及分片结构的数据区进行拷贝赋值。看下面skb_copy()函数的原理图: