linux五種IO模型

Linux下主要的IO主要分為:阻塞IO(Blocking IO),非阻塞IO(Non-blocking IO),同步IO(Sync IO)和異步IO(Async IO)。
同步:調(diào)用端會(huì)一直等待服務(wù)端響應(yīng),直到返回結(jié)果。
異步:調(diào)用端發(fā)起調(diào)用之后不會(huì)立刻返回,不會(huì)等待服務(wù)端響應(yīng)。服務(wù)端通過(guò)通知機(jī)制或者回調(diào)函數(shù)來(lái)通知客戶端。
阻塞:服務(wù)端返回結(jié)果之前,客戶端線程會(huì)被掛起,此時(shí)線程不可被CPU調(diào)度,線程暫停運(yùn)行。
非阻塞:在服務(wù)端返回前,函數(shù)不會(huì)阻塞調(diào)用端線程,而會(huì)立刻返回。

同步異步的區(qū)別在于:服務(wù)端在拷貝數(shù)據(jù)時(shí)是否阻塞調(diào)用端線程;阻塞和非阻塞的區(qū)別在于:調(diào)用端線程在調(diào)用function后是否立刻返回。要理解這些I/O,需要先理解一些基本的概念。

用戶態(tài)和核心態(tài)

Linux系統(tǒng)中分為核心態(tài)(Kernel model)和用戶態(tài)(User model),CPU會(huì)在兩個(gè)model之間切換。

  1. 核心態(tài)代碼擁有完全的底層資源控制權(quán)限,可以執(zhí)行任何CPU指令,訪問(wèn)任何內(nèi)存地址,其占有的處理機(jī)是不允許被搶占的。內(nèi)核態(tài)的指令包括:?jiǎn)?dòng)I/O,內(nèi)存清零,修改程序狀態(tài)字,設(shè)置時(shí)鐘,允許/終止中斷和停機(jī)。內(nèi)核態(tài)的程序崩潰會(huì)導(dǎo)致PC停機(jī)。
  2. 用戶態(tài)是用戶程序能夠使用的指令,不能直接訪問(wèn)底層硬件和內(nèi)存地址。用戶態(tài)運(yùn)行的程序必須委托系統(tǒng)調(diào)用來(lái)訪問(wèn)硬件和內(nèi)存。用戶態(tài)的指令包括:控制轉(zhuǎn)移,算數(shù)運(yùn)算,取數(shù)指令,訪管指令(使用戶程序從用戶態(tài)陷入內(nèi)核態(tài))。

用戶態(tài)和核心態(tài)的切換

用戶態(tài)切換到核心態(tài)有三種方式:
a.系統(tǒng)調(diào)用
這是用戶態(tài)進(jìn)程主動(dòng)要求切換到內(nèi)核態(tài)的一種方式,用戶態(tài)進(jìn)程通過(guò)系統(tǒng)調(diào)用申請(qǐng)使用操作系統(tǒng)提供的服務(wù)程序完成工作,比如前例中fork()實(shí)際上就是執(zhí)行了一個(gè)創(chuàng)建新進(jìn)程的系統(tǒng)調(diào)用。而系統(tǒng)調(diào)用的機(jī)制其核心還是使用了操作系統(tǒng)為用戶特別開(kāi)放的一個(gè)中斷來(lái)實(shí)現(xiàn),例如Linux的int 80h中斷。
b.異常
當(dāng)CPU在執(zhí)行運(yùn)行在用戶態(tài)下的程序時(shí),發(fā)生了某些事先不可知的異常,這時(shí)會(huì)觸發(fā)由當(dāng)前運(yùn)行進(jìn)程切換到處理此異常的內(nèi)核相關(guān)程序中,也就轉(zhuǎn)到了內(nèi)核態(tài),比如缺頁(yè)異常。
c.外圍設(shè)備的中斷
當(dāng)外圍設(shè)備完成用戶請(qǐng)求的操作后,會(huì)向CPU發(fā)出相應(yīng)的中斷信號(hào),這時(shí)CPU會(huì)暫停執(zhí)行下一條即將要執(zhí)行的指令轉(zhuǎn)而去執(zhí)行與中斷信號(hào)對(duì)應(yīng)的處理程序,如果先前執(zhí)行的指令是用戶態(tài)下的程序,那么這個(gè)轉(zhuǎn)換的過(guò)程自然也就發(fā)生了由用戶態(tài)到內(nèi)核態(tài)的切換。比如硬盤(pán)讀寫(xiě)操作完成,系統(tǒng)會(huì)切換到硬盤(pán)讀寫(xiě)的中斷處理程序中執(zhí)行后續(xù)操作等。

