`
jakielong
  • 浏览: 229157 次
  • 性别: Icon_minigender_1
  • 来自: 北京
社区版块
存档分类
最新评论

零拷贝与 sendfile

阅读更多

最近在搞zerocopy的东西,看到了这篇文章,拷过来备用!

转自:http://www.groad.net/bbs/simple/?t2276.html

 

本文整理来自:http://www.linuxjournal.com/article/6345?page=0,0

一、什么是“零拷贝”

零拷贝(zero-copy)基本思想是:数据报从网络设备到用户程序空间传递的过程中,减少数据拷贝次数,减少系统调用,实现CPU的零参与,彻底消除CPU在这方面的负载。实现零拷贝用到的最主要技术是DMA数据传输技术和内存区域映射技术。
先看普通网络服务守护进程的一般服务方法:

 

read(file, tmp_buf, len);
write(socket, tmp_buf, len);

 表面上看来,系统的负荷似乎只是两个系统调用,而没什么开销。如果这么认为,那么这就大错特错了。在这两个函数的背后,数据至少已经被复制了 4 次,以及几乎一样的用户空间到内核空间的切换次数(上下文切换)。下图可以很好的说明这一个过程:

分析上图中的步骤:

第一步,系统调用 read() 导致了用户空间到内核空间的切换。这时,DMA模块将磁盘上的文件内容拷贝(CMA COPY)到内核缓冲区,这就完成了第 1 次复制。

第二步,将内核缓冲区中的内容拷贝到用户缓冲区(kernel buffer --> user buffer),这样又导致了内核空间到用户空间的上下文切换。这时,我们需要的数据已经存放在 tmp_buf 中,这是第 2 次复制。

第三步,在调用 write() 时,又导致了用户空间到内核空间的上下文切换。数据从用户空间缓冲区再次被复制到内核空间缓冲区。这时完成了第 3 次的复制。这里需要注意的是,这次所用的内核缓冲区是与 socket 相关的缓冲区,而非“第一步”中的缓冲区。

第四步,write() 系统调用返回,这时导致了第 4 次的上下文切换。这一次是 DMA 模块将内核中的数据(socket buffer)拷贝到协议引擎中。这些动作是与代码的执行是独立并且是异步发生的。反过来说,假如是非独立且同步的,那么函数返回时那么数据也同时被发送。事实上并非如此,函数返回并不能说明数据被发送出去了,甚至是 write() 的返回都无法保证传输的开始!为什么这么说呢?这是因为,调用的返回,只是表明以太网驱网卡的动程序在其传输队列中有空位,并且已经接受我们的数据用于传输。这时候,有一种情况可能是,还有许多数据还排在我们这些数据的前头,除非我们的数据拥有特权或优先级很高(如果以太网驱动程序是采用队列优先级的方法来发送数据且我们的数据的优先级确实很高),否则数据按照 FIFO 这种先进先出的方法传送。所以在上图中,红色虚线箭头正表示了我们的传输可能发生延迟,若是实线,则刚好发送而没延迟。


正如上面所分析,整个过程中存在着数据冗余。某些冗余可以消除以减少开销并提升性能。有些硬件支持完全绕开内存,可以将数据直接传递给其它设备,这样的特性避免了系统内存中的数据副本,虽然这是一种很好的选择,但并非所有的硬件都能如此。此外,来自硬盘的数据必须重新打包(地址连续)才能用于网络传输,这让情况也会变得有些复杂。

为了减少开销,我们就从消除内核缓冲区和用户缓冲区之间的复制入手。

消除的一种方法就是使用 mmap() 系统调用来替代 read() 系统调用,例如:

 

tmp_buf = mmap(file, len);
write(socket, tmp_buf, len);

 工作原理如下图:

从上图可见:

第一步,调用 mmap() 后,文件的内容通过 DMA 模块复制到内核缓冲区中,该缓冲区之后与用户进程共享,这样一来,就无需再进行内核缓冲区和用户缓冲区的切换,也就是它们之间的复制就不会再发生。

第二步,在调用 write() 后,内核缓冲区中的数据被复制到与 socket 相关联的内核缓冲区中。

第三步,DMA 模块将数据由 socket 的缓冲区复制到协议引擎,这时第 3 次复制发生。


通过调用 mmap() 而不是 read(),我们已经将内核需要执行的复制操作减半。当有大量的数据传输时,有相当好的效果。但是性能改进的同时,也潜藏着一定的代价与陷阱。比如,在对文件进行内存映射后调用 write(),而这时有另外一个进程将映射的文件截断,此时 write() 系统调用会被进程接收到的 SIGBUS 信号而中断,SIGBUS 信号往往意味着尝试进行非法地址访问。对 SIGBUS 信号的默认处理方式是杀死当前进程并生成 core dump 文件 -- 这对于网络服务器来说是极不期望的!

有两种方式可以解决该问题:

第一种是为 SIGBUS 信号设置处理程序,并在处理中简单的执行 return 语句。这样,write() 系统调用将返回被信号中断前已写的字节数,同时设置 errno 变量。但是这样的做法并不值得鼓励,因为收到 SIGBUS 信号意味着发生了严重错误!

第二种是采用文件租约的方式。文件租约是指,通过对文件描述符执行租借,你可以和内核对某个文件达成租约,从内核可以获得读/写租约。当另外的一个进程试图将你正在传输的文件截断时,内核会向你发出实时信号 RT_SIGNAL_LEASE 。该信号通知你的进程,内核即将终止你在该文件上曾经获得的租约。这样,当 write() 访问非法地址时,并即被随后到来的 SIGBUS 杀诉之前,write() 系统调用会被 RT_SIGNAL_LEASE 信号中断。write() 的返回值就是被中断之前已写的字节数,全局变量 errno 设置为成功。下面是一段示例租约的代码:

 

if(fcntl(fd, F_SETSIG, RT_SIGNAL_LEASE) == -1) {
    perror("kernel lease set signal");
    return -1;
}
/* l_type can be F_RDLCK F_WRLCK */
if(fcntl(fd, F_SETLEASE, l_type)){
    perror("kernel lease set type");
    return -1;
}

 在文件进行映射之前 (通过 mmap() 系统调用),应该先获得租约,并在 write() 结束之后结束租约。这通过在 fcntl() 函数中指定租约类型为 F_UNLCK 来实现。


sendfile

sendfile() 的目的是简化通过网络在两个本地之间的数据传输过程。sendfile() 系统调用的引入,不仅减少了数据的复制,还减少了上下文切换的次数。 使用方法如下:

 

#include <sys/sendfile.h>
ssize_t sendfile(int out_fd, int in_fd, off_t *offset, size_t count);

 它的原理如下图所示:

在上图中,

第一步,sendfile() 导致文件内容通过 DMA 模块复制到某个内核缓冲区,之后被复制到与 soket 相关联的的缓冲区中。

第二步,DMA 模块将 socket 缓冲区中的数据复制到协议亲引擎,这时进行第 3 次复制。

在调用 sendfile() 期间,如果有另外一个进程将文件截断,且进程没有为 SIGBUS 注册任何的信号处理函数时,sendfile() 调用仍会返回进程被信号中断前已发送的字节数,并将全局变量 errno 设置为成功。然而,类似的,如果在调用 sendfile() 前,从内核里获得了文件租约,那么 sendfile() 在返回前也会收到 RT_SIGNAL_LEASE。

到此为止,我们已经能够避免内核的多次复制了,然而我们还存在一个多余的副本,这里就是 socket buffer。那么,这个副本是否可以消除? 确实可以,但这需要特定硬件的支持。为了消除内核产生的冗余数据,需要网络适配器支持聚合操作特性。该特性被支持意味着要发送的数据无须存放在地址连续的内存空间中,相反可以存放在各个内存位置。在 2.4 版本的内核中,socket 缓冲区描述符发生了变动,以适合聚合(将零散分布的数据聚合起来发送)操作的要求 --这就是 Linux 中所谓的“零拷贝”。这种方式不但减少了多个上下文的切换,而且消除了数据冗余。从用户层程序的角度来看,没有发生任何改动,所有的代码还是和以前一样。这里所描述原理如下图所示:

在上图中,

第一步,sendfile() 系统调用导致 DMA 模块将文件内容复制到内核缓冲区中。

第二步,数据并未复制到 socket 缓冲区中,取而代之的是,只有记录数据位置和长度的描述符被加入到 socket 缓冲区中。DMA 模块将内核缓冲区中的数据传递给协议引擎,从而消除了遗留的最后一次复制。

由于数据实际上仍然要通过从硬盘拷贝到内存,再由内存再发送到设备,有人可能会觉得这不是真正的“零拷贝”。然而,从操作系统的角度来看,这就是“零拷贝”,因为内核空间不存在冗余数据(既不会存在将数据存放内核一块数据缓冲区中,又将数据存放在 socket 缓冲区中)。应用“零拷贝”特性,除了避免了重复复制外,还可以获得其他性能的优势,例如更少的上下文切换,更少的 CPU cache污染和没有必要的 CPU 必要校验。

 

 

注:
在我的实验中,但存用 write()/read() 拷贝一个 1G 的数据,和用 sendfile() 拷贝同样大小的数据,并未见有什么性能和时间上的优势。详细举例见:http://www.groad.net/bbs/read.php?tid-2308.html

例子放到了下一篇文章!

分享到:
评论

相关推荐

    零拷贝源代码

    2. **sendfile()系统调用**:这是另一个实现零拷贝的关键技术。sendfile()允许内核直接将文件数据从文件描述符传递到网络发送缓冲区,无需经过用户空间。数据仅在内核空间内部进行一次拷贝,极大地提高了I/O性能。 ...

    sendfile 示例代码

    - 支持零拷贝:在某些情况下,操作系统可以实现零拷贝,即数据直接从磁盘传输到网络,不经过任何额外的内存缓冲区。 4. **使用场景** - HTTP服务器:将文件直接从磁盘发送到客户端,无需先读取到内存再写入网络。...

    Linux零拷贝原理.pdf

    Linux零拷贝原理涵盖了操作系统中I/O操作的高效数据传输机制,该原理利用了现代操作系统内核和硬件的能力,减少了数据在系统调用间拷贝的次数,从而提高了数据传输的效率和性能。接下来将详细阐述相关知识点。 零...

    程序员面试之零拷贝技术解析.pdf,这是一份不错的文件

    sendfile是一种高效的零拷贝技术,它能够代替传统的read和write系统调用。通过sendfile,数据可以直接从文件描述符到socket描述符的传输,中间通过DMA完成,避免了用户空间和内核空间之间的多次数据拷贝。这种方式...

    spring-boot-protocol:springboot功能扩展-netty动态协议,可以支持各种网络协议的动态切换(单端口支持多个网络协议)。支持mmap,sendfile零拷贝,http请求批量聚合

    用Netty实现的Spring-boot-protocol将springboot的WebServer更改为NettyTcpServer,为用户扩展了网络编程的能力。...sendFile, mmap. 示例:com.github.netty.http.example.HttpZeroCopyController.java4.HttpServlet

    04零拷贝:如何高效地传输文件1

    零拷贝的关键在于使用特定的系统调用,如Linux下的sendfile或readv/writev。这些调用可以直接将文件描述符和socket句柄传入,让内核在读取文件后,直接将PageCache中的内容拷贝到Socket缓冲区,然后由网卡通过DMA...

    NIO与零拷贝1

    【NIO与零拷贝1】中的主要知识点集中在操作系统层面的效率优化,特别是针对服务器网络编程中的数据传输。零拷贝技术是提升性能的关键,它减少了数据在内存中不必要的复制,降低了CPU的负担。 1. **传统数据读写流程...

    NIO与零拷贝_javanio_nio和零拷贝_

    总的来说,Java NIO与零拷贝技术的结合,为开发者提供了更高效率、更低资源消耗的I/O处理手段,尤其在处理大数据和高并发场景时,其优势更为明显。理解并掌握这些知识,对于提升Java应用程序的性能至关重要。

    Linux中的零拷贝技术

    【零拷贝技术详解】 零拷贝(Zero-Copy)技术是Linux操作系统中为了提高数据传输效率、降低CPU开销而引入的一种优化策略。在传统的Linux I/O操作中,数据在用户空间和内核空间之间频繁拷贝,这不仅消耗了大量的CPU...

    零拷贝原理1

    sendfile系统调用就是一种零拷贝实现,它避免了数据从内核空间到用户空间再到内核空间的两次拷贝。在sendfile中,数据从磁盘通过DMA拷贝到内核缓冲区,然后直接通过DMA拷贝到协议引擎,减少了CPU参与的数据复制。从...

    Linux 中的零拷贝技术,第 2 部分1

    在Linux系统中,有两种主要的零拷贝技术:mmap()和sendfile()。 1. **mmap()技术**: - **工作原理**:mmap()是内存映射文件的系统调用,允许应用程序直接访问文件在内核缓冲区中的内容,减少了数据在用户空间和...

    14. Linux零拷贝1

    Linux零拷贝是一种优化技术,主要用于提高文件在网络中传输的效率。在传统的文件传输过程中,数据需要经过多次拷贝,包括从磁盘到内核缓冲区、从内核缓冲区到用户空间、再从用户空间回内核空间,最后到网络发送缓冲...

    Linux - 零拷贝技术.pdf

    零拷贝技术主要包含两种实现方式:mmap + write和sendfile。mmap允许用户进程映射文件到其地址空间,这样在写入时可以直接将数据写入文件,避免了数据在用户空间和内核空间之间的拷贝。sendfile则允许操作系统直接将...

    蚂蚁二面,面试官问我零拷贝的实现原理,当场懵了…1

    零拷贝(Zero-Copy)是一种优化数据传输的技术,它主要应用于网络通信和文件系统中,目的是减少CPU的参与和减少不必要的数据复制,从而提高系统的效率和吞吐量。在传统的数据传输过程中,数据需要在用户空间和内核...

    Linux 中的零拷贝技术,第 1 部分1

    【零拷贝技术概述】 零拷贝(Zero-Copy)技术是Linux操作系统中为了提高数据传输效率而设计的一种机制,特别是在处理大流量网络通信和I/O密集型应用时,能够显著提升性能。传统的数据传输方式涉及到多次数据在不同...

    Linux环境下普适性零拷贝平台的研究与实现.pdf

    2. **sendfile()**:这是Linux内核提供的一种高效文件传输机制,用于服务器向客户端发送文件。它利用DMA(Direct Memory Access)直接从内核的文件缓冲区将数据传输到网络设备,无需经过用户空间。 3. **Kernel ...

    理解Netty中的零拷贝(Zero-Copy)机制1

    在Linux操作系统中,`sendfile()`系统调用是一个典型的零拷贝实现,它允许数据直接从文件描述符传输到网络套接字,避免了用户空间和内核空间的数据复制。在Java的NIO(非阻塞输入/输出)中,`FileChannel.transferTo...

    浅析Linux中的零拷贝技术的使用

    sendfile()是另一种实现零拷贝的方法,它允许直接将文件内容从一个文件描述符(通常是磁盘文件)传输到另一个文件描述符(如socket)。sendfile()避免了数据在用户空间和内核空间之间的拷贝,但仍然利用DMA将数据从...

    《学习资料》--springboot支持各种网络协议的动态切换.支持mmap,sendfile零拷贝.zip

    个人花大量时间整理出的实战资料,内容丰富,文档也很详细。无论做毕业设计还是用于学习技能,或工作中当做参考资料,都能发挥重要作用 亲们下载我任何一个付费资源后,即可私信联系我免费下载其他相关资源哦~ ...

Global site tag (gtag.js) - Google Analytics