当前位置: 首页 > 科技观察

零拷贝技术让我很困惑...

时间:2023-03-19 13:00:13 科技观察

注:除了DirectI/O,页面缓存技术还用于磁盘相关的文件读写操作。图片来自趵突网的四份数据和四次上下文切换。很多应用程序在面对客户端请求时可以等效为如下系统调用:File.read(file,buf,len)Socket.send(socket,buf,len)比如消息中间件Kafka就是这种应用场景。从磁盘读取一批消息后,写入网卡(NIC,Networkinterfacecontroller)进行传输。在不使用任何优化技术的后台,操作系统将为此执行4次数据复制和4次上下文切换。如下图:如果不做优化,读取磁盘数据再通过网卡传输的场景性能比较差。4份:CPU负责将数据从磁盘移动到内核空间的PageCache中。CPU负责将数据从内核空间的Socket缓冲区移动到网络中。CPU负责将数据从内核空间的PageCache移动到用户空间的buffer中。CPU负责将数据从用户空间的缓冲区移动到内核空间的套接字缓冲区。4上下文切换:当read系统被调用时:用户态切换到内核态。read系统调用完成:内核态切换回用户态。write系统调用时:用户态切换到内核态。write系统调用完成:内核态切换回用户态。我们不禁抱怨:CPU全程负责在内存中拷贝数据是可以接受的,因为效率是可以接受的,但如果CPU负责从内存、磁盘、和网络贯穿整个过程,因为磁盘和网卡的速度远高于CPU。比内存小,内存比CPU小很多。4个副本太多了,4个上下文切换太频繁了。有DMA参与的四次数据复制DMA技术很容易理解。本质上,DMA技术就是我们在主板上放了一个独立的芯片。在内存和I/O设备之间传输数据时,我们不再通过CPU来控制数据传输,而是直接通过DMA控制器(DMAController,简称DMAC)来控制。这个芯片,我们可以把它想象成一个协处理器(Co-Processor)。DMAC最有价值的地方就是当我们要传输的数据特别大特别快,或者要传输的数据很小特别慢的时候。比如我们在使用千兆网卡或者硬盘传输大量数据的时候,如果用CPU来承载的话,肯定会忙不过来,所以可以选择DMAC。当数据传输很慢时,DMAC可以等待数据到达,然后发送信号给CPU进行处理,而不是让CPU在那里等待。请注意这里的“协会”一词。DMAC是在“协助”CPU完成相应的数据传输工作。在DMAC控制数据传输的过程中,我们仍然需要CPU来控制,但是具体数据的拷贝已经不再由CPU来完成了。本来计算机各部件之间的数据拷贝(流)都要经过CPU,如下图所示:网卡。CPU作为DMA的控制器,如下图所示:但DMA也有其局限性。DMA只能用于设备间交换数据时的数据拷贝,而设备内部的数据拷贝也需要CPU。比如CPU需要负责内核空间数据和用户空间数据之间的拷贝(内存内部拷贝),如下图所示:上图中的readbuffer也是pagecache,而套接字缓冲区也是套接字缓冲区。零拷贝技术什么是零拷贝技术?零拷贝技术是一种思想,意思是当计算机执行一个操作时,CPU不需要先将数据从某个内存拷贝到另一个特定区域。可以看出,零拷贝的特点是CPU不负责将内存中的数据写入其他组件,CPU只起到管理作用。但是要注意,零拷贝并不是说不进行拷贝,而是CPU不再负责数据拷贝的处理。如果数据本身不在内存中,首先要通过某种方式将其复制到内存中(CPU不需要参与这个过程),因为数据只能传输,直接由CPU读取和计算如果它在内存中。零拷贝技术的具体实现方式有很多,例如:sendfilemmapspliceDirectDirectI/O不同的零拷贝技术适用于不同的应用场景。下面依次分析sendfile、mmap、DirectI/O。但是,出于总结的目的,我们在这里对以下技术进行前瞻性总结。DMA技术回顾:DMA负责内存与其他部件之间的数据拷贝,CPU只需要负责管理即可,不需要负责数据拷贝的整个过程。使用pagecache的零拷贝:sendfile:替换一次read/write系统调用,利用DMA技术,传递文件描述符实现零拷贝。mmap:只代替read系统调用,将内核空间地址映射到用户空间地址,write操作直接作用于内核空间。通过DMA技术和地址映射技术,用户空间和内核空间不需要拷贝数据,实现零拷贝。DirectI/Owithoutpagecache:直接在磁盘上进行读写操作,不使用pagecache机制,通常结合用户空间的usercache。通过DMA技术,直接与磁盘/网卡交互,实现零拷贝。①sendfilesnedfile的应用场景是:用户从磁盘中读取一些文件数据,然后通过网络传输,不经过任何计算和处理。此场景的典型应用是消息队列。在传统I/O下,如第一节所示,上述应用场景中的一次数据传输需要四个CPU-only副本和四个上下文切换,如本文第一节所述。sendfile主要使用了两种技术:DMA技术。传递文件描述符而不是数据副本。下面依次说明这两种技术的作用。利用DMA技术:sendfile依靠DMA技术将整个过程中CPU负责的复制和四次上下文切换减少到两倍。如下图所示:使用DMA技术减少CPU参与整个过程的2次拷贝。DMA负责将数据从磁盘拷贝到内核空间的Pagecache(readbuffer)和从内核空间的socketbuffer拷贝数据到网卡。传递文件描述符而不是数据副本:传递文件描述符可以替代数据副本有两个原因。如下:pagecache和socketbuffer都在内核空间。数据传输过程前后没有写操作。用传递文件描述符代替内核空间的数据复制注意:只有支持SG-DMA(TheScatter-GatherDirectMemoryAccess)技术的网卡才能通过传递文件描述符避免内核空间的CPU复制。也就是说这个优化取决于Linux系统的物理网卡是否支持(Linux在2.4内核版本中引入了DMA的scatter/gather——scatter/gather功能,只要确保Linux版本高于2.4即可)。一次系统调用代替两次系统调用:由于sendfile只对应一次系统调用,传统的文件操作需要两次系统调用,read和write。正因为如此,sendfile可以将用户态和内核态的上下文切换从4次减少到2次。sendfile系统调用只需要两次上下文切换另一方面,我们需要了解sendfile系统调用的局限性。如果应用程序需要写入从磁盘读取的数据,例如解密或加密,那么sendfile系统调用就完全没用了。这是因为用户线程根本无法通过sendfile系统调用获取传输的数据。②mmapmmap技术在本文[1]中单独开发,请按部就班阅读。③DirectI/ODirectI/O即直接I/O,名字中的“direct”二字是为了区分使用pagecache机制的cachedI/O:CachedfileI/O:用户空间不需要读取和读取writeafile是直接和磁盘交互的,但是中间有一层缓存,也就是页缓存。直接文件I/O:用户空间读取的文件直接与磁盘交互,无需中间页面缓存层。“Direct”在这里还有另外一层语义:在所有其他技术中,数据都需要在内核空间至少存储一份,但在DirectI/O技术中,数据绕过内核,直接存储在用户空间。DirectI/O方式如下图所示:DirectI/O示意图此时,用户空间通过DMA直接向磁盘和网卡拷贝数据。DirectI/O的读写有非常特殊的特点:写操作:由于不使用pagecache,所以写文件。如果返回成功,数据就真的放到了磁盘上(不管磁盘自带的缓存是什么)。读操作:由于没有使用pagecache,所以每次读操作实际上都是从磁盘中读取,而不是从文件系统的缓存中读取。事实上,即使是DirectI/O也可能仍然需要使用操作系统的fsync系统调用。为什么?这是因为虽然文件的数据本身没有使用任何缓存,但是文件的元数据还是需要缓存的,包括VFS中的inode缓存和dentry缓存。在某些操作系统中,DirectI/O模式下的write系统调用可以保证文件数据写入磁盘,但文件元数据不一定写入磁盘。如果在这样的操作系统上,还需要一个fsync系统调用来确保文件元数据也被刷新。否则可能会导致文件异常、元数据不一致等问题。MySQL的O_DIRECTvs.O_DIRECT_NO_FSYNC配置是一个特例。DirectI/O的优缺点如下:优点:Linux中的直接I/O技术省略了缓冲I/O技术中操作系统内核缓冲区的使用,数据直接在应用程序地址之间传输空间和磁盘,从而自缓存应用程序可以省去复杂的系统级缓存结构,实现程序自身定义的数据读写管理,从而减少系统级管理对应用程序访问数据的影响.与其他零拷贝技术一样,避免了将数据从内核空间复制到用户空间。如果传输的数据量很大,直接I/O方式进行数据传输,不需要操作系统内核地址空间来进行数据拷贝操作。参与,这将大大提高性能。缺点:由于设备间的数据传输是通过DMA来完成的,所以用户空间的数据缓冲内存页必须是pagepinned(pagelocked),这是为了防止其物理页框地址被交换到磁盘或移动到新的address导致DMA拷贝数据找不到指定地址的内存页,从而导致pagefault,而pagelocking的开销不小于CPU拷贝,所以为了避免频繁的pagelockingsystemcalls,应用程序必须为数据缓冲分配并注册一个持久内存池。如果访问的数据不在应用程序缓存中,那么每次都会直接从磁盘加载数据,这种直接加载会很慢。在应用层引入直接I/O需要应用层自己管理,这就引入了额外的系统复杂度。谁会使用直接I/O?一篇IBM文章[2]指出,自缓存应用程序可以选择使用直接I/O。自缓存应用程序:对于某些应用程序,它会有自己的数据缓存机制。例如,它将数据缓存在应用程序地址空间中。这样的应用程序根本不需要使用操作系统内核中的缓存内存。此类应用程序称为自缓存应用程序(self-cachingapplications)。例如,应用程序维护一个缓存空间。当有读操作时,首先读取应用层缓存的数据。如果没有,则通过DirectI/O直接通过磁盘I/O读取数据。还是应用了缓存,但是应用觉得自己实现一个缓存比操作系统的缓存效率更高。数据库管理系统是此类应用程序的代表。自缓存应用程序倾向于使用数据的逻辑表示而不是物理表示;当系统内存不足时,自缓存应用程序会导致此类数据的逻辑缓存被换出,而不是磁盘上的实际数据被换出。自缓存应用程序完全了解它所操作的数据的语义,因此它可以使用更有效的缓存替换算法。一个自缓存应用可能会在多台主机之间共享一块内存,所以自缓存应用需要提供一种机制可以有效的使用户地址空间中的缓存数据失效,从而保证应用地址空间缓存数据一致性。另一方面,目前Linux上依赖文件以O_DIRECT方式打开的异步IO库,经常一起使用。如何使用直接I/O?用户应用需要在用户空间实现一个缓存区,读写操作尽量通过这个缓存区提供。如果有性能方面的考虑,尽量避免基于DirectI/O的频繁读写操作。典型案例①KakfaKafka是一个消息队列,涉及到磁盘I/O的操作主要有两个:Provider向Kakfa发送消息,Kakfa负责将消息以日志的形式持久化放置。Consumer从Kakfa拉取消息,Kafka负责从磁盘读取一批日志消息,然后通过网卡发送。Kakfa服务器在接收Provider消息并持久化的场景下使用了mmap机制,可以提供基于顺序磁盘I/O的高效持久化能力。使用的Java类是java.nio.MappedByteBuffer。sendfile机制用于Kakfa服务端向消费者发送消息的场景。这种机制有两个主要优点:sendfile避免了从内核空间到用户空间CPU的数据移动。sendfile是基于PageCache实现的,所以如果多个Consumers同时消费一个topic的消息,由于消息已经缓存在pagecache中,那么多个Consumers可以只用一个磁盘I/O服务。使用mmap持久化接收到的数据,使用sendfile从持久化介质中读取数据然后发送到外部是一种常见的组合。但是注意不能使用sendfile来持久化数据,而使用mmap来实现CPU全程不参与数据处理的数据拷贝。②MySQLMySQL的具体实现要比Kakfa复杂的多,因为支持SQL查询的数据库本身就比消息队列复杂的多。MySQL的零拷贝技术的使用请移步我的另一篇文章[3]。总结DMA技术的引入,使得内存和磁盘、网卡等其他部件复制数据,CPU只需要发送一个控制信号,复制数据的过程就由DMA完成。Linux零拷贝技术的实现策略有很多,但是按照策略可以分为以下几种:减少甚至避免用户空间和内核空间之间的数据拷贝:在某些场景下,用户进程不需要数据被访问和处理。那么就可以完全避免LinuxPageCache和用户进程的buffer之间的数据传输,从而使数据拷贝完全在内核中进行,甚至可以更巧妙的避免内核中的数据拷贝.这种实现一般是通过增加新的系统调用来实现的,比如Linux中的mmap()、sendfile()、splice()等。DirectI/Obypassingthekernel:允许用户态进程绕过内核,直接向硬件传输数据。内核只负责传输过程中的一些管理和辅助工作。这种方法其实和第一种有点类似,也是尽量避免用户空间和内核空间之间的数据传输,但是第一种方法是在内核态完成数据传输过程,而这种方法直接绕过了Kernel和硬件通信,效果类似但原理完全不同。内核缓冲区和用户缓冲区之间的传输优化:这种方法侧重于优化用户进程缓冲区和操作系统页面缓存之间的CPU副本。这种方式延续了过去传统的通讯方式,但更加灵活。相关链接:https://spongecaptain.cool/SimpleClearFileIO/3.%20mmap.htmlhttps://www.ibm.com/developerworks/cn/linux/l-cn-direction/https://spongecaptain.cool/zerocopyofmysql作者:SpongeCaptain主编:陶佳龙来源:http://33h.co/w3kdh