##PIO与DMA 有必要简单地说说慢速I/O设备和内存之间的数据传输方式。linux
###具体步骤: 当应用程序调用read接口时,操做系统检查在内核的高速缓存有没有须要的数据,若是已经缓存了,那么就直接从缓存中返回,若是没有,则从磁盘中读取,而后缓存在操做系统的缓存中。nginx
应用程序调用write接口时,将数据从用户地址空间复制到内核地址空间的缓存中,这时对用户程序来讲,写操做已经完成,至于何时再写到磁盘中,由操做系统决定,除非显示调用了sync同步命令。 数据库
##内存映射(减小数据在用户空间和内核空间之间的拷贝操做,适合大量数据传输
) Linux内核提供一种访问磁盘文件的特殊方式,它能够将内存中某块地址空间和咱们要指定的磁盘文件相关联,从而把咱们对这块内存的访问转换为对磁盘文件的访问,这种技术称为内存映射(Memory Mapping)。缓存
操做系统将内存中的某一块区域与磁盘中的文件关联起来,当要访问内存中的一段数据时,转换为访问文件的某一段数据。这种方式的目的一样是减小数据从内核空间缓存到用户空间缓存的数据复制操做,由于这两个空间的数据是共享的。服务器
内存映射是指将硬盘上文件的位置与进程逻辑地址空间中一块大小相同的区域一一对应,当要访问内存中一段数据时,转换为访问文件的某一段数据。这种方式的目的一样是减小数据在用户空间和内核空间之间的拷贝操做。当大量数据须要传输的时候,采用内存映射方式去访问文件会得到比较好的效率。网络
使用内存映射文件处理存储于磁盘上的文件时,将没必要再对文件执行I/O操做,这意味着在对文件进行处理时将没必要再为文件申请并分配缓存,全部的文件缓存操做均由系统直接管理,因为取消了将文件数据加载到内存、数据从内存到文件的回写以及释放内存块等步骤,使得内存映射文件在处理大数据量的文件时能起到至关重要的做用。并发
###访问步骤
app
在大多数状况下,使用内存映射能够提升磁盘I/O的性能,它无须使用read()或write()等系统调用来访问文件,而是经过mmap()系统调用来创建内存和磁盘文件的关联,而后像访问内存同样自由地访问文件。 有两种类型的内存映射,共享型和私有型,前者能够将任何对内存的写操做都同步到磁盘文件,并且全部映射同一个文件的进程都共享任意一个进程对映射内存的修改;后者映射的文件只能是只读文件,因此不能够将对内存的写同步到文件,并且多个进程不共享修改。显然,共享型内存映射的效率偏低,由于若是一个文件被不少进程映射,那么每次的修改同步将花费必定的开销。异步
##直接I/O(绕过内核缓冲区,本身管理I/O缓存区
) 在Linux 2.6中,内存映射和直接访问文件没有本质上差别,由于数据从进程用户态内存空间到磁盘都要通过两次复制,即在磁盘与内核缓冲区之间以及在内核缓冲区与用户态内存空间。 引入内核缓冲区的目的在于提升磁盘文件的访问性能,由于当进程须要读取磁盘文件时,若是文件内容已经在内核缓冲区中,那么就不须要再次访问磁盘;而当进程须要向文件中写入数据时,实际上只是写到了内核缓冲区便告诉进程已经写成功,而真正写入磁盘是经过必定的策略进行延迟的。socket
然而,对于一些较复杂的应用,好比数据库服务器,它们为了充分提升性能,但愿绕过内核缓冲区,由本身在用户态空间实现并管理I/O缓冲区,包括缓存机制和写延迟机制等,以支持独特的查询机制,好比数据库能够根据更加合理的策略来提升查询缓存命中率。另外一方面,绕过内核缓冲区也能够减小系统内存的开销,由于内核缓冲区自己就在使用系统内存。
应用程序直接访问磁盘数据,不通过操做系统内核数据缓冲区,这样作的目的是减小一次从内核缓冲区到用户程序缓存的数据复制。这种方式一般是在对数据的缓存管理由应用程序实现的数据库管理系统中。 直接I/O的缺点就是若是访问的数据不在应用程序缓存中,那么每次数据都会直接从磁盘进行加载,这种直接加载会很是缓慢。一般直接I/O跟异步I/O结合使用会获得较好的性能。
###访问步骤
Linux提供了对这种需求的支持,即在open()系统调用中增长参数选项O_DIRECT,用它打开的文件即可以绕过内核缓冲区的直接访问,这样便有效避免了CPU和内存的多余时间开销。
顺便提一下,与O_DIRECT相似的一个选项是O_SYNC,后者只对写数据有效,它将写入内核缓冲区的数据当即写入磁盘,将机器故障时数据的丢失减小到最小,可是它仍然要通过内核缓冲区。
##sendfile/零拷贝(网络I/O,kafka用到此特性
) ###普通的网络传输步骤以下: 1)操做系统将数据从磁盘复制到操做系统内核的页缓存中 2)应用将数据从内核缓存复制到应用的缓存中 3)应用将数据写回内核的Socket缓存中 4)操做系统将数据从Socket缓存区复制到网卡缓存,而后将其经过网络发出
一、当调用read系统调用时,经过DMA(Direct Memory Access)将数据copy到内核模式 二、而后由CPU控制将内核模式数据copy到用户模式下的 buffer中 三、read调用完成后,write调用首先将用户模式下 buffer中的数据copy到内核模式下的socket buffer中 四、最后经过DMA copy将内核模式下的socket buffer中的数据copy到网卡设备中传送。
从上面的过程能够看出,数据白白从内核模式到用户模式走了一圈,浪费了两次copy,而这两次copy都是CPU copy,即占用CPU资源。
###sendfile
经过sendfile传送文件只须要一次系统调用,当调用 sendfile时: 一、首先经过DMA copy将数据从磁盘读取到kernel buffer中 二、而后经过CPU copy将数据从kernel buffer copy到sokcet buffer中 三、最终经过DMA copy将socket buffer中数据copy到网卡buffer中发送 sendfile与read/write方式相比,少了 一次模式切换一次CPU copy。可是从上述过程当中也能够发现从kernel buffer中将数据copy到socket buffer是不必的。
为此,Linux2.4内核对sendfile作了改进,下图所示
改进后的处理过程以下: 一、DMA copy将磁盘数据copy到kernel buffer中 二、向socket buffer中追加当前要发送的数据在kernel buffer中的位置和偏移量 三、DMA gather copy根据socket buffer中的位置和偏移量直接将kernel buffer中的数据copy到网卡上。 通过上述过程,数据只通过了2次copy就从磁盘传送出去了。(事实上这个Zero copy是针对内核来说的,数据在内核模式下是Zero-copy的)。 当前许多高性能http server都引入了sendfile机制,如nginx,lighttpd等。
###FileChannel.transferTo(Java中的零拷贝
) Java NIO中FileChannel.transferTo(long position, long count, WriteableByteChannel target)方法将当前通道中的数据传送到目标通道target中,在支持Zero-Copy的linux系统中,transferTo()的实现依赖于 sendfile()调用。
传统方式对比零拷贝方式:
整个数据通路涉及4次数据复制和2个系统调用,若是使用sendfile则能够避免屡次数据复制,操做系统能够直接将数据从内核页缓存中复制到网卡缓存,这样能够大大加快整个过程的速度。
大多数时候,咱们都在向Web服务器请求静态文件,好比图片、样式表等,根据前面的介绍,咱们知道在处理这些请求的过程当中,磁盘文件的数据先要通过内核缓冲区,而后到达用户内存空间,由于是不须要任何处理的静态数据,因此它们又被送到网卡对应的内核缓冲区,接着再被送入网卡进行发送。
数据从内核出去,绕了一圈,又回到内核,没有任何变化,看起来真是浪费时间。在Linux 2.4的内核中,尝试性地引入了一个称为khttpd的内核级Web服务器程序,它只处理静态文件的请求。引入它的目的便在于内核但愿请求的处理尽可能在内核完成,减小内核态的切换以及用户态数据复制的开销。
同时,Linux经过系统调用将这种机制提供给了开发者,那就是sendfile()系统调用。它能够将磁盘文件的特定部分直接传送到表明客户端的socket描述符,加快了静态文件的请求速度,同时也减小了CPU和内存的开销。
在OpenBSD和NetBSD中没有提供对sendfile的支持。经过strace的跟踪看到了Apache在处理151字节的小文件时,使用了mmap()系统调用来实现内存映射,可是在Apache处理较大文件的时候,内存映射会致使较大的内存开销,得不偿失,因此Apache使用了sendfile64()来传送文件,sendfile64()是sendfile()的扩展实现,它在Linux 2.4以后的版本中提供。
这并不意味着sendfile在任何场景下都能发挥显著的做用。对于请求较小的静态文件,sendfile发挥的做用便显得不那么重要,经过压力测试,咱们模拟100个并发用户请求151字节的静态文件,是否使用sendfile的吞吐率几乎是相同的,可见在处理小文件请求时,发送数据的环节在整个过程当中所占时间的比例相比于大文件请求时要小不少,因此对于这部分的优化效果天然不十分明显。
##docs