《TCP/IP详解 卷2:实现》 —2 mbuf:存储器缓存

举报
华章计算机 发表于 2019/11/20 16:11:48 2019/11/20
【摘要】 本节书摘来自华章计算机《TCP/IP详解 卷2:实现》一书中第2章,第2.1节,作者是[美]加里R.赖特(Gary R.Wright),W. 理查德史蒂文斯(W.Richard Stevens),陆雪莹 蒋慧 等译 谢希仁 校。

第2章   mbuf:存储器缓存

2.1   引言

联网协议对内核的存储器管理能力提出了很多要求。这些要求包括能方便地操作可变长缓存,能在缓存头部和尾部添加数据(如低层封装来自高层的数据),能从缓存中移去数据(如当数据分组向上经过协议栈时要去掉首部),并能尽量减少为这些操作所做的数据复制。内核中的存储器管理调度直接关系到联网协议的性能。

在第1章我们介绍了普遍应用于Net/3内核中的存储器缓存—mbuf,它是“memory buffer”的缩写。在本章,我们要查看mbuf和内核中用于操作它们的函数的更多细节,在本书中几乎每一页我们都会遇到mbuf。要理解本书的其他部分必须先要理解mbuf。

mbuf的主要用途是保存在进程和网络接口间互相传递的用户数据。但mbuf也用于保存其他各种数据:源与目标地址、插口选项等等。

图2-1显示了我们要遇到的四种不同类型的mbuf,它们依据在成员m_flags中填写的不同标志M_PKTHDR和M_EXT而不同。图2-1中四个mbuf的区别从左到右罗列如下:

1) 如果m_flags等于0,mbuf只包含数据。在mbuf中有108字节的数据空间(m_dat数组)。指针m_data指向这108字节缓存中的某个位置。图中所示的m_data指向缓存的起始,但它也能指向缓存中的任意位置。成员m_len指示了从m_data开始的数据的字节数。图1-6是这类mbuf的一个例子。

在图2-1中,结构m_hdr中有六个成员,它的总长是20字节。当我们查看此结构的C语言定义时(图2-8),会看见前四个成员每个占用4字节而后两个成员每个占用2字节。在图2-1中我们没有区分4字节成员和2字节成员。

2) 第二类mbuf的m_flags值是M_PKTHDR,它指示这是一个分组首部,描述一个分组数据的第一个mbuf。数据仍然保存在这个mbuf中,但是由于分组首部占用了8字节,只有100字节的数据可存储在这个mbuf中(在m_pktdat数组中)。图1-10是这种mbuf的一个例子。

成员m_pkthdr.len的值是这个分组的mbuf链表中所有数据的总长度:所有通过m_next指针链接的mbuf的m_len值的和,如图1-8所示。输出分组没有使用成员m_pkthdr.rcvif,但对于接收的分组,它包含一个指向接收接口ifnet结构(图3-6)的指针。

3) 下一种mbuf不包含分组首部(没有设置M_PKTHDR),但包含超过208字节的数据,这时用到一个叫“簇”的外部缓存(设置M_EXT)。在此mbuf中仍然为分组首部结构分配了空间,但没有用—在图2-1中,我们用阴影显示出来。Net/3分配一个大小为1024字节或2048字节的簇,而不是使用多个mbuf来保存数据(第一个带有100字节数据,其余的每个带有108字节数据)。在这个mbuf中,指针m_data指向这个簇中的某个位置。

Net/3版本支持七种不同的结构。定义了四种1024字节的簇(惯例值),三种2048字节的簇。习惯上用1024字节的目的是节约存储器:如果簇的大小是2048字节,对于以太网分组(最大1500字节),每个簇大约有四分之一没有用。在27.5节中我们会看到Net/3 TCP发送的每个TCP报文段从来不超过一簇大小,因此当簇的大小为1024字节时,每个1500字节的以太网帧几乎三分之一未用。但是[Mogul 1993,图15-15]显示了当在以太网中发送最大帧而不是1024字节的帧时能明显提高以太网的性能。这就是一种性能/存储器互换。老的系统使用1024字节的簇来节约存储器,而拥有廉价存储器的新系统用2048字节的簇来提高性能。在本书中我们假定一簇的大小是2048字节。

image.png

图2-1   根据不同m_flags值的四种不同类型的mbuf

