套接字缓存之skb_put、skb_push、skb_pull、skb_reserve
skb操作中的预留和对齐操作主要由skb_put、skb_push、skb_pull、skb_reserve完成;这几个函数的区别通过下面图(图片来自:深入理解linux网络技术内幕)可以清晰的区分;另外,需要注意的是skb_reserve只能操作空skb,即在分配了空间,尚未填充数据时调用;
以下为四个函数的源码分析;
1 /** 2 * skb_put - add data to a buffer 3 * @skb: buffer to use 4 * @len: amount of data to add 5 * 6 * This function extends the used data area of the buffer. If this would 7 * exceed the total buffer size the kernel will panic. A pointer to the 8 * first byte of the extra data is returned. 9 */ 10 /* 11 向skb尾部添加数据 12 */ 13 unsigned char *skb_put(struct sk_buff *skb, unsigned int len) 14 { 15 /* 获取当前skb->tail */ 16 unsigned char *tmp = skb_tail_pointer(skb); 17 18 /* 要求skb数据区必须为线性 */ 19 SKB_LINEAR_ASSERT(skb); 20 21 /* skb尾部增加len字节 */ 22 skb->tail += len; 23 /* skb数据总长度增加len字节 */ 24 skb->len += len; 25 26 /* 如果增加之后的tail > end ,则panic */ 27 if (unlikely(skb->tail > skb->end)) 28 skb_over_panic(skb, len, __builtin_return_address(0)); 29 30 //返回添加数据的第一个字节位置 31 return tmp; 32 }
1 /** 2 * skb_push - add data to the start of a buffer 3 * @skb: buffer to use 4 * @len: amount of data to add 5 * 6 * This function extends the used data area of the buffer at the buffer 7 * start. If this would exceed the total buffer headroom the kernel will 8 * panic. A pointer to the first byte of the extra data is returned. 9 */ 10 /* 11 向skb数据区头部添加数据 12 */ 13 unsigned char *skb_push(struct sk_buff *skb, unsigned int len) 14 { 15 /* 数据区data指针前移len字节 */ 16 skb->data -= len; 17 /* 数据总长度增加len字节 */ 18 skb->len += len; 19 20 /* 添加数据长度溢出过header ,panic*/ 21 if (unlikely(skb->data<skb->head)) 22 skb_under_panic(skb, len, __builtin_return_address(0)); 23 24 /* 返回新的data指针 */ 25 return skb->data; 26 }
1 /** 2 * skb_pull - remove data from the start of a buffer 3 * @skb: buffer to use 4 * @len: amount of data to remove 5 * 6 * This function removes data from the start of a buffer, returning 7 * the memory to the headroom. A pointer to the next data in the buffer 8 * is returned. Once the data has been pulled future pushes will overwrite 9 * the old data. 10 */ 11 /* 12 从数据区头部移除数据 13 */ 14 unsigned char *skb_pull(struct sk_buff *skb, unsigned int len) 15 { 16 return skb_pull_inline(skb, len); 17 }
1 /* 根据移除数据长度判断函数调用 */ 2 static inline unsigned char *skb_pull_inline(struct sk_buff *skb, unsigned int len) 3 { 4 /* 5 移除长度> skb数据总长度,返回NULL 6 否则,继续调用_skb_pull函数 7 */ 8 return unlikely(len > skb->len) ? NULL : __skb_pull(skb, len); 9 }
1 /* 从skb数据区头部移除数据 */ 2 static inline unsigned char *__skb_pull(struct sk_buff *skb, unsigned int len) 3 { 4 /* 数据总长度减去len字节 */ 5 skb->len -= len; 6 /* 数据总长度是否有异常 */ 7 BUG_ON(skb->len < skb->data_len); 8 9 /* 10 data指针移动len字节 11 返回移除之后新的data指针 12 */ 13 return skb->data += len; 14 }
1 /* 2 保留头部空间,只能对空的skb使用 3 */ 4 static inline void skb_reserve(struct sk_buff *skb, int len) 5 { 6 /* 数据区data指针增加len字节*/ 7 skb->data += len; 8 /* 数据区tail指针增加len字节 */ 9 skb->tail += len; 10 }
https://www.cnblogs.com/wanpengcoder/p/7529512.html
kfree_skb()只在skb->users为1的情况下才释放内存,否则只简单地递减skb->users,因此假设SKB有三个引用者,那么只有第三次调用dev_kfree_skb()或kfree_skb()时才释放内存。
dev_kfree_skb()只是一个简单调用kfree_skb()的宏。
__kfree_skb绕过了对skb引用计数的判断,一般来说,在内核中函数名前面加“__”的都提示要小心使用,也就是它略去了一些检查,于是在调用这类函数之前要做检查。
kfree()调用了slab_free()并不会释放skb的线性区及分片等。
kfree_skb()释放一个SKB的步骤:
1)kfree_skb()检测sk_buff结构的引用计数users,如果不为1,则说明此次释放后该SKB还将被用户占用,
因此递减引用计数users后即返回;否则说明不再有其他用户占用该sk_buff结构,调用__kfree_skb()释放之。
2)SKB描述符中包含一个dst_entry结构的引用,在释放SKB后,会调用dst_release()来递减dst_entry结构的引用计数。
3)如果初始化了SKB的析构函数,则调用相应的函数。
4)一个SKB描述符是与一个存有真正数据的内存块,即数据区相关的。如果存在聚合分散I/O数据,
该数据区底部的skb_shared_info结构还会包含指向聚合分散I/O数据的指针,同样需要释放这些分片所占用的内存。
alloc_skb--分配skb
dev_alloc_skb--分配skb,通常被设备驱动用在中断上下文中,它是alloc_skb的封装函数,因为在中断处理函数中被调用,因此要求原子操作(GFP_ATOMIC)
kfree_skb--减少skb引用,为0则释放,用于出错丢包时释放skb使用;
dev_kfree_skb==consume_skb--减少skb引用,为0则释放,成功状态下释放skb使用;
---free系列---
1 /** 2 * kfree_skb - free an sk_buff 3 * @skb: buffer to free 4 * 5 * Drop a reference to the buffer and free it if the usage count has 6 * hit zero. 7 */ 8 /* 9 释放skb 10 */ 11 void kfree_skb(struct sk_buff *skb) 12 { 13 if (unlikely(!skb)) 14 return; 15 /* 引用为1,可直接释放 */ 16 if (likely(atomic_read(&skb->users) == 1)) 17 smp_rmb(); 18 /* 19 对引用减1,并且判断,如果结果不为0 20 说明还有引用,返回 21 */ 22 else if (likely(!atomic_dec_and_test(&skb->users))) 23 return; 24 trace_kfree_skb(skb, __builtin_return_address(0)); 25 26 //真正的skb释放 27 __kfree_skb(skb); 28 }
1 /** 2 * __kfree_skb - private function 3 * @skb: buffer 4 * 5 * Free an sk_buff. Release anything attached to the buffer. 6 * Clean the state. This is an internal helper function. Users should 7 * always call kfree_skb 8 */ 9 /* 释放skb */ 10 void __kfree_skb(struct sk_buff *skb) 11 { 12 /* 释放skb附带的所有数据 */ 13 skb_release_all(skb); 14 /* 释放skb */ 15 kfree_skbmem(skb); 16 }
1 #define dev_kfree_skb(a) consume_skb(a)
1 /** 2 * consume_skb - free an skbuff 3 * @skb: buffer to free 4 * 5 * Drop a ref to the buffer and free it if the usage count has hit zero 6 * Functions identically to kfree_skb, but kfree_skb assumes that the frame 7 * is being dropped after a failure and notes that 8 */ 9 /* 释放skb,与kfree_skb区别是,kfree_skb用于失败时丢包释放 */ 10 void consume_skb(struct sk_buff *skb) 11 { 12 if (unlikely(!skb)) 13 return; 14 if (likely(atomic_read(&skb->users) == 1)) 15 smp_rmb(); 16 else if (likely(!atomic_dec_and_test(&skb->users))) 17 return; 18 trace_consume_skb(skb); 19 __kfree_skb(skb); 20 }
转载于:https://www.cnblogs.com/wanpengcoder/p/7529576.html
中断处理的有趣部分处理"发送结束"的情况. 在这个情况下, 统计量被更新, 调用 dev_kfree_skb 来返回 socket 缓存给系统. 实际上, 有这个函数的 3 个变体可以调用:
dev_kfree_skb(struct sk_buff *skb);
这个版本应当在你知道你的代码不会在中断上下文中运行时调用. 因为 snull 没有实际的硬件中断, 我们使用这个版本.
dev_kfree_skb_irq(struct sk_buff *skb);
如果你知道会在中断处理中释放缓存, 使用这个版本, 它对这个情况做了优化.
dev_kfree_skb_any(struct sk_buff *skb);
如果相关代码可能在中断或非中断上下文运行时, 使用这个版本.
https://blog.youkuaiyun.com/mounter625/article/details/55253454
dev_kfree_skb_any是一个释放skb的封装函数,它根据是否在irq环境下,决定直接释放skb还是等softirq去释放skb
void dev_kfree_skb_any(struct sk_buff *skb)
{
if (in_irq() || irqs_disabled())
dev_kfree_skb_irq(skb); //将skb放在softnet_data->completion_queue的首部,等待softirq释放。
else
dev_kfree_skb(skb); // 直接释放
}
EXPORT_SYMBOL(dev_kfree_skb_any);
来看一下dev_kfree_skb_irq,它的功能其实就是将skb放入softnet_data的completion_queue队列头部
void dev_kfree_skb_irq(struct sk_buff *skb)
{
if (atomic_dec_and_test(&skb->users)) {
struct softnet_data *sd;
unsigned long flags;
local_irq_save(flags);
sd = &__get_cpu_var(softnet_data);
skb->next = sd->completion_queue; // skb放在softnet_data->completion_queue的首部
sd->completion_queue = skb;
// 触发softirq, NET_TX_SOFTIRQ 软中断处理函数net_tx_action处理的第一件事情就是遍历completion_queue
// 然后释放skb
raise_softirq_irqoff(NET_TX_SOFTIRQ);
local_irq_restore(flags);
}
}
EXPORT_SYMBOL(dev_kfree_skb_irq);
http://blog.chinaunix.net/uid-26902809-id-4106241.html?utm_source=jiancool