skb_buff的数据空间
相关操作函数
对skb_buff结构体的相关操作,并不是直接修改相关数据,内核提供了一系列的函数用来操作此结构体
skb_put
向后扩大数据区空间。headroom空间不变,tailroom空间降低,skb->data指针不变,skb->tail指针下移;
unsigned char *skb_put(struct sk_buff *skb, unsigned int len)
{
/* 获取当前skb->tail */
unsigned char *tmp = skb_tail_pointer(skb);
/* 要求skb数据区必须为线性 */
SKB_LINEAR_ASSERT(skb);
/* skb尾部增加len字节 */
skb->tail += len;
/* skb数据总长度增加len字节 */
skb->len += len;
/* 如果增加之后的tail > end ,则panic */
if (unlikely(skb->tail > skb->end))
skb_over_panic(skb, len, __builtin_return_address(0));
//返回添加数据的第一个字节位置
return tmp;
}
skb_push
向前扩大数据区空间。headroom空间降低,tailroom空间不变。skb->tail指针不变,skb->data指针上
移;
unsigned char *skb_push(struct sk_buff *skb, unsigned int len)
{
/* 数据区data指针前移len字节 */
skb->data -= len;
/* 数据总长度增加len字节 */
skb->len += len;
/* 添加数据长度溢出过header ,panic*/
if (unlikely(skb->data<skb->head))
skb_under_panic(skb, len, __builtin_return_address(0));
/* 返回新的data指针 */
return skb->data;
}
skb_pull
缩小数据区空间,headroom空间增大。tailroom空间不变,skb->data指针下移,skb->tail指针不变;
unsigned char *skb_pull(struct sk_buff *skb, unsigned int len)
{
return skb_pull_inline(skb, len);
}
skb_reserve
数据区不变,headroom空间增大,tailroom空间降低,skb->data和skb->tail同一时候下移;
static inline void skb_reserve(struct sk_buff *skb, int len)
{
/* 数据区data指针增加len字节*/
skb->data += len;
/* 数据区tail指针增加len字节 */
skb->tail += len;
}
skb_buff结构体相关
kfree_skb
释放结构体空间
void kfree_skb(struct sk_buff *skb)
{
if (unlikely(!skb))
return;
if (likely(atomic_read(&skb->users) == 1))
smp_rmb();
else if (likely(!atomic_dec_and_test(&skb->users)))
return;
trace_kfree_skb(skb, __builtin_return_address(0));
__kfree_skb(skb);
}
alloc_skb
该函数的作用是在上层协议要发送数据包的时候或网络设备准备接收数据包的时候会调用alloc_skb()函数分配sk_buff结构体
static inline struct sk_buff *alloc_skb(unsigned int size,
gfp_t priority)
{
return __alloc_skb(size, priority, 0, NUMA_NO_NODE);
}
该函数调用__alloc_skb函数,原型为:
struct sk_buff *__alloc_skb(unsigned int size, gfp_t gfp_mask,
int fclone, int node)
{
struct kmem_cache *cache;
struct skb_shared_info *shinfo;
struct sk_buff *skb;
u8 *data;
cache = fclone ? skbuff_fclone_cache : skbuff_head_cache;
/* Get the HEAD */
skb = kmem_cache_alloc_node(cache, gfp_mask & ~__GFP_DMA, node);//分配存储空间
if (!skb)
goto out;//分配失败,返回NULL
prefetchw(skb);
/* We do our best to align skb_shared_info on a separate cache
* line. It usually works because kmalloc(X > SMP_CACHE_BYTES) gives
* aligned memory blocks, unless SLUB/SLAB debug is enabled.
* Both skb->head and skb_shared_info are cache line aligned.
*/
size = SKB_DATA_ALIGN(size);//调整skb大小
size += SKB_DATA_ALIGN(sizeof(struct skb_shared_info));
data = kmalloc_node_track_caller(size, gfp_mask, node);//分配数据区
if (!data)
goto nodata;
/* kmalloc(size) might give us more room than requested.
* Put skb_shared_info exactly at the end of allocated zone,
* to allow max possible filling before reallocation.
*/
size = SKB_WITH_OVERHEAD(ksize(data));
prefetchw(data + size);
/*
* Only clear those fields we need to clear, not those that we will
* actually initialise below. Hence, don't put any more fields after
* the tail pointer in struct sk_buff!
*/
//sk_buff结构体中最后6个属性不能改变位置,只能在最后
memset(skb, 0, offsetof(struct sk_buff, tail));//将sk_buff结构体中tail属性之前的属性清零
/* Account for allocated memory : skb + skb->head */
skb->truesize = SKB_TRUESIZE(size);//计算缓冲区的尺寸
atomic_set(&skb->users, 1);
//初始化数据区的指针
skb->head = data;
skb->data = data;
skb_reset_tail_pointer(skb);
skb->end = skb->tail + size;
#ifdef NET_SKBUFF_DATA_USES_OFFSET
skb->mac_header = ~0U;
#endif
/* make sure we initialize shinfo sequentially */
//初始化skb_shared_info
shinfo = skb_shinfo(skb);
memset(shinfo, 0, offsetof(struct skb_shared_info, dataref));
atomic_set(&shinfo->dataref, 1);
kmemcheck_annotate_variable(shinfo->destructor_arg);
if (fclone) {
struct sk_buff *child = skb + 1;
atomic_t *fclone_ref = (atomic_t *) (child + 1);
kmemcheck_annotate_bitfield(child, flags1);
kmemcheck_annotate_bitfield(child, flags2);
skb->fclone = SKB_FCLONE_ORIG;
atomic_set(fclone_ref, 1);
child->fclone = SKB_FCLONE_UNAVAILABLE;
}
out:
return skb;
nodata:
kmem_cache_free(cache, skb);
skb = NULL;
goto out;
}
LINUX 中 SKB 的分配最终是由函数 :struct sk_buff *__alloc_skb(unsigned int size, gfp_t gfp_mask, int fclone, int node) 来完成.
SKB 可以分为 SKB 描述符与 SKB 数据区两个部分,其中描述符必须从 CACHE 中来分配 : 或者从skbuff_fclone_cache 中分配,或者从 skbuff_head_cache 中来分配.
如果从分配描述符失败,则直接反回 NULL,表示 SKB 分配失败.
SKB 描述符分配成功后,即可分配数据区.
在具体分配数据区之前首先要对数据区的长度进行 ALIGN 操作, 通过宏 SKB_DATA_ALIGN 来重新确定 size 大小. 然后调用 kmalloc 函数分配数据区 :
data = kmalloc(size + sizeof(struct skb_shared_info), gfp_mask);
需要注意的是数据区的大小是 SIZE 的大小加上 skb_shared_info 结构的大小.
数据区分配成功后,便对 SKB 描述符进行与此数据区相关的赋值操作 :
memset(skb, 0, offsetof(struct sk_buff, truesize));
skb->truesize = size + sizeof(struct sk_buff);
atomic_set(&skb->users, 1);
skb->head = data;
skb->data = data;
skb->tail = data;
skb->end = data + size;
需要主意的是, SKB 的 truesize 的大小并不包含 skb_shared_info 结构的大小. 另外,skb 的 end 成员指针也就事 skb_shared_info 结构的起始指针,系统用
一个宏 : skb_shinfo 来完成寻找 skb_shared_info 结构指针的操作.
最后,系统初始化 skb_shared_info 结构的成员变量 :
atomic_set(&(skb_shinfo(skb)->dataref), 1);
skb_shinfo(skb)->nr_frags = 0;
skb_shinfo(skb)->tso_size = 0;
skb_shinfo(skb)->tso_segs = 0;
skb_shinfo(skb)->frag_list = NULL;
skb_shinfo(skb)->ufo_size = 0;
skb_shinfo(skb)->ip6_frag_id = 0;
最后,返回 SKB 的指针.
内核中还有一些其他操作skb_buff结构体的函数,这里简单介绍了几个主要的,其他的如有使用到再回来补充