然而,我们所说的“簇”(cluster)用过不同的名字。常量MCLBYTES是这些缓存(1024或2048)的大小,操作这些缓存的宏的名字是MCLGET、MCLALLOC和MCLFREE。这就是之所以称它们为“簇”的原因。但我们还看到mbuf的标志是M_EXT,它代表“外部的”缓存。最后,[Leffler et al. 1989]称它们为映射页(mapped page)。这后一种称法来源于它们的实现,在2.9节我们会看到当要求一个副本时,这些簇是可以共享的。

我们可能会希望这种类型的mbuf的m_len的最小值是209而不是我们在图中所示的208。这是指,208字节数据的记录是可以存放在两个mbuf中的,第一个mbuf存放100字节,第二个mbuf存放108字节。但在源代码中有一个差错:若超过或等于208就分配一个簇。

4) 最后一类mbuf包含一个分组首部,并包含超过208字节的数据。同时设置了标志M_PKTHDR和M_EXT。

对于图2-1,我们还有另外几点需要说明:

mbuf结构的大小总是128字节。这意味着图2-1右边两个mbuf在结构m_ext后面的未用空间为88(即128-20-8-12)字节。

既然有些协议(例如UDP)允许零长记录,当然就可以有m_len为0的数据缓存。

在每个mbuf中的成员m_data指向相应缓存的开始(mbuf缓存本身或一个簇)。这个指针能指向相应缓存的任意位置,不一定是起始。

image.png

图2-2   在一个队列中的两个分组:第一个带有192字节的数据,第二个带有1514字节的数据

带有簇的mbuf总是包含缓存的起始地址(m_ext.ext_buf)和它的大小(m_ext.ext_size)。我们在本书采用的大小为2048。成员m_data和m_ext.ext_buf的值是不同的(如我们所示),除非m_data也指向缓存的第一个字节。结构m_ext的第三个成员ext_free,Net/3当前未用。

指针m_next把mbuf链接在一起,把一个分组(记录)形成一条mbuf链表,如图1-8所示。

指针m_nextpkt把多个分组(记录)链接成一个mbuf链表队列。在队列中的每个分组可以是一个单独的mbuf,也可以是一个mbuf链表。每个分组的第一个mbuf包含一个分组首部。如果多个mbuf定义一个分组,只有第一个mbuf的成员m_nextpkt被使用—链表中其他mbuf的成员m_nextpkt全是空指针。

图2-2所示的是在一个队列中的两个分组的例子,它是图1-8的一个修改版。我们已经把UDP数据报放到接口输出队列中(显示出14字节的以太网首部已经添加到链表中第一个mbuf的IP首部前面),并且第二个分组已经被添加到队列中:TCP段包含1460字节的用户数据。TCP数据包含在一个簇中,并且有一个mbuf包含了它的以太网、IP与TCP首部。通过这个簇,我们可以看到指向簇的数据指针(m_data)不需要指向簇的起始位置。我们所示的队列有一个头指针和一个尾指针,这就是Net/3处理接口输出队列的方法。我们还给有M_EXT标志的mbuf添加了一个m_ext结构,并且用阴影表示这个mbuf中未用的pkthdr结构。

带有UDP数据报分组首部的第一个mbuf的类型是MT_DATA,但带有TCP报文段分组首部的第一个mbuf的类型是MT_HEADER。这是由于UDP和TCP采用了不同的方式往数据中添加首部造成的,但没有什么不同,这两种类型的mbuf本质上一样。链表中第一个mbuf的m_flags的值M_PKTHDR指示了它是一个分组首部。

仔细的读者可能会注意到我们显示一个mbuf的图(Net/3 mbuf,图2-1)与显示一个Net/1 mbuf的图[Leffler et al. 1989,p.290]的区别。这个变化是在Net/2中造成的:添加了成员m_flags,把指针m_act改名为m_nextpkt,并把这个指针移到这个mbuf的前面。

在第一个mbuf中,UDP与TCP协议首部位置的不同是由于UDP调用M_PREPEND (图23-15和习题23.1)而TCP调用MGETHDR(图26-25)造成的。


【版权声明】本文为华为云社区用户转载文章,如果您发现本社区中有涉嫌抄袭的内容,欢迎发送邮件进行举报,并提供相关证据,一经查实,本社区将立刻删除涉嫌侵权内容,举报邮箱: cloudbbs@huaweicloud.com
  • 点赞
  • 收藏
  • 关注作者

评论(0

0/1000
抱歉,系统识别当前为高风险访问,暂不支持该操作

全部回复

上滑加载中

设置昵称

在此一键设置昵称,即可参与社区互动!

*长度不超过10个汉字或20个英文字符,设置后3个月内不可修改。

*长度不超过10个汉字或20个英文字符,设置后3个月内不可修改。