Chinaunix首页 | 论坛 | 博客
  • 博客访问: 443174
  • 博文数量: 403
  • 博客积分: 0
  • 博客等级: 民兵
  • 技术积分: -70
  • 用 户 组: 普通用户
  • 注册时间: 2016-09-05 12:45
文章分类

全部博文(403)

文章存档

2014年(3)

2013年(1)

2012年(3)

2011年(21)

2010年(13)

2009年(64)

2008年(9)

2007年(36)

2006年(253)

分类: 系统运维

2007-03-30 14:58:55

在这个系列文章中,我自己将尝试着分析Linux kernel2.6下面的中的网络部分的源代码,主要是TCP/IP部分,只是记录自己的学习经历,如果你感兴趣,不妨说出你的理解。

    这是第一部分,主要分析一下 TCP/IP相关协议以及实现网络协议时kennel常用的数据结构,提供一些基础知识。注意,虽然分析的主要是TCP/IP族,但不可避免提及其它的一些协议,因为TCP/IP族只是网络协议的一个组成。

TCP/IP协议的分层    

    在TCP/IP协议的学习中,很多人对TCP/IP协议的分层不是很理解,其实,可以用一句话概括分层的原因--“各司其职。TCP/IP中的主要协议,可以用下面这个图进行一个简单的说明。(下图没有包含SCTP,它属于Transport Layer,但在2.6的kernel是支持的)

总的来说:Data-link layer是处理硬件相关的问题,在实现上多是和硬件驱动相关的代码;而Network layer则在Data-link layer的基础上提供数据的投寄,比如我们常说的IP协议,提供转发(将Packet发送到其它host)或者象上层传递(以本机为目的地的 Packet);Transport layer在IP协议的基础上更进一步的对数据的交互提供控制,比如TCP提供可靠的双向连接;Application layer是我们的网络程序,通常实现一个具体的功能。分层的关键点在于:Network layer的IP协议的特点是:unreliable(不可靠), connectionless(无连接)的,原因是IP协议只是尽力去把数据交付给目的地,它并不提供其它诸如安全性,可靠性等保证。在 Transport layer提供其它的保证,比如TCP提供了可靠性等保证,而UDP则在IP协议的上面增加了对给定主机上的多个目的地址进行区别的能力。好了,下面开始正式进入主题。

     在下面,我将首先分析几个重要的数据结构,主要是sk_buff,net_device,sock。好了,喝一口水,放下杯子,Let‘s Go!

sk_buff (Socket Buffer)结构

    在网络相关代码中,一个基本的问题就是内存的管理,这些内存管理的基本结构都是经过精心设计的,例如,在BSD的代码中的mbuf结构,而Linux中则是 sk_buff(Socket Buffer)。在Stevens的巨著TCP/IP Illustrated V2中描述mbuf所面临的问题时,很好的说明了网络Code相关的内存管理的基本观点。关键点是:sk_buff是被不同的Layer所使用,是层层传递的(从Layer1->Layer4或者反向)。

    sk_buff(include/linux/skbuff.h) 的最初的设计者是Alan Cox(大名鼎鼎的Hacker哦,网络部分的Code经常看到其大名),他在网上发表过一篇关于sk_buff设计相关的文章,可以去看看(当然,我是看过的,呵呵,要是不愿意啃e文,就看我的文章好了)。下面我将对sk_buff结构进行分析。
   
    这个结构大小居中,一般来说,这种结构的组织是有一定逻辑的,它可以分为:

  • 分层信息:自然,是根据上面所说的四层结构
  • packet的一般信息
  • 指定特征描述
  • 相关管理函数(callback function的形式)
  了解面向对象(OO)的人一定会说,呵呵,不就是个类(Class)么!不错,这样理解没什么问题,毕竟,面向对象(OO)是思想,与语言关系并不唯一。    

struct sk_buff {
 /* These two members must be first. */
 struct sk_buff  *next;  //这两个变量让sk_buff    
 struct sk_buff  *prev;  //构成双向的链表         

 struct sk_buff_head *list;  //指向链表的头
 struct sock         *sk;    //指向创建这个sk_buff的socket
 struct timeval      stamp;  //该packet到达的时间(以jiffies为单位)
 struct net_device   *dev;   //incoming/outcoming 的设备
 struct net_device   *input_dev; //到达的设备
 struct net_device   *real_dev;  //真实使用的设备

 union { //指向传输层
  struct tcphdr  *th;
  struct udphdr  *uh;
  struct icmphdr *icmph;
  struct igmphdr *igmph;
  struct iphdr   *ipiph;
  struct ipv6hdr *ipv6h;
  unsigned char  *raw;
 } h;

 union { //指向网络层
  struct iphdr   *iph;
  struct ipv6hdr *ipv6h;
  struct arphdr  *arph;
  unsigned char  *raw;
 } nh;

