m_buff介绍

 1.    m_buff介绍

mbufmemory buffer的缩写。它的主要用途是保存在进程和网络接口间互相传递的用户数据,同时也保存其它有用的信息,如源地址、插口选项等。

首先我们来看一下mbuf的数据结构,然后分析各成员变量的含义。

/* mbuf的头部数据结构,大小为20字节 */

struct m_hdr {

      struct     mbuf *mh_next;        

/* 指向下一个mbuf这些buff存放一次应用的数据,send/recvr的一个分组数据可以很多*/

      struct     mbuf *mh_nextpkt;

/*指向下一个分组的第一个mbuf,可见这是所有分组的桥梁  */

      caddr_t  mh_data;     /* 指向有效数据,可以是mbuf中的任意位置 */

      int   mh_len;        /* 这个mbuf的有效数据长度*/

      short      mh_type;  /*  数据类型? */

      short      mh_flags;    // 见下图

/*mbuf标志位。如果为0,则mbuf只包含数据;

/如果为M_PKTHDR则说明这个mbuf 为一个分组的首部。*/    

};

 m_buff介绍_第1张图片

                                     图1 首部的flags

/*分组的首部,第一个mbuf;   if M_PKTHDR */

struct  pkthdr {

      struct     ifnet *rcvif; 

      /* 只有接收时对输入分组有用,否则对于输出分组置为0 */

      int   len;     /* 分组的总长度*/

};

/* 如果使用外部存储 mbuf, valid if M_EXT set */

struct m_ext {

      caddr_t  ext_buf;        /*指向外部存储数据位置*/

      void       (*ext_free)();  /* 不使用时释放外部存储 */

      u_int      ext_size;    /* 外部存储的大小*/

};

struct mbuf {

      struct     m_hdr m_hdr;//20字节

      union {

             struct {

                    struct     pkthdr MH_pkthdr;   /* M_PKTHDR set */

                    union {

                           struct     m_ext MH_ext;   /* M_EXT set */

                           char       MH_databuf[MHLEN];

                    } MH_dat;

             } MH;

             char       M_databuf[MLEN]; /* 只存放数据时最长为108字节*/

      } M_dat;

};

由上面的数据结构,我们可以发现存在多种mbuf

第一种就是m_flags等于0时,mbuf只包含数据,这个mbuf最大可容108个字节,内核定了mbuf最大为128字节,除去20字节的头部。

第二种m_flags等于M_PKTHDR,指示这是一个分组的首部,只首部增加一个m_pktdat结构,占去了8字节,如果不没有外部存储,则这个mbuf最大容纳100字节数据。

第三种mbuf包含外部存储,标志位M_EXT

第四种mbuf既是首部同时还设置了M_EXT

下图2展示了四种mbuf的结构,图3展示了一个队列的应用示意图。

 图2 mbuf实例

 

3 一个队列示意图

2    获取m_buff

struct mbuf *  m_get(int nowait, int type;)

{

      register struct mbuf *m;

      MGET(m, nowait, type);

      return (m);

}

#define MGET(m, how, type) { /

      MALLOC((m), struct mbuf *, MSIZE, mbtypes[type], (how)); /

      if (m) { /

             (m)->m_type = (type); /

             MBUFLOCK(mbstat.m_mtypes[type]++;) /

             (m)->m_next = (struct mbuf *)NULL; /// 这个地方有点妙

             (m)->m_nextpkt = (struct mbuf *)NULL; /

             (m)->m_data = (m)->m_dat; /

             (m)->m_flags = 0; /

      } else /

             (m) = m_retry((how), (type)); /

}

3.    m_devgetm_pullup函数

m_pullup函数用来保证制定数目的字节在链表的第一个m_buff中紧挨着存放,即这些指定数目的字节被复制到一个新的mbuff并紧挨着存放。什么时候会调用到m_pullup,我们要首先看一下其它几个函数

m_devget函数:当网络接收到一个网络帧时,设备驱动程序调用m_devget来创建一组mbuff链表,然后将这些数据复制到mbuff中。数据的多少导致可能创建不同的mbuff链表。在实际应用中,如果第一个mbuff中要16字节空出来,接收到的以太网首部其实不是存放在这里的,但还是要保留16个字节,也就是接收时16字节是浪费的。它的作用是在进行输出时将以太网地址存放在这里,这样就节省了建立输出数据报时的时间。使用16字节而不是以太网所需的14字节是为了用长字对准方式存储IP首部。

我们可以看到当数据少于84字节时,只要使用一个mbuff就可以了;对于85-100字节之间时仍用一个mbuff就可以了,只不过省略了16字节的头部了;大于100小于207字节的数据包,使用两个mbuff;大于207字节的数据包则需要使用外部存储了。

 mtoddtom宏:

#define mtod(m,t) ((t)((m)->m_data))

 返回指向mbuf数据的指针,并把指针声名为指定的类型。

#define dtom(x) ((struct mbuf*)((int)(x)&~(MSIZE-1)))

取得一个存放在一个mbuf 中任意位置的数据指针并返回这个mbuf结构本身的一人指针。内核存储分配器总是分配连续的MSIZE(128)字节的块,dtom只是清除了参数中指针的低位来发现这个mbuf 的起始位置。这应该是基于mbuf起始地址总是128N,否则我觉得结论不一定正确。对于外部存储来说不能使用dtom,因为外部没有指回mbuf的结构,这里我们就可以使用m_pullup来解决问题。

m_pullup(),何时调用m_pullup()?当分组中第一个mbuf的数据量小于很小时,即小于标准的IP(20)+udp(8)/tcp(20)时调用。调用m_pullup是基于协议首部的剩余部分存放在链表的下一个mbuf中,m_pullup重新安排m_buf链表,使得协议首部的数据存放在第一个mbuf中,这样就可以使用mtoddtom来操作。实际上上面这种情况很少使用也很少凑效。

第二个用途:应用在TCP/IP重组问题中。对于IP分组(10章以后需要回头来再看),因为IP的分组算法是基于IP包首部的源和目标地址而建立的双向链表。如果接收到的数据分组是存放在外部簇中,则它的IP首部将在外部簇中,由上面我们可以知道无法使用宏进行操作,这样在遍历链表时就不能转化为mbuf,所以不能将IP链表指针存储在簇中。解决方法:当接收到一个存放在簇中的分组,IP分片例程总是调用m_pullup强制性将20字节的IP首部放到它的mbuf中。新创建了一个 mbuf。这里也有缺陷就是m_pullup开销较大,不但要新分配一个mbuf还要将IP首部复制进来。TCP重组需要以后进一步学习

你可能感兴趣的:(TCP-IP)