您好,欢迎访问三七文档
当前位置:首页 > IT计算机/网络 > 数据库 > 深度探索套接字缓冲区
深度探索套接字缓冲区(1)套接字缓冲区用结构体structsk_buff表示,它用于在网络子系统中的各层之间传递数据,处于一个核心地位,非常之重要。它包含了一组成员数据用于承载网络数据,同时,也定义了在这些数据上操作的一组函数。下面是其完整的定义:structsk_buff{structsk_buff*next;structsk_buff*prev;structsock*sk;structskb_timevaltstamp;structnet_device*dev;structnet_device*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[48];unsignedintlen,data_len,mac_len,csum;__u32priority;__u8local_df:1,cloned:1,ip_summed:2,nohdr:1,nfctinfo:3;__u8pkt_type:3,fclone:2,ipvs_property:1;__be16protocol;void(*destructor)(structsk_buff*skb);#ifdefCONFIG_NETFILTER__u32nfmark;structnf_conntrack*nfct;#ifdefined(CONFIG_NF_CONNTRACK)||defined(CONFIG_NF_CONNTRACK_MODULE)structsk_buff*nfct_reasm;#endif#ifdefCONFIG_BRIDGE_NETFILTERstructnf_bridge_info*nf_bridge;#endif#endif/*CONFIG_NETFILTER*/#ifdefCONFIG_NET_SCHED__u16tc_index;#ifdefCONFIG_NET_CLS_ACT__u16tc_verd;#endif#endifunsignedinttruesize;atomic_tusers;unsignedchar*head,*data,*tail,*end;};这是一个比较宠大的结构体,为了便于理解,我们分成多块进行分析。为了使用套接字缓冲区,内核创建了两个后备高速缓存(looasidecache),它们分别是skbuff_head_cache和skbuff_fclone_cache,协议栈中所使用到的所有的sk_buff结构都是从这两个后备高速缓存中分配出来的。两者的区别在于skbuff_head_cache在创建时指定的单位内存区域的大小是sizeof(structsk_buff),可以容纳任意数目的structsk_buff,而skbuff_fclone_cache在创建时指定的单位内存区域大小是2*sizeof(structsk_buff)+sizeof(atomic_t),它的最小区域单位是一对strcutsk_buff和一个引用计数,这一对sk_buff是克隆的,即它们指向同一个数据缓冲区,引用计数值是0,1或2,表示这一对中有几个sk_buff已被使用。创建一个套接字缓冲区,最常用的操作是alloc_skb,它在skbuff_head_cache中创建一个structsk_buff,如果要在skbuff_fclone_cache中创建,可以调用__alloc_skb,通过特定参数进行。structsk_buff的成员head指向一个已分配的空间的头部,该空间用于承载网络数据,end指向该空间的尾部,这两个成员指针从空间创建之后,就不能被修改。data指向分配空间中数据的头部,tail指向数据的尾部,这两个值随着网络数据在各层之间的传递、修改,会被不断改动。所以,这四个指针指向共同的一块内存区域的不同位置,该内存区域由__alloc_skb在创建缓冲区时创建,四个指针间存在如下关系:head=data=tailend那指向的这块内存区域有多大呢?一般由外部根据需要传入。外部设定这个大小时,会根据实际数据量加上各层协议的首部,再加15(为了处理对齐)传入,在__alloc_skb中根据各平台不同进行长度向上对齐。但是,我们另外还要加上一个存放结构体structskb_shared_info的空间,也就是说end并不真正指向内存区域的尾部,在end后面还有一个结构体structskb_shared_info,下面是其定义:structskb_shared_info{atomic_tdataref;//引用计数。unsignedshortnr_frags;//数据片段的数量。unsignedshorttso_size;unsignedshorttso_segs;unsignedshortufo_size;unsignedintip6_frag_id;structsk_buff*frag_list;//数据片段的链表。skb_frag_tfrags[MAX_SKB_FRAGS];//每一个数据片段的长度。};这个结构体存放分隔存储的数据片段,将数据分解为多个数据片段是为了使用分散/聚集I/O。如果是在skbuff_fclone_cache中创建,则创建一个structsk_buff后,还要把紧邻它的一个structsk_buff的fclone成员置标志SKB_FCLONE_UNAVAILABLE,表示该缓冲区还没有被创建出来,同时置自己的fclone为SKB_FCLONE_ORIG,表示自己可以被克隆。最后置引用计数为1。最后,truesize表示缓存区的整体长度,置为sizeof(structsk_buff)+传入的长度,不包括结构structskb_shared_info的长度。深度探索套接字缓冲区(2)前面一篇文章分析了套接字缓冲区sk_buff的创建过程,但一般来讲,一个套接字缓冲区总是属于一个套接字,所以,除了调用sk_buff本身的alloc_skb函数创建一个套接字缓冲区,套接字本身还要对sk_buff进行一些操作,以及设置自身的一些成员值。下面我们来分析这个过程。如果检查到待发送数据报没有传输层协议头(不是传输层的tcp或udp数据报),套接字创建缓冲区的函数是sock_alloc_send_skb,它的函数原型是:structsk_buff*sock_alloc_send_skb(structsock*sk,unsignedlongsize,intnoblock,int*errcode)它直接调用函数:staticstructsk_buff*sock_alloc_send_pskb(structsock*sk,unsignedlongheader_len,unsignedlongdata_len,intnoblock,int*errcode)参数sk是要创建缓冲区的那个套接字,header_len是sk_buff中,成员data指向的那块数据区的长度,而data_len则是指除那块数据区以外的被分片的数据的总长。noblock指示是否阻塞模式。对于非传输层协议包,不使用分散/聚集IO,所以,置data_len为0。网络层代表一个套接字的结构体structsock有两个成员sk_wmem_alloc和sk_sndbuf,sk_wmem_alloc表示在这个套接字上已经分配的写缓冲区(发送缓冲区)的总长,每次分配完一个属于它的写sk_buff,这个值总是加上sk_buff-truesize。而sk_sndbuf则是这个socket所允许的最大发送缓冲区。它的值在系统初始化的时候设为变量sysctl_wmem_max的值,可以通过系统调用进行修改。其缺省值sysctl_wmem_max为107520字节,因为它的计算长度还包括了structsk_buff,所以,一般认为其缺省值是64K数据。而对于传输层协议包,我们使用sock_wmalloc创建套接字缓冲区,这是一个更为简单的创建函数,没有超时、出错判断机制,直接通过调用alloc_skb创建一个sk_buff并返回。但对于传输层协议有一个不同点就是sk_wmem_alloc最大可以达到两倍sk_sndbuf,即缺省的发送缓冲区可以达到128K。到这里,我们就不难理解structsk_buff中另外两个成员的含义了:len是指数据包全部数据的长度,包括data指向的数据和end后面的分片的数据的总长,而data_len只包括分片的数据的长度。而truesize的最终值是len+sizeof(structsk_buff)。深度探索套接字缓冲区(3)结构体structsk_buff中共有三个联合体,分别是h,nh和mac,它们都是一些指针,指向协议栈各层协议的首部。从含有的首部类型来看,nh是h的子集,而mac是nh的子集。《Linux设备驱动程序》第三版第522页这样介绍这三个联合体:h中包含有传输层的报文头,nh中包含有网络层的报文头,而mac中包含的是链路层的报文头。光靠这样的一个解释可能过于抽象,让我们来看一个UDP数据报是怎么样穿过数千公里长的网线来到我们的网卡,通过网卡的驱动程序层层向上来到协议栈的上层的。当网卡驱动程序收到一个UDP数据报后,它创建一个结构体structsk_buff,确保data成员指向的空间足够存放收到的数据(对于数据报分片的情况,因为比较复杂,我们暂时忽略,我们假设一次收到的是一个完整的UDP数据报)。把收到的数据全部拷贝到data指向的空间,然后,把skb-mac.raw指向data,此时,数据报的开始位置是一个以太网头,所以skb-mac.raw指向链路层的以太网头。然后通过调用skb_pull剥掉以太网头,所谓剥掉以太网头,只是把data加上sizeof(structethhdr),同时len减去这个值,这样,在逻辑上,skb已经不包含以太网头了,但通过skb-mac.raw还能找到它。这就是我们通常所说的,IP数据报被收到后,在链路层被剥去以太网头。在继续往上层的过程中,一直到我们的my_inet域的函数myip_local_deliver_finish中,我们通过__skb_pull剥去IP首部,同样,我们可以通过skb-nh.raw找到它。最后,skb-h.raw指向data,即udp首部,udp首部其实到最后都没有被剥去,应用程序在调用recv接收数据时,直接从skb-data+sizeof(strucudphdr)的位置开始拷贝。我们可以看到,从网卡驱动开始,通过协议栈层层往上传送数据报时,通过增加skb-data的值,来逐步剥离协议首部,但通过h,nh,mac这三个联合指针,我们可以访问到这些协议首部,从而利用其提供的有效信息。但必须指出的是,《Linux设备驱动程序》中的解释并不完全准确,mac中包含链路层报文头,这是毫无疑问的,nh中包含义网络层的报文头,也没有问题,因为ARP协议也属于网络层协议,nh中包含IP首部或者ARP首部。当我们接收到一个icmp数据报时,在myip_local_deliver_finish中剥去IP首部后,skb-h.raw指向的是icmp首部,但icmp显然不是传输层协议,它是网络层的一个附属协议。igmp也是相同的情况,我想这也是为什么sk_buff的三个联合体不命名为th,nh,mac的原因,因为th(transpr
本文标题:深度探索套接字缓冲区
链接地址:https://www.777doc.com/doc-2241496 .html