主要說明io基本概念及 linux幾種I/O模型
1. 什么是I/O
我們常說的IO,指的是文件的輸入和輸出。
linux的內(nèi)核將所有外部設(shè)備都可以看做一個(gè)文件來操作。那么我們對(duì)與外部設(shè)備的操作都可以看做對(duì)文件進(jìn)行操作。我們對(duì)一個(gè)文件的讀寫,都通過調(diào)用內(nèi)核提供的系統(tǒng)調(diào)用;內(nèi)核給我們返回一個(gè)file descriptor(fd,文件描述符)。對(duì)一個(gè)socket的讀寫也會(huì)有相應(yīng)的描述符,稱為socketfd(socket描述符)。描述符就是一個(gè)數(shù)字(可以理解為一個(gè)索引),指向內(nèi)核中一個(gè)結(jié)構(gòu)體(文件路徑,數(shù)據(jù)區(qū),等一些屬性)。應(yīng)用程序?qū)ξ募淖x寫就通過對(duì)描述符的讀寫完成。
一個(gè)基本的IO,它會(huì)涉及到兩個(gè)系統(tǒng)對(duì)象,一個(gè)是調(diào)用這個(gè)IO的進(jìn)程對(duì)象,另一個(gè)就是系統(tǒng)內(nèi)核(kernel)。當(dāng)一個(gè)read操作發(fā)生時(shí),它會(huì)經(jīng)歷兩個(gè)階段:
- 通過read系統(tǒng)調(diào)用想內(nèi)核發(fā)起讀請(qǐng)求。
- 內(nèi)核向硬件發(fā)送讀指令,并等待讀就緒。
- 內(nèi)核把將要讀取的數(shù)據(jù)復(fù)制到描述符所指向的內(nèi)核緩存區(qū)中。
- 將數(shù)據(jù)從內(nèi)核緩存區(qū)拷貝到用戶進(jìn)程空間中。
拿一次磁盤文件讀取為例,我們要讀取的文件是存儲(chǔ)在磁盤上的,我們的目的是把它讀取到內(nèi)存中。可以把這個(gè)步驟簡(jiǎn)化成把數(shù)據(jù)從硬件(硬盤)中讀取到用戶空間中。
這里面的魚塘就可以映射成磁盤,中間過渡的魚鉤可以映射成內(nèi)核空間,最終放魚的魚簍可以映射成用戶空間。一次完整的釣魚(IO)操作,是魚(文件)從魚塘(硬盤)中轉(zhuǎn)移(拷貝)到魚簍(用戶空間)的過程
2. I/O模型
在linux系統(tǒng)下面,根據(jù)IO操作的是否被阻塞以及同步異步問題進(jìn)行分類,可以得到下面五種IO模型
2.1阻塞I/O模型
最常見的I/O模型是阻塞I/O模型,缺省情形下,所有文件操作都是阻塞的。我們以套接口為例來講解此模型。在進(jìn)程空間中調(diào)用recvfrom,其系統(tǒng)調(diào)用直到數(shù)據(jù)報(bào)到達(dá)且被拷貝到應(yīng)用進(jìn)程的緩沖區(qū)中或者發(fā)生錯(cuò)誤才返回,期間一直在等待。我們就說進(jìn)程在從調(diào)用recvfrom開始到它返回的整段時(shí)間內(nèi)是被阻塞的。

