一文搞懂什么是阻塞IO、信號驅(qū)動(dòng)IO、Reactor模型、零拷貝
基礎(chǔ)IO
如何從數(shù)據(jù)傳輸方式理解IO流?
從數(shù)據(jù)傳輸方式或者說是運(yùn)輸方式角度看,可以將 IO 類分為:
- 字節(jié)流, 字節(jié)流讀取單個(gè)字節(jié),字符流讀取單個(gè)字符(一個(gè)字符根據(jù)編碼的不同,對應(yīng)的字節(jié)也不同,如 UTF-8 編碼中文漢字是 3 個(gè)字節(jié),GBK編碼中文漢字是 2 個(gè)字節(jié)。)
- 字符流, 字節(jié)流用來處理二進(jìn)制文件(圖片、MP3、視頻文件),字符流用來處理文本文件(可以看做是特殊的二進(jìn)制文件,使用了某種編碼,人可以閱讀)。
字節(jié)是給計(jì)算機(jī)看的,字符才是給人看的
- 字節(jié)流
圖片
image.png
- 字符流
圖片
- 字節(jié)轉(zhuǎn)字符?
圖片
如何從數(shù)據(jù)操作上理解IO流?
從數(shù)據(jù)來源或者說是操作對象角度看,IO 類可以分為:
圖片
Java IO設(shè)計(jì)上使用了什么設(shè)計(jì)模式?
裝飾者模式:所謂裝飾,就是把這個(gè)裝飾者套在被裝飾者之上,從而動(dòng)態(tài)擴(kuò)展被裝飾者的功能。
- 裝飾者舉例
設(shè)計(jì)不同種類的飲料,飲料可以添加配料,比如可以添加牛奶,并且支持動(dòng)態(tài)添加新配料。每增加一種配料,該飲料的價(jià)格就會增加,要求計(jì)算一種飲料的價(jià)格。
下圖表示在 DarkRoast 飲料上新增新添加 Mocha 配料,之后又添加了 Whip 配料。DarkRoast 被 Mocha 包裹,Mocha 又被 Whip 包裹。它們都繼承自相同父類,都有 cost() 方法,外層類的 cost() 方法調(diào)用了內(nèi)層類的 cost() 方法。
圖片
image.png
- 以 InputStream 為例
- InputStream 是抽象組件;
- FileInputStream 是 InputStream 的子類,屬于具體組件,提供了字節(jié)流的輸入操作;
- FilterInputStream 屬于抽象裝飾者,裝飾者用于裝飾組件,為組件提供額外的功能。例如 BufferedInputStream 為 FileInputStream 提供緩存的功能。
圖片
image.png
實(shí)例化一個(gè)具有緩存功能的字節(jié)流對象時(shí),只需要在 FileInputStream 對象上再套一層 BufferedInputStream 對象即可。
FileInputStream fileInputStream = new FileInputStream(filePath);
BufferedInputStream bufferedInputStream = new BufferedInputStream(fileInputStream);
DataInputStream 裝飾者提供了對更多數(shù)據(jù)類型進(jìn)行輸入的操作,比如 int、double 等基本類型。
5種IO模型
什么是阻塞?什么是同步?
- 阻塞IO 和 非阻塞IO
這兩個(gè)概念是程序級別的。主要描述的是程序請求操作系統(tǒng)IO操作后,如果IO資源沒有準(zhǔn)備好,那么程序該如何處理的問題: 前者等待;后者繼續(xù)執(zhí)行(并且使用線程一直輪詢,直到有IO資源準(zhǔn)備好了)
- 同步IO 和 非同步IO
這兩個(gè)概念是操作系統(tǒng)級別的。主要描述的是操作系統(tǒng)在收到程序請求IO操作后,如果IO資源沒有準(zhǔn)備好,該如何響應(yīng)程序的問題: 前者不響應(yīng),直到IO資源準(zhǔn)備好以后;后者返回一個(gè)標(biāo)記(好讓程序和自己知道以后的數(shù)據(jù)往哪里通知),當(dāng)IO資源準(zhǔn)備好以后,再用事件機(jī)制返回給程序。
什么是Linux的IO模型?
網(wǎng)絡(luò)IO的本質(zhì)是socket的讀取,socket在linux系統(tǒng)被抽象為流,IO可以理解為對流的操作。剛才說了,對于一次IO訪問(以read舉例),數(shù)據(jù)會先被拷貝到操作系統(tǒng)內(nèi)核的緩沖區(qū)中,然后才會從操作系統(tǒng)內(nèi)核的緩沖區(qū)拷貝到應(yīng)用程序的地址空間。所以說,當(dāng)一個(gè)read操作發(fā)生時(shí),它會經(jīng)歷兩個(gè)階段:
- 第一階段:等待數(shù)據(jù)準(zhǔn)備 (Waiting for the data to be ready)。
- 第二階段:將數(shù)據(jù)從內(nèi)核拷貝到進(jìn)程中 (Copying the data from the kernel to the process)。
對于socket流而言,
- 第一步:通常涉及等待網(wǎng)絡(luò)上的數(shù)據(jù)分組到達(dá),然后被復(fù)制到內(nèi)核的某個(gè)緩沖區(qū)。
- 第二步:把數(shù)據(jù)從內(nèi)核緩沖區(qū)復(fù)制到應(yīng)用進(jìn)程緩沖區(qū)。
網(wǎng)絡(luò)應(yīng)用需要處理的無非就是兩大類問題,網(wǎng)絡(luò)IO,數(shù)據(jù)計(jì)算。相對于后者,網(wǎng)絡(luò)IO的延遲,給應(yīng)用帶來的性能瓶頸大于后者。網(wǎng)絡(luò)IO的模型大致有如下幾種:
- 同步阻塞IO(bloking IO)
- 同步非阻塞IO(non-blocking IO)
- 多路復(fù)用IO(multiplexing IO)
- 信號驅(qū)動(dòng)式IO(signal-driven IO)
- 異步IO(asynchronous IO)
圖片
什么是同步阻塞IO?
應(yīng)用進(jìn)程被阻塞,直到數(shù)據(jù)復(fù)制到應(yīng)用進(jìn)程緩沖區(qū)中才返回。
- 舉例理解
你早上去買有現(xiàn)炸油條,你點(diǎn)單,之后一直等店家做好,期間你啥其它事也做不了。(你就是應(yīng)用級別,店家就是操作系統(tǒng)級別, 應(yīng)用被阻塞了不能做其它事)
- Linux 中IO圖例
圖片
image.png
什么是同步非阻塞IO?
應(yīng)用進(jìn)程執(zhí)行系統(tǒng)調(diào)用之后,內(nèi)核返回一個(gè)錯(cuò)誤碼。應(yīng)用進(jìn)程可以繼續(xù)執(zhí)行,但是需要不斷的執(zhí)行系統(tǒng)調(diào)用來獲知 I/O 是否完成,這種方式稱為輪詢(polling)。
- 舉例理解
你早上去買現(xiàn)炸油條,你點(diǎn)單,點(diǎn)完后每隔一段時(shí)間詢問店家有沒有做好,期間你可以做點(diǎn)其它事情。(你就是應(yīng)用級別,店家就是操作系統(tǒng)級別,應(yīng)用可以做其它事情并通過輪詢來看操作系統(tǒng)是否完成)
- Linux 中IO圖例
圖片
image.png
什么是多路復(fù)用IO?
系統(tǒng)調(diào)用可能是由多個(gè)任務(wù)組成的,所以可以拆成多個(gè)任務(wù),這就是多路復(fù)用。
- 舉例理解
你早上去買現(xiàn)炸油條,點(diǎn)單收錢和炸油條原來都是由一個(gè)人完成的,現(xiàn)在他成了瓶頸,所以專門找了個(gè)收銀員下單收錢,他則專注在炸油條。(本質(zhì)上炸油條是耗時(shí)的瓶頸,將他職責(zé)分離出不是瓶頸的部分,比如下單收銀,對應(yīng)到系統(tǒng)級別也時(shí)一樣的意思)
- Linux 中IO圖例
使用 select 或者 poll 等待數(shù)據(jù),并且可以等待多個(gè)套接字中的任何一個(gè)變?yōu)榭勺x,這一過程會被阻塞,當(dāng)某一個(gè)套接字可讀時(shí)返回。之后再使用 recvfrom 把數(shù)據(jù)從內(nèi)核復(fù)制到進(jìn)程中。
它可以讓單個(gè)進(jìn)程具有處理多個(gè) I/O 事件的能力。又被稱為 Event Driven I/O,即事件驅(qū)動(dòng) I/O。
圖片
有哪些多路復(fù)用IO?
目前流程的多路復(fù)用IO實(shí)現(xiàn)主要包括四種: select、poll、epoll、kqueue。下表是他們的一些重要特性的比較:
IO模型 | 相對性能 | 關(guān)鍵思路 | 操作系統(tǒng) | JAVA支持情況 |
select | 較高 | Reactor | windows/Linux | 支持,Reactor模式(反應(yīng)器設(shè)計(jì)模式)。Linux操作系統(tǒng)的 kernels 2.4內(nèi)核版本之前,默認(rèn)使用select;而目前windows下對同步IO的支持,都是select模型 |
poll | 較高 | Reactor | Linux | Linux下的JAVA NIO框架,Linux kernels 2.6內(nèi)核版本之前使用poll進(jìn)行支持。也是使用的Reactor模式 |
epoll | 高 | Reactor/Proactor | Linux | Linux kernels 2.6內(nèi)核版本及以后使用epoll進(jìn)行支持;Linux kernels 2.6內(nèi)核版本之前使用poll進(jìn)行支持;另外一定注意,由于Linux下沒有Windows下的IOCP技術(shù)提供真正的 異步IO 支持,所以Linux下使用epoll模擬異步IO |
kqueue | 高 | Proactor | Linux | 目前JAVA的版本不支持 |
多路復(fù)用IO技術(shù)最適用的是“高并發(fā)”場景,所謂高并發(fā)是指1毫秒內(nèi)至少同時(shí)有上千個(gè)連接請求準(zhǔn)備好。其他情況下多路復(fù)用IO技術(shù)發(fā)揮不出來它的優(yōu)勢。另一方面,使用JAVA NIO進(jìn)行功能實(shí)現(xiàn),相對于傳統(tǒng)的Socket套接字實(shí)現(xiàn)要復(fù)雜一些,所以實(shí)際應(yīng)用中,需要根據(jù)自己的業(yè)務(wù)需求進(jìn)行技術(shù)選擇。
什么是信號驅(qū)動(dòng)IO?
應(yīng)用進(jìn)程使用 sigaction 系統(tǒng)調(diào)用,內(nèi)核立即返回,應(yīng)用進(jìn)程可以繼續(xù)執(zhí)行,也就是說等待數(shù)據(jù)階段應(yīng)用進(jìn)程是非阻塞的。內(nèi)核在數(shù)據(jù)到達(dá)時(shí)向應(yīng)用進(jìn)程發(fā)送 SIGIO 信號,應(yīng)用進(jìn)程收到之后在信號處理程序中調(diào)用 recvfrom 將數(shù)據(jù)從內(nèi)核復(fù)制到應(yīng)用進(jìn)程中。
相比于非阻塞式 I/O 的輪詢方式,信號驅(qū)動(dòng) I/O 的 CPU 利用率更高。
- 舉例理解
你早上去買現(xiàn)炸油條,門口排隊(duì)的人多,現(xiàn)在引入了一個(gè)叫號系統(tǒng),點(diǎn)完單后你就可以做自己的事情了,然后等叫號就去拿就可以了。(所以不用再去自己頻繁跑去問有沒有做好了)
- Linux 中IO圖例
圖片
image.png
什么是異步IO?
相對于同步IO,異步IO不是順序執(zhí)行。用戶進(jìn)程進(jìn)行aio_read系統(tǒng)調(diào)用之后,無論內(nèi)核數(shù)據(jù)是否準(zhǔn)備好,都會直接返回給用戶進(jìn)程,然后用戶態(tài)進(jìn)程可以去做別的事情。等到socket數(shù)據(jù)準(zhǔn)備好了,內(nèi)核直接復(fù)制數(shù)據(jù)給進(jìn)程,然后從內(nèi)核向進(jìn)程發(fā)送通知。IO兩個(gè)階段,進(jìn)程都是非阻塞的。
- 舉例理解
你早上去買現(xiàn)炸油條, 不用去排隊(duì)了,打開美團(tuán)外賣下單,然后做其它事,一會外賣自己送上門。(你就是應(yīng)用級別,店家就是操作系統(tǒng)級別, 應(yīng)用無需阻塞,這就是非阻塞;系統(tǒng)還可能在處理中,但是立刻響應(yīng)了應(yīng)用,這就是異步)
- Linux 中IO圖例
(Linux提供了AIO庫函數(shù)實(shí)現(xiàn)異步,但是用的很少。目前有很多開源的異步IO庫,例如libevent、libev、libuv)
圖片
什么是Reactor模型?
大多數(shù)網(wǎng)絡(luò)框架都是基于Reactor模型進(jìn)行設(shè)計(jì)和開發(fā),Reactor模型基于事件驅(qū)動(dòng),特別適合處理海量的I/O事件。
- 傳統(tǒng)的IO模型?
這種模式是傳統(tǒng)設(shè)計(jì),每一個(gè)請求到來時(shí),大致都會按照:請求讀取->請求解碼->服務(wù)執(zhí)行->編碼響應(yīng)->發(fā)送答復(fù) 這個(gè)流程去處理。
圖片
服務(wù)器會分配一個(gè)線程去處理,如果請求暴漲起來,那么意味著需要更多的線程來處理該請求。若請求出現(xiàn)暴漲,線程池的工作線程數(shù)量滿載那么其它請求就會出現(xiàn)等待或者被拋棄。若每個(gè)小任務(wù)都可以使用非阻塞的模式,然后基于異步回調(diào)模式。這樣就大大提高系統(tǒng)的吞吐量,這便引入了Reactor模型。
- Reactor模型中定義的三種角色:
- Reactor:負(fù)責(zé)監(jiān)聽和分配事件,將I/O事件分派給對應(yīng)的Handler。新的事件包含連接建立就緒、讀就緒、寫就緒等。
- Acceptor:處理客戶端新連接,并分派請求到處理器鏈中。
- Handler:將自身與事件綁定,執(zhí)行非阻塞讀/寫任務(wù),完成channel的讀入,完成處理業(yè)務(wù)邏輯后,負(fù)責(zé)將結(jié)果寫出channel??捎觅Y源池來管理。
- 單Reactor單線程模型
Reactor線程負(fù)責(zé)多路分離套接字,accept新連接,并分派請求到handler。Redis使用單Reactor單進(jìn)程的模型。
圖片
image.png
消息處理流程:
- Reactor對象通過select監(jiān)控連接事件,收到事件后通過dispatch進(jìn)行轉(zhuǎn)發(fā)。
- 如果是連接建立的事件,則由acceptor接受連接,并創(chuàng)建handler處理后續(xù)事件。
- 如果不是建立連接事件,則Reactor會分發(fā)調(diào)用Handler來響應(yīng)。
- handler會完成read->業(yè)務(wù)處理->send的完整業(yè)務(wù)流程。
- 單Reactor多線程模型
將handler的處理池化。
圖片
- 多Reactor多線程模型
主從Reactor模型:主Reactor用于響應(yīng)連接請求,從Reactor用于處理IO操作請求,讀寫分離了。
圖片
什么是Java NIO?
NIO主要有三大核心部分:Channel(通道),Buffer(緩沖區(qū)), Selector。傳統(tǒng)IO基于字節(jié)流和字符流進(jìn)行操作,而NIO基于Channel和Buffer(緩沖區(qū))進(jìn)行操作,數(shù)據(jù)總是從通道讀取到緩沖區(qū)中,或者從緩沖區(qū)寫入到通道中。Selector(選擇區(qū))用于監(jiān)聽多個(gè)通道的事件(比如:連接打開,數(shù)據(jù)到達(dá))。因此,單個(gè)線程可以監(jiān)聽多個(gè)數(shù)據(jù)通道。
NIO和傳統(tǒng)IO(一下簡稱IO)之間第一個(gè)最大的區(qū)別是,IO是面向流的,NIO是面向緩沖區(qū)的。
圖片
image.png
零拷貝
傳統(tǒng)的IO存在什么問題?為什么引入零拷貝的?
如果服務(wù)端要提供文件傳輸?shù)墓δ埽覀兡芟氲降淖詈唵蔚姆绞绞牵簩⒋疟P上的文件讀取出來,然后通過網(wǎng)絡(luò)協(xié)議發(fā)送給客戶端。
傳統(tǒng) I/O 的工作方式是,數(shù)據(jù)讀取和寫入是從用戶空間到內(nèi)核空間來回復(fù)制,而內(nèi)核空間的數(shù)據(jù)是通過操作系統(tǒng)層面的 I/O 接口從磁盤讀取或?qū)懭搿?/p>
代碼通常如下,一般會需要兩個(gè)系統(tǒng)調(diào)用:
read(file, tmp_buf, len);
write(socket, tmp_buf, len);
代碼很簡單,雖然就兩行代碼,但是這里面發(fā)生了不少的事情。
圖片
首先,期間共發(fā)生了 4 次用戶態(tài)與內(nèi)核態(tài)的上下文切換,因?yàn)榘l(fā)生了兩次系統(tǒng)調(diào)用,一次是 read() ,一次是 write(),每次系統(tǒng)調(diào)用都得先從用戶態(tài)切換到內(nèi)核態(tài),等內(nèi)核完成任務(wù)后,再從內(nèi)核態(tài)切換回用戶態(tài)。
上下文切換到成本并不小,一次切換需要耗時(shí)幾十納秒到幾微秒,雖然時(shí)間看上去很短,但是在高并發(fā)的場景下,這類時(shí)間容易被累積和放大,從而影響系統(tǒng)的性能。
其次,還發(fā)生了 4 次數(shù)據(jù)拷貝,其中兩次是 DMA 的拷貝,另外兩次則是通過 CPU 拷貝的,下面說一下這個(gè)過程:
- 第一次拷貝,把磁盤上的數(shù)據(jù)拷貝到操作系統(tǒng)內(nèi)核的緩沖區(qū)里,這個(gè)拷貝的過程是通過 DMA 搬運(yùn)的。
- 第二次拷貝,把內(nèi)核緩沖區(qū)的數(shù)據(jù)拷貝到用戶的緩沖區(qū)里,于是我們應(yīng)用程序就可以使用這部分?jǐn)?shù)據(jù)了,這個(gè)拷貝到過程是由 CPU 完成的。
- 第三次拷貝,把剛才拷貝到用戶的緩沖區(qū)里的數(shù)據(jù),再拷貝到內(nèi)核的 socket 的緩沖區(qū)里,這個(gè)過程依然還是由 CPU 搬運(yùn)的。
- 第四次拷貝,把內(nèi)核的 socket 緩沖區(qū)里的數(shù)據(jù),拷貝到網(wǎng)卡的緩沖區(qū)里,這個(gè)過程又是由 DMA 搬運(yùn)的。
我們回過頭看這個(gè)文件傳輸?shù)倪^程,我們只是搬運(yùn)一份數(shù)據(jù),結(jié)果卻搬運(yùn)了 4 次,過多的數(shù)據(jù)拷貝無疑會消耗 CPU 資源,大大降低了系統(tǒng)性能。
這種簡單又傳統(tǒng)的文件傳輸方式,存在冗余的上文切換和數(shù)據(jù)拷貝,在高并發(fā)系統(tǒng)里是非常糟糕的,多了很多不必要的開銷,會嚴(yán)重影響系統(tǒng)性能。
所以,要想提高文件傳輸?shù)男阅埽托枰獪p少「用戶態(tài)與內(nèi)核態(tài)的上下文切換」和「內(nèi)存拷貝」的次數(shù)。
mmap + write怎么實(shí)現(xiàn)的零拷貝?
在前面我們知道,read() 系統(tǒng)調(diào)用的過程中會把內(nèi)核緩沖區(qū)的數(shù)據(jù)拷貝到用戶的緩沖區(qū)里,于是為了減少這一步開銷,我們可以用 mmap() 替換 read() 系統(tǒng)調(diào)用函數(shù)。
buf = mmap(file, len);
write(sockfd, buf, len);
mmap() 系統(tǒng)調(diào)用函數(shù)會直接把內(nèi)核緩沖區(qū)里的數(shù)據(jù)「映射」到用戶空間,這樣,操作系統(tǒng)內(nèi)核與用戶空間就不需要再進(jìn)行任何的數(shù)據(jù)拷貝操作。
圖片
image.png
具體過程如下:
- 應(yīng)用進(jìn)程調(diào)用了 mmap() 后,DMA 會把磁盤的數(shù)據(jù)拷貝到內(nèi)核的緩沖區(qū)里。接著,應(yīng)用進(jìn)程跟操作系統(tǒng)內(nèi)核「共享」這個(gè)緩沖區(qū);
- 應(yīng)用進(jìn)程再調(diào)用 write(),操作系統(tǒng)直接將內(nèi)核緩沖區(qū)的數(shù)據(jù)拷貝到 socket 緩沖區(qū)中,這一切都發(fā)生在內(nèi)核態(tài),由 CPU 來搬運(yùn)數(shù)據(jù);
- 最后,把內(nèi)核的 socket 緩沖區(qū)里的數(shù)據(jù),拷貝到網(wǎng)卡的緩沖區(qū)里,這個(gè)過程是由 DMA 搬運(yùn)的。
我們可以得知,通過使用 mmap() 來代替 read(), 可以減少一次數(shù)據(jù)拷貝的過程。
但這還不是最理想的零拷貝,因?yàn)槿匀恍枰ㄟ^ CPU 把內(nèi)核緩沖區(qū)的數(shù)據(jù)拷貝到 socket 緩沖區(qū)里,而且仍然需要 4 次上下文切換,因?yàn)橄到y(tǒng)調(diào)用還是 2 次。
sendfile怎么實(shí)現(xiàn)的零拷貝?
在 Linux 內(nèi)核版本 2.1 中,提供了一個(gè)專門發(fā)送文件的系統(tǒng)調(diào)用函數(shù) sendfile(),函數(shù)形式如下:
#include <sys/socket.h>
ssize_t sendfile(int out_fd, int in_fd, off_t *offset, size_t count);
它的前兩個(gè)參數(shù)分別是目的端和源端的文件描述符,后面兩個(gè)參數(shù)是源端的偏移量和復(fù)制數(shù)據(jù)的長度,返回值是實(shí)際復(fù)制數(shù)據(jù)的長度。
首先,它可以替代前面的 read() 和 write() 這兩個(gè)系統(tǒng)調(diào)用,這樣就可以減少一次系統(tǒng)調(diào)用,也就減少了 2 次上下文切換的開銷。
其次,該系統(tǒng)調(diào)用,可以直接把內(nèi)核緩沖區(qū)里的數(shù)據(jù)拷貝到 socket 緩沖區(qū)里,不再拷貝到用戶態(tài),這樣就只有 2 次上下文切換,和 3 次數(shù)據(jù)拷貝。如下圖:
圖片
但是這還不是真正的零拷貝技術(shù),如果網(wǎng)卡支持 SG-DMA(The Scatter-Gather Direct Memory Access)技術(shù)(和普通的 DMA 有所不同),我們可以進(jìn)一步減少通過 CPU 把內(nèi)核緩沖區(qū)里的數(shù)據(jù)拷貝到 socket 緩沖區(qū)的過程。
你可以在你的 Linux 系統(tǒng)通過下面這個(gè)命令,查看網(wǎng)卡是否支持 scatter-gather 特性:
$ ethtool -k eth0 | grep scatter-gather
scatter-gather: on
于是,從 Linux 內(nèi)核 2.4 版本開始起,對于支持網(wǎng)卡支持 SG-DMA 技術(shù)的情況下, sendfile() 系統(tǒng)調(diào)用的過程發(fā)生了點(diǎn)變化,具體過程如下:
- 第一步,通過 DMA 將磁盤上的數(shù)據(jù)拷貝到內(nèi)核緩沖區(qū)里;
- 第二步,緩沖區(qū)描述符和數(shù)據(jù)長度傳到 socket 緩沖區(qū),這樣網(wǎng)卡的 SG-DMA 控制器就可以直接將內(nèi)核緩存中的數(shù)據(jù)拷貝到網(wǎng)卡的緩沖區(qū)里,此過程不需要將數(shù)據(jù)從操作系統(tǒng)內(nèi)核緩沖區(qū)拷貝到 socket 緩沖區(qū)中,這樣就減少了一次數(shù)據(jù)拷貝;
所以,這個(gè)過程之中,只進(jìn)行了 2 次數(shù)據(jù)拷貝,如下圖:
圖片
這就是所謂的零拷貝(Zero-copy)技術(shù),因?yàn)槲覀儧]有在內(nèi)存層面去拷貝數(shù)據(jù),也就是說全程沒有通過 CPU 來搬運(yùn)數(shù)據(jù),所有的數(shù)據(jù)都是通過 DMA 來進(jìn)行傳輸?shù)摹?/p>
零拷貝技術(shù)的文件傳輸方式相比傳統(tǒng)文件傳輸?shù)姆绞?,減少了 2 次上下文切換和數(shù)據(jù)拷貝次數(shù),只需要 2 次上下文切換和數(shù)據(jù)拷貝次數(shù),就可以完成文件的傳輸,而且 2 次的數(shù)據(jù)拷貝過程,都不需要通過 CPU,2 次都是由 DMA 來搬運(yùn)。