《TCP/IP详解:实现》: mbuf 详解一

一、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除了可以存放要发送或者接收的用户数据,还可以存储各种不同类型的数据结构,具体如上图所示。

第1章概述 1.1引言 1.2源代码表示 1.2.1将拥塞窗口设置为 1.2.2印刷约定 1.3历史 1.4应用编程接口 1.5程序示例 1.6系统调用和库函数 1.7网络实现概述 1.8描述符 1.9mbuf与输出处理 1.9.1包含插口地址结构的mbuf 1.9.2包含数据的mbuf 1.9.3添力口IP和UDP首部 1.9.4IP输出 1.9.5以太网输出 1.9.6UDP输出小结 1.10输入处理 1.10.1以太网输入 1.10.2IP输入 1.10.3UDP输入 1.10.4进程输入 1.11网络实现概述(续) 1.12中断级别与并发 1.13源代码组织 1.14测试网络 1.15小结 第2章mbuf:存储器缓存 2.1引言 2.2代码介绍 2.2.1全局变量 2.2.2统计 2.2.3内核统计 2.3mbuf的定义 2.4mbuf结构 2.5简单的mbuf宏和函数 2.5.1m_get函数 2.5.2MGET宏 2.5.3m_retry函数 2.5.4mbuf锁 2.6m_devget和m_pullup函数 2.6.1m_devget函数 2.6.2mtod和dtom宏 2.6.3m_pullup函数和连续的协议首部 2.6.4m_pullup和IP的分片与重组 2.6.5TCP重组避免调用m_pullup 2.6.6m_pullup使用总结 2.7mbuf宏和函数的小结 2.8Net/3联网数据结构小结 2.9m_copy和簇引用计数 2.10其他选择 2.11小结 第3章接口层 3.1引言 3.2代码介绍 3.2.1全局变量 3.2.2SNMP变量 3.3ifnet结构 3.4ifaddr结构 3.5sockaddr结构 3.6ifnet与ifaddr的专用化 3.7网络初始化概述 3.8以太网初始化 3.9SLIP初始化 3.10环回初始化 …… 第4章接口:以太网 第5章接口:SLIP和环回 第6章IP编址 第7章域和协议 第8章IP:网际协议 第9章IP选项处理 第10章IP的分片与重装 第11章ICMP:Internet控制报文协议 第12章IP多播 第13章ICMP:Internet组管理协议 第14章IP多播选路 第15章插口层 第16章插口I/O 第17章插口选项 第18章Radix树路由表 第19章选路请求和选路消息 第20章选路插口书摘插图第1章概述
评论 2
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

HarkerYX

你的鼓励将是我创作的最大动力

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值