釣魚的時(shí)候,有一種方式比較愜意,比較輕松,那就是我們坐在魚竿面前,這個(gè)過程中我們什么也不做,雙手一直把著魚竿,就靜靜的等著魚兒咬鉤。一旦手上感受到魚的力道,就把魚釣起來放入魚簍中。然后再釣下一條魚。映射到Linux操作系統(tǒng)中,這就是一種最簡(jiǎn)單的IO模型,即阻塞IO。 阻塞 I/O 是最簡(jiǎn)單的 I/O 模型,一般表現(xiàn)為進(jìn)程或線程等待某個(gè)條件,如果條件不滿足,則一直等下去。條件滿足,則進(jìn)行下一步操作。
2.2 非阻塞I/O模型
進(jìn)程把一個(gè)套接口設(shè)置成非阻塞是在通知內(nèi)核:當(dāng)所請(qǐng)求的I/O操作不能滿足要求時(shí)候,不把本進(jìn)程投入睡眠,而是返回一個(gè)錯(cuò)誤。也就是說當(dāng)數(shù)據(jù)沒有到達(dá)時(shí)并不等待,而是以一個(gè)錯(cuò)誤返回。 然后通過輪詢的方式,不停的去問內(nèi)核數(shù)據(jù)準(zhǔn)備有沒有準(zhǔn)備好。如果某一次輪詢發(fā)現(xiàn)數(shù)據(jù)已經(jīng)準(zhǔn)備好了,那就把數(shù)據(jù)拷貝到用戶空間中。
釣魚的時(shí)候,在等待魚兒咬鉤的過程中,我們可以做點(diǎn)別的事情,比如玩一把王者榮耀、看一集《延禧攻略》等等。但是,我們要時(shí)不時(shí)的去看一下魚竿,一旦發(fā)現(xiàn)有魚兒上鉤了,就把魚釣上來。
2.3 I/O復(fù)用模型
linux提供select/poll,進(jìn)程通過將一個(gè)或多個(gè)fd傳遞給select或poll系統(tǒng)調(diào)用,阻塞在select;這樣select/poll可以幫我們偵測(cè)許多fd是否就緒。但是select/poll是順序掃描fd是否就緒,而且支持的fd數(shù)量有限。linux還提供了一個(gè)epoll系統(tǒng)調(diào)用,epoll是基于事件驅(qū)動(dòng)方式,而不是順序掃描,當(dāng)有fd就緒時(shí),立即回調(diào)函數(shù)rollback;

IO多路轉(zhuǎn)接是多了一個(gè)select函數(shù),多個(gè)進(jìn)程的IO可以注冊(cè)到同一個(gè)select上,當(dāng)用戶進(jìn)程調(diào)用該select,select會(huì)監(jiān)聽所有注冊(cè)好的IO,如果所有被監(jiān)聽的IO需要的數(shù)據(jù)都沒有準(zhǔn)備好時(shí),select調(diào)用進(jìn)程會(huì)阻塞。當(dāng)任意一個(gè)IO所需的數(shù)據(jù)準(zhǔn)備好之后,select調(diào)用就會(huì)返回,然后進(jìn)程在通過recvfrom來進(jìn)行數(shù)據(jù)拷貝。這里的IO復(fù)用模型,并沒有向內(nèi)核注冊(cè)信號(hào)處理函數(shù),所以,他并不是非阻塞的。進(jìn)程在發(fā)出select后,要等到select監(jiān)聽的所有IO操作中至少有一個(gè)需要的數(shù)據(jù)準(zhǔn)備好,才會(huì)有返回,并且也需要再次發(fā)送請(qǐng)求去進(jìn)行文件的拷貝。
我們釣魚的時(shí)候,為了保證可以最短的時(shí)間釣到最多的魚,我們同一時(shí)間擺放多個(gè)魚竿,同時(shí)釣魚。然后哪個(gè)魚竿有魚兒咬鉤了,我們就把哪個(gè)魚竿上面的魚釣起來。映射到Linux操作系統(tǒng)中,這就是IO復(fù)用模型。多個(gè)進(jìn)程的IO可以注冊(cè)到同一個(gè)管道上,這個(gè)管道會(huì)統(tǒng)一和內(nèi)核進(jìn)行交互。當(dāng)管道中的某一個(gè)請(qǐng)求需要的數(shù)據(jù)準(zhǔn)備好之后,進(jìn)程再把對(duì)應(yīng)的數(shù)據(jù)拷貝到用戶空間中。
2.4 信號(hào)驅(qū)動(dòng)異步I/O模型
首先開啟套接口信號(hào)驅(qū)動(dòng)I/O功能, 并通過系統(tǒng)調(diào)用sigaction安裝一個(gè)信號(hào)處理函數(shù)(此系統(tǒng)調(diào)用立即返回,進(jìn)程繼續(xù)工作,它是非阻塞的)。當(dāng)數(shù)據(jù)報(bào)準(zhǔn)備好被讀時(shí),就為該進(jìn)程生成一個(gè)SIGIO信號(hào)。隨即可以在信號(hào)處理程序中調(diào)用recvfrom來讀數(shù)據(jù)報(bào),井通知主循環(huán)數(shù)據(jù)已準(zhǔn)備好被處理中。也可以通知主循環(huán),讓它來讀數(shù)據(jù)報(bào)。

