磁盘可以说是计算机体系中最慢的硬件之一,读写速度相差内存10倍以上,所以针对优化磁盘的技能十分的多,比方:零复制,直接I/O,异步I/O等等。
这里咱们就以文件传输为切入点,剖析I/O工作方式,以及怎么优化文件传输的功能。
为什么要有DMA技能
DMA技能,全称为Direct Memory Access(又称直接内存访问技能)。
没有DMA技能前的I/O进程
可以看到整个数据传输的进程:
- 首先在用户进程进行read()体系调用的时分,操作体系会由用户态切换到内核态,然后由CPU向磁盘发起IO恳求。
- 磁盘在接收到IO恳求今后,进行数据准备工作,将数据放在磁盘控制器缓冲区里。
- 在数据准备工作完结今后,磁盘向CPU宣布IO中止信号。
- CPU收到中止信号后,会先将磁盘缓冲区中的文件copy到PageCache中,再将数据从PageCache中copy到用户缓冲区中。在这期间CPU是无法履行其他使命的。copy完结之后read()调用回来,操作体系刚从内核态切换回用户态。
故可以得知:在数据传输的进程,需求CPU亲身的去复制数据,而且在这期间CPU无法去做其他事情。简单的转移几个字符没有问题,但是当处理很多数据的时分,假如每次都让CPU来转移,显然忙不过来。
有DMA技能之后的I/O进程
-
当有了DMA控制器今后,在进程向CPU宣布read()调用今后,CPU向DMA控制器发起IO恳求,然后DMA控制器再向磁盘宣布IO恳求。
-
当磁盘接收到IO恳求今后,会进行数据准备工作,将数据放到磁盘数据缓冲区傍边。
-
当磁盘的数据准备工作完结今后,不再向CPU宣布中止信号,而是告诉DMA控制器。
-
DMA控制器在接收到告诉今后,将数据从磁盘控制器缓冲区中copy到内核缓冲区中。在这期间并不占用CPU,CPU可以处理其他的事情,履行其他的使命。
-
DMA控制器处理完之后向CPU宣布中止信号,由CPU将数据从内核缓冲区copy至用户缓冲区中。copy完结之后read()调用回来,操作体系从内核态切换回用户态。
留意:起先的DMA控制器只在主板上,但是现在IO设备越来越多,数据传输的需求也不尽相同,所以现在每个IO设备中都有DMA控制器。
在进行read调用的时分,操作体系由用户态转为内核态,需求进行DMA复制和CPU复制各一次。(DMA复制是指由DMA控制器将磁盘控制器缓冲区中的数据复制到内核缓冲区中,CPU复制是指将数据从内核缓冲区中copy到用户缓冲区中),在read调用结束今后,操作体系再从内核态切换回用户态。
在进行write调用的时分,操作体系由用户态转为内核态,需求进行CPU复制和DMA复制各一次。(CPU复制是指将用户缓冲区的数据复制到socket缓冲区中。而DMA复制是指将socket缓冲区中的数据复制到网卡中。)
在文件传输场景下,咱们无需在用户空间对数据进行再加工。因而用户缓冲区的存在是没有必要的。
怎么完结零复制
完结零复制的技能主要有两种:mmap+write,sendfile
下面就谈一谈,他们是怎么削减上下文切换和数据复制的次数的。
mmap+write
运用mmap替换read体系调用函数。
mmap体系调用函数会直接将内核缓冲区的数据映射到用户空间,这样操作体系内核与用户空间之间就不需求任何的复制操作。
具体流程如下: 运用进程调用了mmap体系调用函数之后,DMA会把磁盘缓冲区的数据复制到内核缓冲区中。接着,运用进程和操作体系内核同享这个缓冲区。 运用进程再调用write函数,操作体系直接将内核缓冲区的数据复制到socket缓冲区。再由DMA控制器copy到网卡。
这还不是最理想的零复制因为还是需求两次体系调用,四次上下文切换,3次复制。
sendfile
在linux内核版别2.1中,供给了一个新的体系调用函数sendfile。这个体系调用函数可以替代read和write两个体系调用函数。
这样体系调用的次数就变成了一次,上下文切换的次数减小到了两次,数据复制次数为3次。
这还不是真实的零复制技能: 假如网卡支持SG—DMA,就可以将数据复制次数减小为两次。
这就是所谓的零复制技能,咱们没有在内存层面区复制数据,也就是说全进程都没有运用CPU转移数据,所有数据都是通过DMA进行传输的。
整个进程只需求两次上下文的切换和两次数据复制。
kafka、rocketMQ、Nginx都运用到了零复制技能。
PageCache
零复制的内核缓冲区正是运用了PageCache技能。在零复制技能中,用PageCache来缓存最近被访问过的数据,当空间不足时筛选最久未运用的缓存。
而且给予局部性原理,进行预读,削减IO次数。
但是在传输大文件的时分,功能丢失十分大。因为在传输大文件的时分假如选用PageCache,PageCache容量有限,则PageCache因为长期被大文件占有,其他热门的小文件可能就无法充分运用到。这样磁盘的读写功能就会降低。而且PageCache中的大文件数据,因为没有享受到缓存带来的好处,但却消耗 DMA 多复制到 PageCache 一次,形成资源的糟蹋。
因而PageCache 被大文件占有,而导致「热门」小文件无法运用到 PageCache,这样在高并发的环境下,会带来严峻的功能问题。
大文件传输选用什么完结
在最初的方案中:
- 调用read方法的时分,操作体系会由用户态切换为内核态。
- 然后内核向磁盘发起IO恳求,磁盘收到IO恳求今后,会将数据放在磁盘控制器缓冲区中。
- 磁盘完结该操作今后,向内核发起中止信号,然后内核将磁盘控制器缓冲区中的信号复制到PageCache中。
- 然后内核再将PageCache中的数据复制到用户缓冲区中。
- 完结今后,read调用回来。操作体系由内核态窃魂会用户态。
在read方法回来前,进程一直处于堵塞的状况。
针对堵塞的问题,可以选用异步IO来处理。
异步IO的流程图如下所示:
核心思想在于:在发起异步IO读今后,不等候数据就位就可以当即回来,然后去处理其他使命。直到收到内核回来的读取成功告诉,才去处理数据。这样就处理了高并发场景下零复制技能大文件读取的堵塞问题。
因而,关于传输大文件,应该运用异步IO+直接IO的方法。而关于小文件,则应该运用零复制。