在内核中有许多很短简单的函数来操作sk_buff结构,在linux/skbuff.h和net/core/skbuff.c源文件中,几乎所有函数都有两个版本,类似do_something和__do_something。通常来讲,第一种是封装函数,增加了一些额外的参数合理性检查或在调用第二种函数前加入上锁机制。skb_put:在tail偏移后面扩展n个字节的空间,但不会超过end偏移的限制空间。返回扩展空间的第一个字节指针
skb_push:在data指针前面扩展n个字节的空间,但不能超过head指针限制。返回新的data指针
skb_pull:将data指针后移n个字节,将来再使用skb_push操作就会将这n个字节的空间内容覆盖,返回新的data指针
skb_reserve:在缓冲区头部保留n个字节的空间,这个操作只允许对空的缓冲区进行操作
分配内存
定义在skbuff.c源文件猪的alloc_skb是分配缓冲区的主要函数。数据缓冲区和sk_buff结构是两个不同的实例,建议一个缓冲区会涉及两次内存分配,分配数据缓冲区和分配sk_buff结构。
alloc_skb通过调研kmem_cache_alloc函数从一个缓存中取得一个sk_buff结构,然后调研kmalloc分配一个数据缓冲区:
在调用kmalloc前,size参数会被SKB_DATA_ALIGN宏进行调整强制对齐。alloc_skb函数会对sk_buff结构中的元素进行参数初始化,其中几个参数的值如下图:
在数据缓冲区的底端的skb_shared_info数据结构主要用于处理一些IP分片。
dev_alloc_skb是由设备驱动程序使用的缓冲区分配函数,应该是在中断模式中执行。此函数是alloc_skb的一个封装函数,为了优化在申请大小上加了16个字节。而且由于此函数是由中断事件处理函数调用的,所以会要求原子操作(GFP_ATOMIC):
- struct sk_buff *dev_alloc_skb(unsigned int length)
- {
- /*
- * There is more code here than it seems:
- * __dev_alloc_skb is an inline
- */
- return __dev_alloc_skb(length, GFP_ATOMIC);
- }
- static inline struct sk_buff *__dev_alloc_skb(unsigned int length, gfp_t gfp_mask)
- {
- struct sk_buff *skb = alloc_skb(length + NET_SKB_PAD, gfp_mask);
- if (likely(skb))
- skb_reserve(skb, NET_SKB_PAD);
- return skb;
- }
释放内存kfree_skb和dev_kfree_skb函数是一样的,都是在skb的引用计数为1后,再调用__skb_free函数释放data数据缓冲区和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);
- }
数据预留和对齐
skb_reserve会在缓冲区的头部预留一些空间,通常允许插入一个报头或者强迫数据对齐到某个边界。此函数会同时移动data和tail指针,所以这个函数一般在分配完数据缓冲区后立即调用。
在以太网卡驱动程序drivers/net/3c59x.c源文件中,vortex_rx函数在将任何数据存储到刚分配的缓冲区之前,都会调用下面的命令:
skb_reserve(skb, 2); /* Align IP on 16 byte boundaries */
这样,在将Ethernet帧拷贝到数据缓冲区中后,IP报头将从缓冲区开始按16字节边界对齐。
发送数据时,使用skb_reserve为下层网络保留报头空间的过程:
a.当TCP被请求发送数据时,会根据一些准则如TCP MSS(Max Segment Size),支持分散-聚集IO(Scatter-gather IO)等分配一个缓冲区;
b.TCP会在缓冲区头部调用skb_reserve保留足够的空间,以容纳所有层TCP、IP、链路层的报头。参数MAX_TCP_HEADER是所有层报头的总和,并且要考虑最坏的情况,所以会为每个层预留最大可能的报头。
c.将TCP有效载荷拷贝到缓冲区。TCP有效载荷可能以不同的方式组织,如可以作为多个片段来存储。
d.TCP添加报头
e.TCP层将缓冲区传给IP层,IP层也同样添加报头
f.IP层将IP包传递给链路层,链路层增加链路层帧头。
当缓冲区往下传播经过网络协议栈时,每个协议会将skb->data往下传,并将其报头拷贝进来,然后更新skb->len。
skb_push将一个数据块添加到缓冲区开端,skb_put将一个数据块添加到尾端,这些函数都没有真正将数据添加进缓冲区,只是简单地移动指向头和尾的指针,需要其他函数将数据复制进来。skb_pull将data指针后移,将一个数据块从缓冲区头部删除。
skb_shared_info数据结构
在数据缓冲区尾端的skb_shared_info数据结构,来保持此数据块的附加信息。这个数据结构在标记缓冲区尾部的end指针之后。
- /* This data is invariant across clones and lives at
- * the end of the header data, ie. at skb->end.
- */
- struct skb_shared_info {
- unsigned short nr_frags;
- unsigned short gso_size;
- /* Warning: this field is not always filled in (UFO)! */
- unsigned short gso_segs;
- unsigned short gso_type;
- __be32 ip6_frag_id;
- __u8 tx_flags;
- struct sk_buff *frag_list;
- struct skb_shared_hwtstamps hwtstamps;
- /*
- * Warning : all fields before dataref are cleared in __alloc_skb()
- */
- atomic_t dataref;
- /* Intermediate layers must ensure that destructor_arg
- * remains valid until skb destructor */
- void * destructor_arg;
- /* must be last field, see pskb_expand_head() */
- skb_frag_t frags[MAX_SKB_FRAGS];
- };
其中dataref代表数据块的用户数,在缓冲区的克隆和拷贝中会用到。nr_frags、frag_list、frags用于处理IP片段。skb_is_nonlinear函数用于检查缓冲区是否为片段,而skb_linearize用于将几个片段压合为一个缓冲区,将引发拷贝从而使性能下降。sk_buff结构中没有指向skb_shared_info结构的字段,需要通过end指针来访问:
- #define skb_shinfo(SKB) ((struct skb_shared_info *)(skb_end_pointer(SKB)))
阅读(9101) | 评论(0) | 转发(2) |