進(jìn)程切換

為了控制進(jìn)程的執(zhí)行,內(nèi)核必須有能力掛起正在CPU上運(yùn)行的進(jìn)程,并恢復(fù)以前掛起的某個(gè)進(jìn)程的執(zhí)行。這種行為被稱(chēng)為進(jìn)程切換。因此可以說(shuō),任何進(jìn)程都是在操作系統(tǒng)內(nèi)核的支持下運(yùn)行的,是與內(nèi)核緊密相關(guān)的。從一個(gè)進(jìn)程的運(yùn)行轉(zhuǎn)到另一個(gè)進(jìn)程上運(yùn)行,這個(gè)過(guò)程中經(jīng)過(guò)下面這些變化:

  1. 保存處理機(jī)上下文,包括程序計(jì)數(shù)器和其他寄存器。
  2. 更新PCB信息。
  3. 把進(jìn)程的PCB移入相應(yīng)的隊(duì)列,如就緒、在某事件阻塞等隊(duì)列。
  4. 選擇另一個(gè)進(jìn)程執(zhí)行,并更新其PCB。
  5. 更新內(nèi)存管理的數(shù)據(jù)結(jié)構(gòu)。
  6. 恢復(fù)處理機(jī)上下文。

進(jìn)程阻塞

正在執(zhí)行的進(jìn)程由于一些事情發(fā)生,如請(qǐng)求資源失敗、等待某種操作完成、新數(shù)據(jù)尚未達(dá)到或者沒(méi)有新工作做等,由系統(tǒng)自動(dòng)執(zhí)行阻塞原語(yǔ),使進(jìn)程狀態(tài)變?yōu)樽枞麪顟B(tài)。因此,進(jìn)程阻塞是進(jìn)程自身的一種主動(dòng)行為,只有處于運(yùn)行中的進(jìn)程才可以將自身轉(zhuǎn)化為阻塞狀態(tài)。<font color="#FF0000">當(dāng)進(jìn)程被阻塞,它是不占用CPU資源的。</font>

文件描述符(fd, File Descriptor)

FD用于描述指向文件的引用的抽象化概念。文件描述符在形式上是一個(gè)非負(fù)整數(shù)。實(shí)際上,它是一個(gè)索引值,指向內(nèi)核為每一個(gè)進(jìn)程所維護(hù)的該進(jìn)程打開(kāi)文件的記錄表。當(dāng)程序打開(kāi)一個(gè)現(xiàn)有文件或者創(chuàng)建一個(gè)新文件時(shí),內(nèi)核向進(jìn)程返回一個(gè)文件描述符。在程序設(shè)計(jì)中,一些涉及底層的程序編寫(xiě)往往會(huì)圍繞著文件描述符展開(kāi)。但是文件描述符這一概念往往只適用于UNIX、Linux這樣的操作系統(tǒng)。

緩存I/O

緩存IO又被稱(chēng)作標(biāo)準(zhǔn)IO,大多數(shù)文件系統(tǒng)的默認(rèn)IO 操作都是緩存IO。在Linux的緩存IO 機(jī)制中,操作系統(tǒng)會(huì)將 IO 的數(shù)據(jù)緩存在文件系統(tǒng)的頁(yè)緩存( page cache )中,也就是說(shuō),數(shù)據(jù)會(huì)先被拷貝到操作系統(tǒng)內(nèi)核的緩沖區(qū)中,然后才會(huì)從操作系統(tǒng)內(nèi)核的緩沖區(qū)拷貝到應(yīng)用程序的地址空間。

緩存IO的缺點(diǎn):

數(shù)據(jù)在傳輸過(guò)程中需要在應(yīng)用程序地址空間和內(nèi)核進(jìn)行多次數(shù)據(jù)拷貝操作,這些數(shù)據(jù)拷貝操作所帶來(lái)的 CPU 以及內(nèi)存開(kāi)銷(xiāo)是非常大的。

Linux下的五種I/O模型

Linux下主要有以下五種I/O模型:

  1. 阻塞I/O(blocking IO)
  2. 非阻塞I/O (nonblocking I/O)
  3. I/O 復(fù)用 (I/O multiplexing)
  4. 信號(hào)驅(qū)動(dòng)I/O (signal driven I/O (SIGIO))
  5. 異步I/O (asynchronous I/O)

阻塞IO模型

