skbuff详细分析.docx
- 文档编号:17666210
- 上传时间:2023-07-27
- 格式:DOCX
- 页数:13
- 大小:21.27KB
skbuff详细分析.docx
《skbuff详细分析.docx》由会员分享,可在线阅读,更多相关《skbuff详细分析.docx(13页珍藏版)》请在冰点文库上搜索。
skbuff详细分析
SK_BUFF详细分析
1.定义
Packet:
通过网卡收发的报文,包括链路层、网络层、传输层的协议头和携带的应用数据。
DataBuffer:
用于存储packet的内存空间。
SKB:
structsk_buffer的简写。
2.概述
Structsk_buffer是linuxTCP/IP协议栈中,用于管理DataBuffer的结构。
Sk_buffer在数据包的发送和接收中起着重要的作用。
为了提高网络处理的性能,应尽量避免数据包的拷贝。
Linux内核开发者们在设计sk_buffer结构的时候,充分考虑到这一点。
目前Linux协议栈在接收数据的时候,需要拷贝两次:
数据包进入网卡驱动后拷贝一次,从内核空间递交给用户空间的应用时再拷贝一次。
Sk_buffer结构随着内核版本的升级,也一直在改进。
学习和理解sk_buffer结构,不仅有助于更好的理解内核代码,而且也可以从中学到一些设计技巧。
3.Sk_buffer定义
structsk_buff{
structsk_buff*next;
structsk_buff*prev;
structsock*sk;
structskb_timevaltstamp;
structnet_device*dev;
structnet_device*input_dev;
//structnet_device*curlayer_input_dev;
//structnet_device*l2tp_input_dev;
union{
structtcphdr*th;
structudphdr*uh;
structicmphdr*icmph;
structigmphdr*igmph;
structiphdr*ipiph;
structipv6hdr*ipv6h;
unsignedchar*raw;
}h;
union{
structiphdr*iph;
structipv6hdr*ipv6h;
structarphdr*arph;
unsignedchar*raw;
}nh;
union{
unsignedchar*raw;
}mac;
structdst_entry*dst;
structsec_path*sp;
charcb[40];
unsignedintlen,
data_len,
mac_len,
csum;
__u32priority;
__u8local_df:
1,
cloned:
1,
ip_summed:
2,
nohdr:
1,
nfctinfo:
3;
__u8pkt_type:
3,
fclone:
2;
__be16protocol;
void(*destructor)(structsk_buff*skb);
/*Theseelementsmustbeattheend,seealloc_skb()fordetails.*/
unsignedinttruesize;
atomic_tusers;
unsignedchar*head,
*data,
*tail,
*end;
};
4.成员变量
structskb_timevaltstamp;
此变量用于记录packet的到达时间或发送时间。
由于计算时间有一定开销,因此只在必要时才使用此变量。
需要记录时间时,调用net_enable_timestamp(),不需要时,调用net_disable_timestamp()。
tstamp主要用于包过滤,也用于实现一些特定的socket选项,一些netfilter的模块也要用到这个域。
structnet_device*dev;structnet_device*input_dev;
这几个变量都用于跟踪与packet相关的device。
由于packet在接收的过程中,可能会经过多个virtualdriver处理,因此需要几个变量。
接收数据包的时候,dev和input_dev都指向最初的interface,此后,如果需要被virtualdriver处理,那么dev会发生变化,而input_dev始终不变。
charcb[40];
此数组作为SKB的控制块,具体的协议可用它来做一些私有用途,例如TCP用这个控制块保存序列号和重传状态。
unsignedintlen,data_len,mac_len,csum;
‘len’表示此SKB管理的DataBuffer中数据的总长度;通常,DataBuffer只是一个简单的线性buffer,这时候len就是线性buffer中的数据长度;但在有‘pageddata’情况下,DataBuffer不仅包括第一个线性buffer,还包括多个pagebuffer;这种情况下,‘data_len’指的是pagebuffer中数据的长度,’len’指的是线性buffer加上pagebuffer的长度;len–data_len就是线性buffer的长度。
‘mac_len’指MAC头的长度。
目前,它只在IPSec解封装的时候被使用。
将来可能从SKB结构中去掉。
‘csum’保存packet的校验和。
__u32priority;
“priority”用于实现QoS,它的值可能取之于IPv4头中的TOS域。
TrafficControl模块需要根据这个域来对packet进行分类,以决定调度策略。
__u8local_df:
1,cloned:
1,ip_summed:
2,nohdr:
1,nfctinfo:
3;
为了能迅速的引用一个SKB的数据,当clone一个已存在的SKB时,会产生一个新的SKB,但是这个SKB会共享已有SKB的数据区。
当一个SKB被clone后,原来的SKB和新的SKB结构中,”cloned”都要被设置为1。
void(*destructor)(structsk_buff*skb);
unsignedinttruesize;
一个SKB所消耗的内存包括SKB本身和databuffer。
truesize就是databuffer的空间加上SKB的大小。
structsock结构中,有两个域,用于统计用于发送的内存空间和用于接收的内存空间,它们是:
rmem_alloc、wmem_alloc。
另外两个域则统计接收到的数据包的总大小和发送的数据包的总大小。
它们是:
rcvbuf和sndbuf。
rmem_alloc和rcvbuf,wmem_alloc和sndbuf用于不同的目的。
当我们收到一个数据包后,需要统计这个socket总共消耗的内存,这是通过skb_set_owner_r()来做的。
staticinlinevoidskb_set_owner_r(structsk_buff*skb,structsock*sk)
{
skb->sk=sk;
skb->destructor=sock_rfree;
atomic_add(skb->truesize,&sk->sk_rmem_alloc);
}
最后,当释放一个SKB后,需要调用skb->destruction()来减少rmem_alloc的值。
同样,在发送一个SKB的时候,需要调用skb_set_owner_w(),
staticinlinevoidskb_set_owner_w(structsk_buff*skb,structsock*sk)
{
sock_hold(sk);
skb->sk=sk;
skb->destructor=sock_wfree;
atomic_add(skb->truesize,&sk->sk_wmem_alloc);
}
在释放这样的一个SKB的时候,需要调用sock_free()
voidsock_wfree(structsk_buff*skb)
{
structsock*sk=skb->sk;
/*Incaseitmightbewaitingformorememory.*/
atomic_sub(skb->truesize,&sk->sk_wmem_alloc);
if(!
sock_flag(sk,SOCK_USE_WRITE_QUEUE))
sk->sk_write_space(sk);
sock_put(sk);
}
(Anothersubtleissueisworthpointingouthere.Forreceivebufferaccounting,wedonotgrabareferencetothesocket(via'sock_hold()'),becausethesockethandlingcodewillalwaysmakesuretofreeupanypacketsinit'sreceivequeuebeforeallowingthesockettobedestroyed.Whereasforsendpackets,wehavetodoproperaccountingwith'sock_hold()'and'sock_put()'.Sendpacketscanbefreedasynchronouslyatanypointintime.Forexample,apacketcouldsitinadevicestransmitqueueforalongtimeundercertainconditions.If,meanwhile,thesocketisclosed,wehavetokeepthesocketreferencearounduntilSKBsreferencingthatsocketareliberated.)
unsignedchar*head,*data,*tail,*end;
SKB对DataBuffer的巧妙管理,就是靠这四个指针实现的。
下图展示了这四个指针是如何管理数据buffer的:
Head指向buffer的开始,end指向buffer结束。
Data指向实际数据的开始,tail指向实际数据的结束。
这四个指针将整个buffer分成三个区:
Packetdata:
这个空间保存的是真正的数据;
Headroom:
处于packetdata之上的空间,是一个空闲区域;
Tailroom:
处于packetdata之下的空间,也是空闲区域。
由于TCP/IP协议族是一种分层的协议,传输层、网络层、链路层,都有自己的协议头,因此TCP/IP协议栈对于数据包的处理是比较复杂的。
为了提高处理效率,避免数据移动、拷贝,sk_buffer在对数据buffer管理的时候,在packetdata之上和之下,都预留了空间。
如果需要增加协议头,只需要从headroom中拿出一块空间即可,而如果需要增加数据,则可以从tailroom中获得空间。
这样,整个内存只分配一次空间,此后协议的处理,只需要挪动指针。
5.Sk_buffer对内存的管理
我们以构造一个用于发送的数据包的过程,来理解sk_buffer是如何管理内存的。
5.1.构造Skb_buffer
alloc_skb()用于构造skb_buffer,它需要一个参数,指定了存放packet的空间的大小。
构造时,不仅需要创建skb_buffer结构本身,还需要分配空间用于保存packet。
skb=alloc_skb(len,GFP_KERNEL);
head,data,tail指向buffer开始,end指向buffer结束,整个buffer都被当作tailroom。
Sk_buffer当前的数据长度是0。
5.2.为protocolheader留出空间
通常,当构造一个用于发送的数据包时,需要留出足够的空间给协议头,包括TCP/UDPheader,IPheader和链路层头。
对IPv4数据包,可以从sk->sk_prot->max_header知道协议头的最大长度。
skb_reserve(skb,header_len);
5.3.将用户空间数据拷贝到buffer中
首先通过skb_put(skb,user_data_len),从tailroom中留出用于保存数据的空间,然后通过csum_and_copy_from_user()将数据从用户空间拷贝到这个空间中。
5.4.构造UDP协议头
通过skb_push(),向headroom中要一块空间,然后在此空间中构造UDP头。
5.5.构造IP头
通过skb_push(),向headroom中要一块空间,然后在此空间中构造IP头。
6.Sk_buffer的秘密
当调用alloc_skb()构造SKB和databuffer时,需要的buffer大小是这样计算的:
data=kmalloc(size+sizeof(structskb_shared_info),gfp_mask);
除了指定的size以外,还包括一个structskb_shared_info结构的空间大小。
也就是说,当调用alloc_skb(size)要求分配size大小的buffer的时候,同时还创建了一个skb_shared_info。
这个结构定义如下:
structskb_shared_info{
atomic_tdataref;
unsignedintnr_frags;
unsignedshorttso_size;
unsignedshorttso_segs;
structsk_buff*frag_list;
skb_frag_tfrags[MAX_SKB_FRAGS];
};
我们只要把end从char*转换成skb_shared_info*,就能访问到这个结构。
Linux提供一个宏来做这种转换:
#defineskb_shinfo(SKB)((structskb_shared_info*)((SKB)->end))
那么,这个隐藏的结构用意何在?
它至少有两个目的:
1、用于管理pageddata
2、用于管理分片
接下来分别研究sk_buffer对pageddata和分片的处理。
7.对pageddata的处理
某些情况下,希望能将保存在文件中的数据,通过socket直接发送出去,这样,避免了把数据先从文件拷贝到缓冲区,从而提高了效率。
Linux采用一种“pageddata”的技术,来提供这种支持。
这种技术将文件中的数据直接被映射为多个page。
Linux用structskb_frag_strut来管理这种page:
typedefstructskb_frag_structskb_frag_t;
structskb_frag_struct{
structpage*page;
__u16page_offset;
__u16size;
};
并在sharedinfo中,用数组frags[]来管理这些结构。
如此一来,sk_buffer就不仅管理着一个buffer空间的数据了,它还可能通过shareinfo结构管理一组保存在page中的数据。
在采用“pageddata”时,data_len成员派上了用场,它表示有多少数据在page中。
因此,如果data_len非0,这个sk_buffe管理的数据就是“非线性”的。
Skb->len–skb->data_len就是非paged数据的长度。
在有“pageddata”情况下,skb_put()就无法使用了,必须使用pskb_put()。
8.对分片的处理
9.SKB的管理函数
9.1.DataBuffer的基本管理函数
unsignedchar*skb_put(structsk_buff*skb,unsignedintlen)
“推”入数据。
在buffer的结束位置增加数据,len是要增加的长度。
这个函数有两个限制,需要调用者自己注意,否则后果由调用者负责
1)、不能用于“pageddata”的情况。
这要求调用者自己判断是否为“pageddata”情况;
2)、增加新数据后,长度不能超过buffer的实际大小。
这要求调用者自己计算能增加的数据大小。
unsignedchar*skb_push(structsk_buff*skb,unsignedintlen)
“压”入数据。
从buffer起始位置增加数据,len是要增加的长度。
实际就是将新的数据“压”入到headroom中。
unsignedchar*skb_pull(structsk_buff*skb,unsignedintlen)
“拉”走数据。
从buffer起始位置去除数据,len是要去除的长度。
如果len大于skb->len,那么,什么也不做。
在处理接收到的packet过程中,通常要通过skb_pull()将最外层的协议头去掉;例如当网络层处理完毕后,就需要将网络层的header去掉,进一步交给传输层处理。
voidskb_trim(structsk_buff*skb,unsignedintlen)
调整buffer的大小,len是调整后的大小。
如果len比buffer小则不做调整。
因此,实际是将buffer底部的数据去掉。
对于没有pageddata的情况,很好处理;但是有pageddata情况下,则需要调用__pskb_trim()来进行处理。
9.2.“Pageddata”和分片的管理函数
char*pskb_pull(structsk_buff*skb,unsignedintlen)
“拉“走数据。
如果len大于线性buffer中的数据长度,则调用__pskb_pull_tail()进行处理。
(Q:
最后,returnskb->data+=len;是否会导致skb->data超出了链头范围?
)
intpskb_may_pull(structsk_buff*skb,unsignedintlen)
在调用skb_pull()去掉外层协议头之前,通常先调用此函数判断一下是否有足够的数据用于“pull”。
如果线性buffer足够pull,则返回1;如果需要pull的数据超过skb->len,则返回0;最后,调用__pskb_pull_tail()来检查pagebuffer有没有足够的数据用于pull。
intpskb_trim(structsk_buff*skb,unsignedintlen)
将DataBuffer的数据长度调整为len。
在没有pagebuffer情况下,等同于skb_trim();在有pagebuffer情况下,需要调用___pskb_trim()进一步处理。
intskb_linearize(structsk_buff*skb,gfp_tgfp)
structsk_buff*skb_clone(structsk_buff*skb,gfp_tgfp_mask)
‘clone’一个新的SKB。
新的SKB和原有的SKB结构基本一样,区别在于:
1)、它们共享同一个DataBuffer
2)、它们的cloned标志都设为1
3)、新的SKB的sk设置为空
(Q:
在什么情况下用到克隆技术?
)
·structsk_buff*skb_copy(conststructsk_buff*skb,gfp_tgfp_mask)
·structsk_buff*pskb_copy(structsk_buff*skb,gfp_tgfp_mask)
·structsk_buff*skb_pad(structsk_buff*skb,intpad)
·voidskb_clone_fraglist(structsk_buff*skb)
·voidskb_drop_fraglist(structsk_buff*skb)
·voidcopy_skb_header(structsk_buff*new,conststructsk_buff*old)
·pskb_expand_head(structsk_buff*skb,intnhead,intntail,gfp_tgfp_mask)
·intskb_copy_bits(conststructsk_buff*skb,intoffset,void*to,intlen)
·intskb_store_bits(conststructsk_buff*skb,intoffset,void*from,intlen)
·structsk_buff*skb_dequeue(structsk_buff_head*list)
·structsk_buff*skb_dequeue(structsk_buff_head*list)
·voidskb_queue_purge(structsk_buff_head*list)
·voidskb_queue_purge(structsk_buff_head*list)
·voidskb_queue_tail(structsk_buff_head*list,structsk_buff*newsk)
·voidskb_unlink(structsk_buff*skb,structsk_buff_head*list)
·voidskb_append(structsk_buff*old,structsk_buff*newsk,structsk_buff_head*list)
·voidskb_insert(structsk_buff*old,structsk_buff*newsk,structsk_buff_head*list)
·intskb_add_data(structsk_buff*skb,char__user*from,intcopy)
·structsk_buff*skb_padto(structsk_buff*skb,unsignedintlen)
·intskb_cow(structsk_buff*skb,unsignedintheadroom)
这个函数要对SKB的headerroom调整,调整后的headerroom大小是headroom.如果headroom长度超过当前headerroom的大小,或者SKB被clone过,那么需要调整,方法是:
分配一块新的databuffer空间,SKB使用新的databuffer空间,而原有空间的引用计数减1。
在没有其它使用者的情况下,原有空间被释放。
·
- 配套讲稿:
如PPT文件的首页显示word图标,表示该PPT已包含配套word讲稿。双击word图标可打开word文档。
- 特殊限制:
部分文档作品中含有的国旗、国徽等图片,仅作为作品整体效果示例展示,禁止商用。设计者仅对作品中独创性部分享有著作权。
- 关 键 词:
- skbuff 详细 分析