Linux 五種I/O技術(shù)以及在Java中應(yīng)用
I/O概念說明
在學(xué)習(xí)linux內(nèi)核I/O方面的知識,首先要了解一下概念。
用戶空間和內(nèi)核空間
- 內(nèi)核空間:操作系統(tǒng)的核心是內(nèi)核,可以訪問受保護(hù)的內(nèi)存空間、硬件設(shè)備、執(zhí)行高權(quán)限指令。內(nèi)核由系統(tǒng)中所有進(jìn)程共享,當(dāng)進(jìn)程運(yùn)行在內(nèi)核空間時屬于內(nèi)核態(tài)。當(dāng)程序執(zhí)行了系統(tǒng)調(diào)用(例如:磁盤io)或者觸發(fā)異常中斷,此時進(jìn)程就會進(jìn)入內(nèi)核態(tài)。
- 用戶空間:存放用戶程序的代碼和數(shù)據(jù),執(zhí)行l(wèi)inux第三級別的指令。運(yùn)行在用戶空間的進(jìn)程處于用戶態(tài)。
- 上下文切換:用戶應(yīng)用空間的進(jìn)程通過系統(tǒng)調(diào)用進(jìn)入內(nèi)核態(tài)時,需要傳遞用戶空間的很多變量、參數(shù)等信息給內(nèi)核。內(nèi)核態(tài)運(yùn)行時也會保存進(jìn)程的寄存器值和變量信息,在用戶進(jìn)程恢復(fù)時傳遞該數(shù)據(jù)給用戶態(tài)。
進(jìn)程切換
操作系統(tǒng)為運(yùn)行的每個進(jìn)程開辟一個進(jìn)程表項,該項包含了進(jìn)程狀態(tài)、程序計數(shù)器、堆棧指針、內(nèi)存分配、打開文件描述等上下文信息。進(jìn)程的切換是內(nèi)核掛起正在執(zhí)行的進(jìn)程,并恢復(fù)以前運(yùn)行的進(jìn)程的過程。
進(jìn)程阻塞
正在執(zhí)行的進(jìn)程,由于需要請求系統(tǒng)資源或者等待某種操作完成,而主動執(zhí)行阻塞令名 使 進(jìn)程進(jìn)入阻塞狀態(tài)。進(jìn)入阻塞狀態(tài)后,進(jìn)程是不占有cpu資源的。
文件描述符
它是一個用于表述指向文件的引用的抽象化概念。指向內(nèi)核維護(hù)的進(jìn)程打開文件的記錄表。
I/O緩存
在linux操作系統(tǒng)會將 IO 的數(shù)據(jù)緩存在文件系統(tǒng)的頁緩存,磁盤數(shù)據(jù)會先被寫入到內(nèi)核系統(tǒng)緩沖區(qū),然后操作系統(tǒng)內(nèi)核將緩沖區(qū)數(shù)據(jù)復(fù)制到應(yīng)用程序地址空間。數(shù)據(jù)會在內(nèi)核空間和用戶空間之間進(jìn)行多次拷貝。為解決socket用戶空間和內(nèi)核空間之間的數(shù)據(jù)拷貝問題,一般采用zero-copy技術(shù)解決。例如java中FileChannel.transferTo(long position, long count, WritableByteChannel target)將數(shù)據(jù)從文件通道傳輸?shù)搅私o定的可寫字節(jié)通道。
Linux IO模型
在Linux 系統(tǒng)中常用的IO模型有阻塞BIO、非阻塞NIO、IO多路復(fù)用epoll、異步模式AIO。其中阻塞、非阻塞、多路復(fù)用都是同步模型。
IO涉及兩個階段:
第一階段:等待數(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)。
同步阻塞模型BIO
在同步阻塞模型中,通過調(diào)用操作系統(tǒng)命令recvform 阻塞應(yīng)用程序,同步阻塞等待數(shù)據(jù)從內(nèi)核態(tài)復(fù)制到用戶態(tài)。在等待數(shù)據(jù)處理的兩個階段,整個進(jìn)程都是阻塞的。

