sk_buff
概述
sk_buff在内部是以链表的形式整体组织的,如下图所示:

如下图所示,解释了skb_buff的一些重要的数据成员的含义

skb空间分布
alloc_skb函数
skb = alloc_skb(length, GFP_KERNEL);

如图所示:该函数会分配
NET_SKB_PAD + length + sizeof(struct skb_shared_info)长的空间,并把head、data、tail指向buff的头部,注意:NET_SKB_PAD是为了对齐用length是实际可以使用的空间sizeof(struct skb_shared_info):只是分配了这个结构体大小
参考源码如下:
static inline struct sk_buff *dev_alloc_skb(unsigned int length) { size = NET_SKB_PAD + length; /* 分配skb 结构体的内存*/ skb = kmem_cache_alloc_node(cache, gfp_mask & ~__GFP_DMA, node); /* 将 size 和 L1 Cache Line 长度对其 */ size = SKB_DATA_ALIGN(size); /* size 加上 sizeof(struct skb_shared_info),并与 L1 Cache Line 长度对其*/ size += SKB_DATA_ALIGN(sizeof(struct skb_shared_info)); /* 申请 skb 指向的 data buffer 空间 */ data = kmalloc_reserve(size, gfp_mask, node, &pfmemalloc); skb->head = data; skb->data = data; skb_reset_tail_pointer(skb); skb->end = skb->tail + size; }
执行完该函数后,该sk_buff的总长度就固定下来了,能存放数据的长度为length
skb_reserve函数
skb_reserve(skb, Head_len)

如图所示:此时会把data、tail指针往下移动Head_len长度,留出来headroom给协议栈用来存放各种协议的头。
参考源码:
static inline void skb_reserve(struct sk_buff *skb, int len) { skb->data += len; skb->tail += len; }
skb_put函数
skb_put(skb,data_len)
skb_put 负责向skb 指向的buffer 中添加数据。如下图,在用户将数据memcpy( skb->data, data_len) 到 data 指针开始的位置时,然后调用 skb_put 移动 tail 指针到 skb->data + data_len位置

参见源码:
/** * skb_put - add data to a buffer * @skb: buffer to use * @len: amount of data to add * * This function extends the used data area of the buffer. If this would * exceed the total buffer size the kernel will panic. A pointer to the * first byte of the extra data is returned. */ void *skb_put(struct sk_buff *skb, unsigned int len) { void *tmp = skb_tail_pointer(skb); SKB_LINEAR_ASSERT(skb); skb->tail += len; skb->len += len; /* 返回原始的tail指针 */ return tmp; }
skb_push函数
skb_push(skb,udp/tcp_len)
skb_push 在 skb->data 指针前面继续添加数据。例如在协议栈TX数据包时,需要添加UDP或者TCP等协议头部信息,则会调用 skb_push 将 skb->data 指针向上移动udp/tcp_len 长度,减少的是headroom的空间

当然,后面可能还会继续添加ip协议头和二层协议头如下:

参考源码如下:
/** * skb_push - add data to the start of a buffer * @skb: buffer to use * @len: amount of data to add * * This function extends the used data area of the buffer at the buffer * start. If this would exceed the total buffer headroom the kernel will * panic. A pointer to the first byte of the extra data is returned. */ void *skb_push(struct sk_buff *skb, unsigned int len) { skb->data -= len; skb->len += len; return skb->data; }
skb_pull函数
skb_pull(skb,L2_len )
skb_pull 从skb->data 指向的位置向下移动,类似于 pop 出栈。如下图,将二层协议头 pop 掉,则skb->data 回到IP协议头 时的位置。这主要被协议栈RX收包时,去除协议头时使用。

举例说明
/* 分配 skb 总长度为length */ skb = alloc_skb(length, GFP_KERNEL); /* 设置保留空间 给协议栈的头部使用 */ skb_reserve(skb, header_len); /* skb put 将tail+user_data_len,返回原始tail指针 给 data*/ unsigned char *data = skb_put(skb, user_data_len); /* 将用户态user_pointer的数据 copy 到 data */ skb->csum = csum_and_copy_from_user(user_pointer, data, user_data_len, 0, &err); struct udphdr *uh; /* 将skb data 指针向上移动 udphdr size,然后赋值 udp head*/ skb->h.raw = skb_push(skb, sizeof(struct udphdr)); uh = skb->h.uh uh->source = fl->fl_ip_sport; uh->dest = fl->fl_ip_dport; uh->len = htons(user_data_len); uh->check = 0;