本文分析基于Linux Kernel 3.2.1
原创作品,转载请标明http://blog.csdn.net/yming0221/article/details/7972647
更多请查看网络栈分析专栏http://blog.csdn.net/column/details/linux-kernel-net.html
作者:闫明
1、alloc_skb()函数
该函数的作用是在上层协议要发送数据包的时候或网络设备准备接收数据包的时候会调用alloc_skb()函数分配sk_buff结构体,需要释放时调用kfree_skb()函数。
- static inline struct sk_buff *alloc_skb(unsigned int size,
- gfp_t priority)
- {
- return __alloc_skb(size, priority, 0, NUMA_NO_NODE);
- }
这里使用内联函数,非内联函数调用会进堆栈的切换,造成额外的开销,而内联函数可以解决这一点,可以提高执行效率,只是增加了程序的空间开销。
函数调用需要时间和空间开销,调用函数实际上将程序执行流程转移到被调函数中,被调函数的代码执行完后,再返回到调用的地方。这种调用操作要求调用前保护好现场并记忆执行的地址,返回后恢复现场,并按原来保存的地址继续执行。对于较长的函数这种开销可以忽略不计,但对于一些函数体代码很短,又被频繁调用的函数,就不能忽视这种开销。引入内联函数正是为了解决这个问题,提高程序的运行效率。
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
- 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;
-
-
- skb = kmem_cache_alloc_node(cache, gfp_mask & ~__GFP_DMA, node);
- if (!skb)
- goto out;
- prefetchw(skb);
-
-
-
-
-
-
- size = SKB_DATA_ALIGN(size);
- size += SKB_DATA_ALIGN(sizeof(struct skb_shared_info));
- data = kmalloc_node_track_caller(size, gfp_mask, node);
- if (!data)
- goto nodata;
-
-
-
-
- size = SKB_WITH_OVERHEAD(ksize(data));
- prefetchw(data + size);
-
-
-
-
-
-
-
- memset(skb, 0, offsetof(struct sk_buff, tail));
-
- 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
-
-
-
- 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;
- }
函数执行完成后,sk_buff的数据指针的形式如下:
2、kfree_skb()函数
该函数就是释放不被使用的sk_buff结构
-
-
-
-
-
-
-
- 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);
- }
再调用__kfree_skb函数
- void __kfree_skb(struct sk_buff *skb)
- {
- skb_release_all(skb);
- kfree_skbmem(skb);
- }
这里不再向深层函数探究,以后再说。
3、skb_put()函数
该函数是在数据区的末端添加某协议的尾部
-
-
-
-
-
-
-
-
-
- unsigned char *skb_put(struct sk_buff *skb, unsigned int len)
- {
- unsigned char *tmp = skb_tail_pointer(skb);
- SKB_LINEAR_ASSERT(skb);
- skb->tail += len;
- skb->len += len;
- if (unlikely(skb->tail > skb->end))
- skb_over_panic(skb, len, __builtin_return_address(0));
- return tmp;
- }
执行前后的示意图如下:
4、skb_push()函数
该函数的作用是在数据区的前端添加某协议的头部,和skb_put类似。
只不过这里移动的数据指针的是data前移len个单位。
- unsigned char *skb_push(struct sk_buff *skb, unsigned int len)
- {
- skb->data -= len;
- skb->len += len;
- if (unlikely(skb->data<skb->head))
- skb_under_panic(skb, len, __builtin_return_address(0));
- return skb->data;
- }
5、skb_pull和skb_trim函数正好和上面两个函数的功能相反,是去掉相应的部分,不再赘述。
6、skb_reverse()函数
该函数的作用是在数据区创建存储协议头部的空间,函数实现很简单。
- static inline void skb_reserve(struct sk_buff *skb, int len)
- {
- skb->data += len;
- skb->tail += len;
- }
7、sk_buff缓冲区链表的操作函数
skb_orphan()函数是将一个缓冲结构体变成孤立的skb
- static inline void skb_orphan(struct sk_buff *skb)
- {
- if (skb->destructor)
- skb->destructor(skb);
- skb->destructor = NULL;
- skb->sk = NULL;
- }
skb_queue_head_init()函数将初始化sk_buff_head结构体
- static inline void skb_queue_head_init(struct sk_buff_head *list)
- {
- spin_lock_init(&list->lock);
- __skb_queue_head_init(list);
- }
skb_queue_head()在链表头添加一个sk_buff结构
- void skb_queue_head(struct sk_buff_head *list, struct sk_buff *newsk)
- {
- unsigned long flags;
-
- spin_lock_irqsave(&list->lock, flags);
- __skb_queue_head(list, newsk);
- spin_unlock_irqrestore(&list->lock, flags);
- }
调用__skb_queue_head()函数实现功能
- static inline void __skb_queue_head(struct sk_buff_head *list,
- struct sk_buff *newsk)
- {
- __skb_queue_after(list, (struct sk_buff *)list, newsk);
- }
- static inline void __skb_queue_after(struct sk_buff_head *list,
- struct sk_buff *prev,
- struct sk_buff *newsk)
- {
- __skb_insert(newsk, prev, prev->next, list);
- }
最后用函数__skb_insert操作双链表
- static inline void __skb_insert(struct sk_buff *newsk,
- struct sk_buff *prev, struct sk_buff *next,
- struct sk_buff_head *list)
- {
- newsk->next = next;
- newsk->prev = prev;
- next->prev = prev->next = newsk;
- list->qlen++;
- }
函数skb_queue_tail() 在缓冲区链表尾部添加新的skb
函数skb_dequeue() 在链表头部移走一个skb
函数skb_dequeue_tail() 在链表尾部移走一个skb
函数skb_queue_purge() 清空一个由sk_buff_head管理的缓冲区链表,具体操作如下:
- void skb_queue_purge(struct sk_buff_head *list)
- {
- struct sk_buff *skb;
- while ((skb = skb_dequeue(list)) != NULL)
- kfree_skb(skb);
- }
函数skb_append() 在指定的skb后附加一个缓冲区,最终还是调用__skb_insert()函数完成的链表操作