流程描述:
進(jìn)程調(diào)用了recv()/recvfrom()這個系統(tǒng)調(diào)用,內(nèi)核kernel 執(zhí)行第一階段數(shù)據(jù)準(zhǔn)備,kernel阻塞等待數(shù)據(jù)到來。等內(nèi)核態(tài)數(shù)據(jù)緩存區(qū)準(zhǔn)備好數(shù)據(jù)后,會進(jìn)行第二個階段。將數(shù)據(jù)從內(nèi)核kernel緩存區(qū)拷貝到用戶空間(拷貝階段同步阻塞)。
同步非阻塞模型NIO
同步非阻塞模型的原理是通過輪詢的方式,判斷kernel內(nèi)核是否數(shù)據(jù)準(zhǔn)備完成。非阻塞將大的整片時間的阻塞分成N多的小的阻塞。操作系統(tǒng)調(diào)用recvform命令后,進(jìn)程并不阻塞而是立即返回進(jìn)程,然后循環(huán)往復(fù)的進(jìn)行recvform系統(tǒng)調(diào)用。輪詢檢測內(nèi)核數(shù)據(jù)狀態(tài),直到內(nèi)核緩存數(shù)據(jù)準(zhǔn)備完成,再進(jìn)行數(shù)據(jù)拷貝。

該模型會導(dǎo)致系統(tǒng)吞吐量下降,因為每個一段時間輪詢一次,而內(nèi)核數(shù)據(jù)準(zhǔn)備完成時間是任意的。
多路復(fù)用I/O模型
多路復(fù)用模型是通過操作系統(tǒng)底層調(diào)用select、poll、epoll函數(shù)命令。在select()函數(shù)處阻塞 監(jiān)聽 多個IO端口,當(dāng)任何一個socket內(nèi)核數(shù)據(jù)準(zhǔn)備好時返回,然后調(diào)用recvform命令將數(shù)據(jù)從內(nèi)核態(tài)拷貝到用戶態(tài)。多路復(fù)用模型select()也會阻塞進(jìn)程,而與阻塞模型不同的是 該 函數(shù)可以阻塞多個socket。

IO多路復(fù)用是阻塞在select,epoll這樣的系統(tǒng)調(diào)用之上,而沒有阻塞在真正的I/O系統(tǒng)調(diào)用如recvfrom之上。從而使在單線程情況下可以操作多個客戶端請求。
IO多路復(fù)用模型最大優(yōu)勢是不需要創(chuàng)建的額外的線程 系統(tǒng)開銷小,節(jié)省系統(tǒng)資源。其底層實現(xiàn)方式是操作系統(tǒng)命令支持,select、poll、epoll函數(shù)命令。
異步非阻塞模型AIO
Linux提供了AIO庫函數(shù)實現(xiàn)異步,當(dāng)用戶發(fā)起aio_read操作后 內(nèi)核 會立即返回不會block。內(nèi)核會負(fù)責(zé)數(shù)據(jù)準(zhǔn)備和數(shù)據(jù)從內(nèi)核態(tài)拷貝到用戶態(tài),完成之后會向用戶進(jìn)程發(fā)送signal或通過線程回調(diào)函數(shù)完成IO 處理過程。

在non-blocking IO中,雖然進(jìn)程大部分時間都不會被block,但是它仍然要求進(jìn)程去主動的check,并且當(dāng)數(shù)據(jù)準(zhǔn)備完成以后,也需要進(jìn)程主動的再次調(diào)用recvfrom來將數(shù)據(jù)拷貝到用戶內(nèi)存。而asynchronous IO則完全不同。它就像是用戶進(jìn)程將整個IO操作交給了他人(kernel)完成,然后他人做完后發(fā)信號通知。在此期間,用戶進(jìn)程不需要去檢查IO操作的狀態(tài),也不需要主動的去拷貝數(shù)據(jù)。