我們釣魚的時(shí)候,為了避免自己一遍一遍的去查看魚竿,我們可以給魚竿安裝一個(gè)報(bào)警器。當(dāng)有魚兒咬鉤的時(shí)候立刻報(bào)警。然后我們?cè)偈盏綀?bào)警后,去把魚釣起來。
映射到Linux操作系統(tǒng)中,這就是信號(hào)驅(qū)動(dòng)IO。應(yīng)用進(jìn)程在讀取文件時(shí)通知內(nèi)核,如果某個(gè) socket 的某個(gè)事件發(fā)生時(shí),請(qǐng)向我發(fā)一個(gè)信號(hào)。在收到信號(hào)后,信號(hào)對(duì)應(yīng)的處理函數(shù)會(huì)進(jìn)行后續(xù)處理
應(yīng)用進(jìn)程預(yù)先向內(nèi)核注冊(cè)一個(gè)信號(hào)處理函數(shù),然后用戶進(jìn)程返回,并且不阻塞,當(dāng)內(nèi)核數(shù)據(jù)準(zhǔn)備就緒時(shí)會(huì)發(fā)送一個(gè)信號(hào)給進(jìn)程,用戶進(jìn)程便在信號(hào)處理函數(shù)中開始把數(shù)據(jù)拷貝的用戶空間中。這種方式釣魚,和前幾種相比,所使用的工具有了一些變化,需要有一些定制(實(shí)現(xiàn)復(fù)雜)。但是釣魚的人就可以在魚兒咬鉤之前徹底做別的事兒去了。等著報(bào)警器響就行了。
2.5 異步I/O模型
告知內(nèi)核啟動(dòng)某個(gè)操作,并讓內(nèi)核在整個(gè)操作完成后(包括將數(shù)據(jù)從內(nèi)核拷貝到用戶自己的緩沖區(qū))通知我們。這種模型與信號(hào)驅(qū)動(dòng)模型的主要區(qū)別是:信號(hào)驅(qū)動(dòng)I/O:由內(nèi)核通知我們何時(shí)可以啟動(dòng)一個(gè)I/O操作;異步I/O模型:由內(nèi)核通知我們I/O操作何時(shí)完成。

用戶進(jìn)程發(fā)起aio_read操作之后,給內(nèi)核傳遞描述符、緩沖區(qū)指針、緩沖區(qū)大小等,告訴內(nèi)核當(dāng)整個(gè)操作完成時(shí),如何通知進(jìn)程,然后就立刻去做其他事情了。當(dāng)內(nèi)核收到aio_read后,會(huì)立刻返回,然后內(nèi)核開始等待數(shù)據(jù)準(zhǔn)備,數(shù)據(jù)準(zhǔn)備好以后,直接把數(shù)據(jù)拷貝到用戶控件,然后再通知進(jìn)程本次IO已經(jīng)完成。
我們釣魚的時(shí)候,采用一種高科技釣魚竿,即全自動(dòng)釣魚竿??梢宰詣?dòng)感應(yīng)魚上鉤,自動(dòng)收竿,更厲害的可以自動(dòng)把魚放進(jìn)魚簍里。然后,通知我們魚已經(jīng)釣到了,他就繼續(xù)去釣下一條魚去了。映射到Linux操作系統(tǒng)中,這就是異步IO模型。應(yīng)用進(jìn)程把IO請(qǐng)求傳給內(nèi)核后,完全由內(nèi)核去操作文件拷貝。內(nèi)核完成相關(guān)操作后,會(huì)發(fā)信號(hào)告訴應(yīng)用進(jìn)程本次IO已經(jīng)完成。
2.6 總結(jié)·
前四種都是同步IO,在內(nèi)核數(shù)據(jù)copy到用戶空間時(shí)都是阻塞的。
最后一種是異步IO,通過API把IO操作交由操作系統(tǒng)處理,當(dāng)前進(jìn)程不關(guān)心具體IO的實(shí)現(xiàn),通過回調(diào)函數(shù),或者信號(hào)量通知當(dāng)前進(jìn)程直接對(duì)IO返回結(jié)果進(jìn)行處理。
首先一個(gè)IO操作其實(shí)分成了兩個(gè)步驟:發(fā)起IO請(qǐng)求和實(shí)際的IO操作,同步IO和異步IO的區(qū)別就在于第二個(gè)步驟是否阻塞,如果實(shí)際的IO讀寫阻塞請(qǐng)求進(jìn)程,那么就是同步IO,因此阻塞IO、非阻塞IO、IO復(fù)用、信號(hào)驅(qū)動(dòng)IO都是同步IO,如果不阻塞,而是操作系統(tǒng)幫你做完IO操作再將結(jié)果返回給你,那么就是異步IO。阻塞IO和非阻塞IO的區(qū)別在于第一步,發(fā)起IO請(qǐng)求是否會(huì)被阻塞,如果阻塞直到完成那么就是傳統(tǒng)的阻塞IO,如果不阻塞,那么就是非阻塞IO。