進(jìn)程會(huì)一直阻塞,直到數(shù)據(jù)拷貝完成
應(yīng)用程序調(diào)用一個(gè)IO函數(shù),導(dǎo)致應(yīng)用程序阻塞,等待數(shù)據(jù)準(zhǔn)備好。數(shù)據(jù)準(zhǔn)備好后,從內(nèi)核拷貝到用戶空間,IO函數(shù)返回成功指示。阻塞IO模型圖如下所示:

blocking-io

非阻塞IO模型

通過(guò)進(jìn)程反復(fù)調(diào)用IO函數(shù),在數(shù)據(jù)拷貝過(guò)程中,進(jìn)程是阻塞的。模型圖

non-blocking-io

IO復(fù)用模型

主要是select和epoll。一個(gè)線程可以對(duì)多個(gè)IO端口進(jìn)行監(jiān)聽(tīng),當(dāng)socket有讀寫(xiě)事件時(shí)分發(fā)到具體的線程進(jìn)行處理。模型如下所示:

io-multiplexing

信號(hào)驅(qū)動(dòng)IO模型

信號(hào)驅(qū)動(dòng)式I/O:首先我們?cè)试SSocket進(jìn)行信號(hào)驅(qū)動(dòng)IO,并安裝一個(gè)信號(hào)處理函數(shù),進(jìn)程繼續(xù)運(yùn)行并不阻塞。當(dāng)數(shù)據(jù)準(zhǔn)備好時(shí),進(jìn)程會(huì)收到一個(gè)SIGIO信號(hào),可以在信號(hào)處理函數(shù)中調(diào)用I/O操作函數(shù)處理數(shù)據(jù)。過(guò)程如下圖所示:

sigio

異步IO模型

相對(duì)于同步IO,異步IO不是順序執(zhí)行。用戶進(jìn)程進(jìn)行aio_read系統(tǒng)調(diào)用之后,無(wú)論內(nèi)核數(shù)據(jù)是否準(zhǔn)備好,都會(huì)直接返回給用戶進(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)程都是非阻塞的。異步過(guò)程如下圖所示:

aio

五種IO模型比較

阻塞IO和非阻塞IO的區(qū)別
調(diào)用阻塞IO后進(jìn)程會(huì)一直等待對(duì)應(yīng)的進(jìn)程完成,而非阻塞IO不會(huì)等待對(duì)應(yīng)的進(jìn)程完成,在kernel還在準(zhǔn)備數(shù)據(jù)的情況下直接返回。
同步IO和異步IO的區(qū)別
首先看一下POSIX中對(duì)這兩個(gè)IO的定義:

A synchronous I/O operation causes the requesting process to be blocked until that I/O operation completes;
An asynchronous I/O operation does not cause the requesting process to be blocked;

<font color="#FF0000">兩者的區(qū)別就在于synchronous IO做”IO operation”的時(shí)候會(huì)將process阻塞。</font>按照這個(gè)定義,之前所述的blocking IO,non-blocking IO,IO multiplexing都屬于synchronous IO。注意到non-blocking IO會(huì)一直輪詢(xún)(polling),這個(gè)過(guò)程是沒(méi)有阻塞的,但是recvfrom階段blocking IO,non-blocking IO和IO multiplexing都是阻塞的。
而asynchronous IO則不一樣,當(dāng)進(jìn)程發(fā)起IO 操作之后,就直接返回再也不理睬了,直到kernel發(fā)送一個(gè)信號(hào),告訴進(jìn)程說(shuō)IO完成。在這整個(gè)過(guò)程中,進(jìn)程完全沒(méi)有被block。

io-diff

IO復(fù)用之select、poll、epoll簡(jiǎn)介

epoll是linux所特有,而select是POSIX所規(guī)定,一般操作系統(tǒng)均有實(shí)現(xiàn)。

select

