本文档的copyleft归gfree.wind@gmail.com所有,使用GPL发布,可以*拷贝,转载。但转载时请保持文档的完整性,并注明原作者及原文链接,严禁用于任何商业用途。
作者:gfree.wind@gmail.com
博客:linuxfocus.blog.chinaunix.net
在前文的学习中,在理解Linux对于网卡的Scatter/Gather I/O的支持上,理解并不是清楚。现在基本上已经搞清了,特此写篇文章。
首先,什么是Scatter/Gather I/O?Scatter/Gather I/O是一种与block dma对应的dma传输方式。
在DMA传输数据的过程中,要求源物理地址和目标物理地址必须是连续的。如果物理上不是连续的,则DMA传输要分成多次完成。
如果传输完一块物理连续的数据后,发起另一次中断,通知主机进行下一次传输,这种方式叫做block dma。
而Scatter/Gather方式,则是用一个链表描述物理不连续的存储器,然后把链表首地址告诉dma master。dma master传输完一块物理连续的数据后,就不用再发中断了,而是根据链表传输下一块物理连续的数据。这种方式无疑要比block dma效率高得多。
目前,根据我的理解,在TCP/IP的协议栈中,对于Scatter/Gather I/O的应用,主要是在IP分片和ip_append_data效率的提高上。
首先看一下,当没有Scatter/Gather I/O且没有MSG_MORE标志情况下,buffer的图示。
在L4调用ip_append_data,准备发送一个payload超过PMTU的数据时(假设小于2倍的PMTU)。那么kernel会申请两个sk_buff,第一个的大小是PMTU,第二个的大小为剩下的数据的大小。这时的buffer情况就如上图所示。
我们要知道ip_append_data实际上并不发送数据,那么L4还可能继续追加数据。但是第二个sk_buff的空间却不够。这怎么办呢?只有申请一个新的buffer,将第二个sk_buff的数据复制到这个buffer中,然后用其替换掉原有的第二个sk_buff,并释放掉。可以看出,这样无疑降低了效率。
因此在实际的应用中,为了避免这种低效率的情况,kernel引入了MSG_MORE标志。该标志用于通知L3,即将有更多的数据被append。这时L3分配sk_buffer的情况就与上图不同。
从上图可以看出,第二个的sk_buff的实际数据大小是小于申请的大小的(PMTU)。也就说,L3在为分片申请sk_buff时,始终按照最大的空间(这里是PMTU)申请的。这样当L4继续追加数据时,可以继续填充最后一个sk_buff,从而避免了不必要的复制(如上一种情况)。
如果设备支持Scatter/Gather I/O的话,则是另外一种情况。
a)图中,第一个sk_buff跟不支持Scatter Gather I/O的情况一样,而当再次追加数据时,情况则不同。kernel会申请一个page buffer,将新的数据填入这个page,同时让sk_buff->skb_shingo->frags指向这个page,并置上正确的偏移。frags[0]的偏移是0,frags[1]的偏移则是frags[0]的大小。
通过这样的方法,可以看出Scatter Gather I/O既避免了第一种情况下的低效的分配,复制,也避免了第二种情况下,必须给最后一个sk_buff申请最大的空间的局限,同时还避免了L2和L3头部数据的复制。
注:
1. 本文图片摘自《Understanding Linux Networks Internals》;
2. 内容也来自该书对于Scatter Gather I/O阐述的理解