《TCP/IP详解 卷2:实现》 —2.9 m_copy和簇引用计数

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

2.9   m_copy和簇引用计数

使用簇的一个明显好处就是在要求包含大量数据时能减少mbuf的数目。例如,如果不使用簇,要有10个mbuf才能包含1024字节的数据:第一个mbuf带有100字节的数据,后面8个每个存放108字节数据,最后一个存放60字节数据。分配并链接10个mbuf比分配一个包含1024字节簇的mbuf开销要大。簇的一个潜在缺点是浪费空间。在我们的例子中使用一个簇(2048 + 128)要2176字节,而1280字节不到1簇(10×128)。

簇的另外一个好处是在多个mbuf间可以共享一个簇。在TCP输出和m_copy函数中我们遇到过这种情况,但现在我们要更详细地说明这个问题。

例如,假设应用程序执行一个write,把4096字节写到TCP插口中。假设插口的发送缓存原来是空的,接收窗口至少有4096,则会发生以下操作。插口层把前2048字节的数据放在一个簇中,并且调用协议的发送例程。TCP发送例程把这个mbuf追加到它的发送缓存,如图2-24所示,并调用tcp_output。结构socket中包含sockbuf结构,这个结构中存储着发送缓存mbuf链的链表表头:so_snd.sb_mb。

image.png

图2-24   包含2048字节数据的TCP插口发送缓存

对这个连接(典型的是以太网)而言,假设一个TCP最大报文段大小(MSS)为1460,tcp_output建立一个报文段来发送包含前1460字节的数据。它还建立一个包含IP和TCP首部的mbuf,为链路层首部(16字节)预留了空间,并将这个mbuf链传给IP输出。在接口输出队列尾部的mbuf链显示在图2-25中。

在1.9节的UDP例子中,UDP用mbuf链来存放数据报,在前面添加一个mbuf来存放协议首部,并把此链传给IP输出。UDP并不把这个mbuf保存在它的发送缓存中。而TCP不能这样做,因为TCP是一个可靠协议,并且它必须维护一个发送数据的副本,直到数据被对方确认。

在这个例子中,tcp_output调用函数m_copy,请求复制1460字节的数据,从发送缓存起始位置开始。但由于数据被存放在一个簇中,m_copy创建一个mbuf(图2-25的右下侧)并且对它初始化,将它指向那个已存在的簇的正确位置(此例中是簇的起始处)。这个mbuf的长度是1460字节,虽然有另外588字节的数据在簇中。我们所示的这个mbuf链的长度是1514,包括以太网首部、IP首部和TCP首部。

在图2-25的右下侧我们还显示了这个mbuf包含一个分组首部,但它不是链中的第一个mbuf。当m_copy复制一个包含分组首部的mbuf并且从原来mbuf的起始地址开始复制时,分组首部也被复制下来。因为这个mbuf不是链中的第一个mbuf,这个额外的分组首部被忽略。而在这个额外的分组首部中的m_pkthdr.len的值2048也被忽略。

image.png

图2-25   TCP插口发送缓存和接口输出队列中的报文段

这个共享的簇避免了内核将数据从一个mbuf复制到另一个mbuf中—这节约了很多开销。它是通过为每个簇提供一个引用计数来实现的,每次另一个mbuf指向这个簇时计数加1,当一个簇释放时计数减1。仅当引用计数到达0时,被这个簇占用的存储器才能被其他程序使用(见习题2.4)。

例如,当图2-25底部的mbuf链到达以太网设备驱动程序并且它的内容已被复制给这个设备时,驱动程序调用m_freem。这个函数释放带有协议首部的第一个mbuf,并注意到链中第二个mbuf指向一个簇;簇引用计数减1,但由于它的值变成了1,它仍然保存在存储器中;它不能被释放,因为它仍在TCP发送缓存中。

image.png

图2-26   用于发送1460字节TCP报文段的mbuf链

继续我们的例子,由于在发送缓存中剩余的588字节不能组成一个报文段,tcp_output在把1460字节的报文段传给IP后返回(在第26章我们会说明在这种条件下tcp_output发送数据的细节)。插口层继续处理来自应用程序的数据:剩下的2048字节被存放到一个带有一个簇的mbuf中,TCP发送例程再次被调用,并且新的mbuf被追加到插口发送缓存中。因为能发送一个完整的报文段,tcp_output建立另一个带有协议首部和1460字节数据的mbuf链表。m_copy的参数指定了1460字节的数据在发送缓存中的起始位移和长度(1460字节)。这显示在图2-26中,并假设这个mbuf链在接口输出队列中(这个链中的第一个mbuf的长度反映了以太网首部、IP首部及TCP首部)。

这次1460字节的数据来自两个簇:前588字节来自发送缓存的第一个簇,而后面的872字节来自发送缓存的第二个簇。它用两个mbuf来存放1460字节,但m_copy还是不复制这1460字节的数据—它引用已存在的簇。

这次我们没有在图2-26右下侧的任何mbuf中显示分组首部。原因是调用m_copy的起始位移为零。但在插口发送缓存中的第二个mbuf包含一个分组首部,而不是链中的第一个mbuf。这是函数sosend的特点,这个额外的分组首部被简单地忽略了。

我们通篇会多次遇到函数m_copy。虽然这个名字隐含着对数据进行物理复制,但如果数据被包含在一个簇中,则是仅引用这个簇而不是复制。


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

评论(0

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

全部回复

上滑加载中

设置昵称

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

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

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