select本質(zhì)是通過(guò)設(shè)置或檢查存放fd標(biāo)志位的數(shù)據(jù)結(jié)構(gòu)來(lái)進(jìn)行下一步處理。缺點(diǎn)是:

  1. 單個(gè)進(jìn)程可監(jiān)視的fd數(shù)量被限制,即能監(jiān)聽(tīng)端口的大小有限。一般來(lái)說(shuō)和系統(tǒng)內(nèi)存有關(guān),具體數(shù)目可以cat /proc/sys/fs/file-max察看。32位默認(rèn)是1024個(gè),64位默認(rèn)為2048個(gè)
  2. 對(duì)socket進(jìn)行掃描時(shí)是線性掃描,即采用輪詢(xún)方法,效率低。當(dāng)套接字比較多的時(shí)候,每次select()都要遍歷FD_SETSIZE個(gè)socket來(lái)完成調(diào)度,不管socket是否活躍都遍歷一遍。會(huì)浪費(fèi)很多CPU時(shí)間。如果能給套接字注冊(cè)某個(gè)回調(diào)函數(shù),當(dāng)他們活躍時(shí),自動(dòng)完成相關(guān)操作,就避免了輪詢(xún),這正是epoll與kqueue做的
  3. 需要維護(hù)一個(gè)用來(lái)存放大量fd的數(shù)據(jù)結(jié)構(gòu),會(huì)使得用戶空間和內(nèi)核空間在傳遞該結(jié)構(gòu)時(shí)復(fù)制開(kāi)銷(xiāo)大

poll

poll本質(zhì)和select相同,將用戶傳入的數(shù)據(jù)拷貝到內(nèi)核空間,然后查詢(xún)每個(gè)fd對(duì)應(yīng)的設(shè)備狀態(tài),如果設(shè)備就緒則在設(shè)備等待隊(duì)列中加入一項(xiàng)并繼續(xù)遍歷,如果遍歷所有fd后沒(méi)有發(fā)現(xiàn)就緒設(shè)備,則掛起當(dāng)前進(jìn)程,直到設(shè)備就緒或主動(dòng)超時(shí),被喚醒后又要再次遍歷fd。它沒(méi)有最大連接數(shù)的限制,原因是它是基于鏈表來(lái)存儲(chǔ)的,但缺點(diǎn)是:

  1. 大量的fd的數(shù)組被整體復(fù)制到用戶態(tài)和內(nèi)核空間之間,不管有無(wú)意義。
  2. poll還有一個(gè)特點(diǎn)“水平觸發(fā)”,如果報(bào)告了fd后,沒(méi)有被處理,那么下次poll時(shí)再次報(bào)告該ffd。

epoll

epoll支持水平觸發(fā)和邊緣觸發(fā),最大特點(diǎn)在于邊緣觸發(fā),只告訴哪些fd剛剛變?yōu)榫途w態(tài),并且只通知一次。還有一特點(diǎn)是,epoll使用“事件”的就緒通知方式,通過(guò)epoll_ctl注冊(cè)fd,一量該fd就緒,內(nèi)核就會(huì)采用類(lèi)似callback的回調(diào)機(jī)制來(lái)激活該fd,epoll_wait便可以收到通知。epoll的優(yōu)點(diǎn):

  1. 沒(méi)有最大并發(fā)連接的限制。
  2. 效率提升,只有活躍可用的FD才會(huì)調(diào)用callback函數(shù)。
  3. 內(nèi)存拷貝,利用mmap()文件映射內(nèi)存加速與內(nèi)核空間的消息傳遞。

select、poll、epoll區(qū)別總結(jié):

支持一個(gè)進(jìn)程打開(kāi)連接數(shù) IO效率 消息傳遞方式
select 32位機(jī)器1024個(gè),64位2048個(gè) IO效率低 內(nèi)核需要將消息傳遞到用戶空間,都需要內(nèi)核拷貝動(dòng)作
poll 無(wú)限制,原因基于鏈表存儲(chǔ) IO效率低 內(nèi)核需要將消息傳遞到用戶空間,都需要內(nèi)核拷貝動(dòng)作
epoll 有上限,但很大,2G內(nèi)存20W左右 只有活躍的socket才調(diào)用callback,IO效率高 通過(guò)內(nèi)核與用戶空間共享一塊內(nèi)存來(lái)實(shí)現(xiàn)

參考資料:

《Unix網(wǎng)絡(luò)編程》

最后編輯于
?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請(qǐng)聯(lián)系作者
【社區(qū)內(nèi)容提示】社區(qū)部分內(nèi)容疑似由AI輔助生成,瀏覽時(shí)請(qǐng)結(jié)合常識(shí)與多方信息審慎甄別。
平臺(tái)聲明:文章內(nèi)容(如有圖片或視頻亦包括在內(nèi))由作者上傳并發(fā)布,文章內(nèi)容僅代表作者本人觀點(diǎn),簡(jiǎn)書(shū)系信息發(fā)布平臺(tái),僅提供信息存儲(chǔ)服務(wù)。

相關(guān)閱讀更多精彩內(nèi)容

友情鏈接更多精彩內(nèi)容