 union { //指向链路层
    unsigned char  *raw;
 } mac;

 struct  dst_entry *dst; //目的表项(指向一个路由cache,在后面文章详解)
 struct sec_path   *sp;  //只被xfrm使用(这个讨论基本上没用到)

 /*
  * This is the control buffer. It is free to use for every
  * layer. Please put your private variables there. If you
  * want to keep them across layers you have to do a skb_clone()
  * first. This is owned by whoever has the skb queued ATM.
  */
 char   cb[40];

 unsigned int  len,      //真实数据区的长度
               data_len, //数据区的长度
               mac_len,  //mac长度
               csum;     //checknum
 __u32   priority;

//以下都是作为flag用,只用了一个byte的5个bit
 __u8    local_df:1, //允许local分片标志
         cloned:1,   //允许clone操作标志
         ip_summed:2,
         nohdr:1;
         /* 3 bits spare */


 __u8     pkt_type; //packet的类型,在if_packet.h中定义取值
 __be16   protocol;

 void   (*destructor)(struct sk_buff *skb);
............

............
 /* These elements must be at the end, see alloc_skb() for details.  */
 unsigned int  truesize; //buffer的大小(后面详解)
 atomic_t      users;    //user计数,atomic_t指明该变量只能"原子"操作

//在后面详细介绍
 unsigned char  *head,
                *data,
                *tail,
                *end;
};

下面这张图对sk_buff在分配了外部Data区之后构成的sk_buff双向链表进行的进行初步的说明,余下部分,是我的任务了,呵呵:

  • next,prev用来构成双向的链表(图2所示)
  • 联合h,nh,mac分别指向该sk_buff所拥有的data区,表明packet各层头的起始位置
  • head指向该sk_buff所拥有的data的头,end指向sk_buff所拥有的data的尾;而data,tail则是分别指向该data区packet的头和尾.这里,head与data之间的空间被称为"头空间(headroom)";tail和end之间的空间则被叫做尾空间"(tailroom)"

这种预留空间的做法对性能会有一个提升,比如,一个packet从IP层传到数据链路层,如果头空间已经足够大,这不必再次分配buffer,要知道,再次分配buffer代价很昂贵,因为你可能不仅要分配新的buffer,还有可能需要拷贝到新的buffer中去

  • 在sk_buff所拥有的外部data存储区域,变量dataref(在结构skb_shared_info中,该结构也在skbff.h中)是对该data区域"引用计数".("引用计数"的在对性能要求很高的地方方法很常见,在《C++沉思录》中有几章专门将这一种技术,是我看的讲"引用计数"最清楚的书了)

  • 看到void   (*destructor)(struct sk_buff *skb);有没有想起C++中常见的析构函数?(有人评论说Linux kernel的代码多是面向对象的c实现,不信,我们剖析代码时慢慢看来)

    好了,变量暂时分析到这里,下面,是对该结构进行操作的一些函数的说明,这才可以进一步看出该结构在网络代码的实现中可以提供哪些便利,对性能会有哪些提升.这些函数我把它主要分为两类,一类是对sk_buff 结构内部进行操作,另一类则是对sk_buff 构成的链表进行操作,分类可能不尽合理,但这是我理解问题的方式,你可以按照你的方式来理解.我先描述第一类,涉及的函数见下面:

1.创建以及释放skb_buff:

  alloc_skb()(net/core/skbuff.c) 

在函数alloc_skb()中,主要是调用内核函数kmalloc去分配外部的Data区域,分配完成后指针的指向以及内存的情况,请参见上图2
                     

  dev_alloc_skb()(include/linux/skbuff.h),

有设备调用在收到包时调用alloc_skb()

  kfree_skb()(include/linux/skbuff.h),

很明显的功能,与alloc_skb()相对应,我就不细说了

  dev_kfree_skb()(include/linux/skbuff.h)

与dev_alloc_skb()对应

2.拷贝(copy)与克隆(clone)

skb_copy()(net/core/skbuff.c) 

拷贝skb_buff结构各个变量的值以及外部的Data区域

skb_copy_expand()(net/core/skbuff.c)

拷贝skb_buff结构各个变量的值以及外部Data区域,以指定值扩大Data的大小
 

skb_clone()(net/core/skbuff.c) 

只拷贝skb_buff结构中相关变量的值,对外部Data区域, 仅"引用计数"加一

评注:在C++中,对拷贝构造函数有两种语义,"深拷贝"和"浅拷贝",这里的拷贝(copy)与克隆(clone)即与起很类似,因此,操作是要考虑清楚语义的问题.(Ps:C++中,难的恰好是语义,而非语法)

3.管理外部的Data空间

4.其它相关

阅读(4672) | 评论(0) | 转发(0) |
给主人留下些什么吧!~~