一、mbuf 介绍
在BSD TCP/IP协议栈代码设计中的一个基本概念就是存储器缓存,称作一个mbuf,全称为"memory buffer",用于存储各种信息。mbuf的主要用途是保存在进程和网络接口间互相传递的用户数据。但mbuf也用于保存其他各种数据:源与目标地址、插口选项等等。mbuf相当于Linux内核中的skb。
mbuf的相关定义在文件/4.4BSD-Lite/usr/src/sys/sys/Mbuf.h
二、mbuf 结构体
/*每个mbuf的大小*/
#define MSIZE 128
/*一个mbuf簇(外部缓存)的大小*/
#define MCLBYTES 2048
/*128-20=108,正常mbuf中的最大数据量*/
#define MLEN (MSIZE - sizeof(struct m_hdr)) /* normal data len */
/*108-8=100,带分组首部的mbuf的最大数据量*/
#define MHLEN (MLEN - sizeof(struct pkthdr)) /* data len w/pkthdr */
/*208,存储到簇中的最小数据量*/
#define MINCLSIZE (MHLEN + MLEN)
/* mbuf的头部信息 */
这个结构用来描述mbuf跟具体的内容无关
struct m_hdr {
struct mbuf *mh_next; /* 指向链中下一个mbuf的指针 */
struct mbuf *mh_nextpkt; /* 指向下一个链的指针 */
int mh_len; /* mbuf中数据的长度(不包括头部) */
char *mh_data; /* 指向数据区的指针 */
short mh_type; /* mbuf的数据类型,如MT_DATA*/
short mh_flags; /* mbuf标识,具体定义见下 */
};
/ *记录/包头在链的第一个mbuf中; M_PKTHDR设置有效* /
struct pkthdr {
int len; /* 整个mbuf链表包含数据的总长度,在链表的第一个mbuf中维
护一个带有总长度的分组首部的原因是,当需要总长度时可以避
免查看所有mbuf中的mh_len来求和*/
struct ifnet *rcvif; /* 指向接收分组的接收接口结构的指针*/
};
/ *描述外部存储映射到mbuf,M_EXT设置有效* /
struct m_ext {
caddr_t ext_buf; /* 缓冲区的开始 */
void (*ext_free)(); /* free routine if not the usual */
u_int ext_size; /* 缓冲区大小,对于ext_free */
};
这个就是mbuf的描述,设计的比较巧妙
struct mbuf {
struct m_hdr m_hdr;
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]; /* !M_PKTHDR, !M_EXT */
} M_dat;
};
// 为了便于访问,这里定义了很多宏
#define m_next m_hdr.mh_next
#define m_len m_hdr.mh_len
#define m_data m_hdr.mh_data
#define m_type m_hdr.mh_type
#define m_flags m_hdr.mh_flags
#define m_nextpkt m_hdr.mh_nextpkt
#define m_act m_nextpkt
#define m_pkthdr M_dat.MH.MH_pkthdr
#define m_ext M_dat.MH.MH_dat.MH_ext
#define m_pktdat M_dat.MH.MH_dat.MH_databuf
#define m_dat M_dat.M_databuf
mbuf的总长为128个字节,前20个字节是首部(struct m_hdr),mbuf可以用成员mh_next和mh_nextpkt链接起来。(m_next指向链中下一个mbuf,而m_nextpkt指向下一个链)
+-----------+ +-----------+ +-----------+ | m_next |------->| m_next |------->| m_next |-------> ... +-----------+ +-----------+ +-----------+ ----| m_nextpkt | | ... | | ... | | +-----------+ | | | | | | ... | | | | | | +-----------+ +-----------+ +-----------+ | | +-----------+ +-----------+ |-->| m_next |------->| m_next |-------> ... +-----------+ +-----------+ --- | m_nextpkt | | ... | | +-----------+ | | | | ... | | | | +-----------+ +-----------+ | |--> ...
三、mbuf 四种类型
可以看到mbuf大体由两部分组成,m_hdr和M_dat。
M_dat是个联合体,使用其中哪个成员是由m_hdr.mh_flags来决定的。
1.如果mh_flags里没有M_EXT与M_PKTHDR,则此mbuf完全是用来存放数据的。总共可以存放MLEN(108)字节的数据
2.如果mh_flags里有M_EXT则此mbuf使用外部簇(外部存储空间(cluster))来存放数据
3.如果mh_flags里有M_PKTHDR,则此mbuf是一个记录的起始mbuf(某个packet的一串mbuf中的第一个),由于MH_pkthdr占用了部分空间,总共只能存放MHLEN(100)字节的数据;
4.如果mh_flags里既有M_PKTHDR又有M_EXT(注意: 一个mbuf可以同时设置上述两个标志)则此mbuf是起始mbuf,但是使用外部簇来存放数据。
/* mbuf mh_flags*/ #define M_EXT 0x0001 /* 一个mbuf的大小是128字节,是一个相对比较小的缓存。如果数 据比较多,就需要多个mbuf连起来或者用一个叫簇的东西来存储 数据。M_EXT就是这个标志 */ #define M_PKTHDR 0x0002 /* 表明分组的第一个mbuf,在数据区中有pkthdr */ #define M_EOR 0x0004 /* 表明记录的尾部,TCP是一个字节流,不设置这个标志 */
m_hdr.mh_type 介绍
/* mbuf types */ #define MT_FREE 0 /* should be on free list */ #define MT_DATA 1 /* dynamic (data) allocation */ 数据就是这个类型 #define MT_HEADER 2 /* packet header */ #define MT_SOCKET 3 /* socket structure */ #define MT_PCB 4 /* protocol control block */ #define MT_RTABLE 5 /* routing tables */ #define MT_HTABLE 6 /* IMP host tables */ #define MT_ATABLE 7 /* address resolution tables */ #define MT_SONAME 8 /* socket name */ #define MT_SOOPTS 10 /* socket options */ #define MT_FTABLE 11 /* fragment reassembly header */ #define MT_RIGHTS 12 /* access rights */ #define MT_IFADDR 13 /* interface address */ #define MT_CONTROL 14 /* extra-data protocol message */ #define MT_OOBDATA 15 /* expedited data */
当mh_flags设置为M_PKTHDR时,一般与下面的类型进行或操作 如 mh_flags |= M_MCAST
/* mbuf pkthdr flags, also in m_flags */ #define M_BCAST 0x0100 /* send/received as link-level broadcast */ #define M_MCAST 0x0200 /* send/received as link-level multicast */ /* flags copied when copying m_pkthdr */ 这个具体干嘛用的不懂。。。 #define M_COPYFLAGS (M_PKTHDR|M_EOR|M_BCAST|M_MCAST)
判断类型
(m_flags & M_PKTHDR) == 1
(m_flags & M_EXT) == 1
mbuf大小固定的理由:
1. 可以最大限度地避免内存碎片的产生
2. 可在不重新分配和拷贝的前提下完成协议对数据区的操作。
3. 如果mbuf不是固定长度,dtom()函数的开销将会非常大。
4. mbuf中的pakcet头中的tags用于携带一些关于packet的但又不能放在packet内部的信息。
四、以图说明mbuf 四种类型
把图中分为四个部分:从左到右依次叫 图-1,图-2,图-3,图-4
1. m_flags的值为0,图-1 表示此mbuf只包含数据。在mbuf中有108字节的数据空间(m_dat数组,详情见mbuf结构定义),指针m_data指向这108字节缓存中的某个位置。图中m_data指向缓存数据的起始位置,但它可以指向缓存中的任意位置。m_len指示了从m_data开始的数据字节数。
2. m_flags的值为M_PKTHDR,图-2 M_PKTHDR表示此mbuf是一个分组首部,而且是一个分组数据的第一个mbuf。数据仍然保存在这个mbuf中,但是由于分组首部占用8个字节,所以只有100字节的数据可以存储在这个mbuf中(m_pktdat数组,详情见mbuf结构定义)。
m_pkthdr.len的值是这个分组的mbuf链中所有数据的总长度;即所有通过m_next指针链接的mbuf的m_len值的和。输出分组的m_pkthdr.rcvif为空,但是对于输入分组,m_pkthdr.rcvif是一个指向接收接口的ifnet结构的指针。
3. m_flags的值为M_EXT,图-3 这种mbuf没有分组首部(没有设置M_PKTHDR),但是包含超过208字节的数据(MINCLSIZE),此时用到一个叫“簇”的外部缓存(设置M_EXT)。此mbuf中仍然为分组首部分配了空间,但是没用,图-3 中用阴影表示出来。
一般来说,我们希望这种类型的mbuf的m_len的值最小为209,即至少存储的数据大小为209字节(而不应该是图-3 中的208,图中有误)。208字节数据可以存放到两个mbuf中,第一个存放100字节,第二个存放108字节。
4. m_flags的值为M_PKTHDR|M_EXT,图-4 此类mbuf包含一个分组首部,并且数据超过208字节。
关于mbuf的几点说明:
1. mbuf结构的大小总是128字节。这意味着图-3、图-4两个mbuf在结构m_ext后面的未用空间为88字节(128-20-8-12)。
2. 既然有些协议(例如UDP)允许零长记录,当然就可以有m_len为0的缓存。
3. 在每个mbuf中的成员m_data指向相应缓存数据的开始(mbuf本身或者一个簇)。这个指针能指向相应缓存的任意位置,不一定是开始。
4. 带有簇的mbuf总是包含缓存的起始地址(m_ext.ext_buf)以及缓存大小(m_ext.ext_size)。注意m_data和m_ext.ext_buf的不同含义,m_data是指向缓存数据的起始地址,m_ext.ext_buf是指向缓存(簇)的起始地址,只有当m_data也只指向缓存(簇)的第一个字节时,两者的值是一样的。
5. 指针m_next是把多个mbuf链接在一起,把一个分组形成一条mbuf链表。指针m_nextpkt把多个分组链接成一个mbuf队列。在队列中的每个分组可以是一个单独的mbuf,也可以是一个mbuf链表。每个分组的第一个mbuf包含一个分组首部。如果分组是一个mbuf链表,只有第一个mbuf的m_nextpktp被使用,其它mbuf的m_nextpkt为空指针。
上图是包含两个分组的mbuf队列,第一个分组UDP数据报已经放到接口输出队列中(14字节的以太网商务部已经添加到链表中第一个mbuf的IP首部前面);第二个分组TCP报文段(1460字节数据)也被添加到队列中,TCP数据包含在一个簇中,并且第一个mbuf中包含了以太网、IP与TCP首部,在这个簇中可以看到指向簇中缓存数据的指针m_data并没有指向簇的起始位置(注意理解)。
上图所示的是m_flags的五个独立的值。M_EXT和M_PKTHDR前文已经介绍过,如果数据流有记录边界,那么对于记录尾的mbuf其m_flags应该设置M_EOR,TCP从来不需要设置,因为它提供的是无记录边界的字节流服务。OSI和XNS运输层要使用到这个标志。M_BCAST和M_MCAST主要用于链接层广播或者多播数据的接收和发送。
m_type指示存储在mbuf中的数据类型。mbuf除了可以存放要发送或者接收的用户数据,还可以存储各种不同类型的数据结构,具体如上图所示。