Linux操作系統(tǒng)的IO模型詳解
Linux操作系統(tǒng)中IO模型是很常見(jiàn)的。下面由學(xué)習(xí)啦小編為大家整理了Linux操作系統(tǒng)的IO模型詳解,希望對(duì)大家有幫助!
Linux操作系統(tǒng)的IO模型詳解
Linux下的五種IO模型
阻塞IO(blocking IO)
非阻塞IO (nonblocking IO)
IO復(fù)用(select 和poll) (IO multiplexing)
信號(hào)驅(qū)動(dòng)IO (signal driven IO (SIGIO))
異步IO (asynchronous IO (the POSIX aio_functions))
前四種都是同步,只有最后一種才是異步IO。
阻塞IO模型
在這個(gè)模型中,應(yīng)用程序(application)為了執(zhí)行這個(gè)read操作,會(huì)調(diào)用相應(yīng)的一個(gè)system call,將系統(tǒng)控制權(quán)交給kernel,然后就進(jìn)行等待(這其實(shí)就是被阻塞了)。kernel開(kāi)始執(zhí)行這個(gè)system call,執(zhí)行完畢后會(huì)向應(yīng)用程序返回響應(yīng),應(yīng)用程序得到響應(yīng)后,就不再阻塞,并進(jìn)行后面的工作。
非阻塞IO
在linux下,應(yīng)用程序可以通過(guò)設(shè)置文件描述符的屬性O(shè)_NONBLOCK,IO操作可以立即返回,但是并不保證IO操作成功。也就是說(shuō),當(dāng)應(yīng)用程序設(shè)置了O_NONBLOCK之后,執(zhí)行write操作,調(diào)用相應(yīng)的system call,這個(gè)system call會(huì)從內(nèi)核中立即返回。但是在這個(gè)返回的時(shí)間點(diǎn),數(shù)據(jù)可能還沒(méi)有被真正的寫(xiě)入到指定的地方。也就是說(shuō),kernel只是很快的返回了這個(gè) system call(只有立馬返回,應(yīng)用程序才不會(huì)被這個(gè)IO操作blocking),但是這個(gè)system call具體要執(zhí)行的事情(寫(xiě)數(shù)據(jù))可能并沒(méi)有完成。而對(duì)于應(yīng)用程序,雖然這個(gè)IO操作很快就返回了,但是它并不知道這個(gè)IO操作是否真的成功了,為了知道IO操作是否成功,一般有兩種策略:一是需要應(yīng)用程序主動(dòng)地循環(huán)地去問(wèn)kernel(這種方法就是同步非阻塞IO);二是采用IO通知機(jī)制,比如:IO多路復(fù)用(這種方法屬于異步阻塞IO)或信號(hào)驅(qū)動(dòng)IO(這種方法屬于異步非阻塞IO)。
IO多路復(fù)用(異步阻塞IO)
和之前一樣,應(yīng)用程序要執(zhí)行read操作,因此調(diào)用一個(gè)system call,這個(gè)system call被傳遞給了kernel。但在應(yīng)用程序這邊,它調(diào)用system call之后,并不等待kernel的返回結(jié)果而是立即返回,雖然立即返回的調(diào)用函數(shù)是一個(gè)異步的方式,但應(yīng)用程序會(huì)被像select()、poll和epoll等具有復(fù)用多個(gè)文件描述符的函數(shù)阻塞住,一直等到這個(gè)system call有結(jié)果返回了,再通知應(yīng)用程序。也就是說(shuō),“在這種模型中,IO函數(shù)是非阻塞的,使用阻塞 select、poll、epoll系統(tǒng)調(diào)用來(lái)確定一個(gè) 或多個(gè)IO 描述符何時(shí)能操作。”所以,從IO操作的實(shí)際效果來(lái)看,異步阻塞IO和第一種同步阻塞IO是一樣的,應(yīng)用程序都是一直等到IO操作成功之后(數(shù)據(jù)已經(jīng)被寫(xiě)入或者讀取),才開(kāi)始進(jìn)行下面的工作。不同點(diǎn)在于異步阻塞IO用一個(gè)select函數(shù)可以為多個(gè)描述符提供通知,提高了并發(fā)性。舉個(gè)例子:假如有一萬(wàn)個(gè)并發(fā)的read請(qǐng)求,但是網(wǎng)絡(luò)上仍然沒(méi)有數(shù)據(jù),此時(shí)這一萬(wàn)個(gè)read會(huì)同時(shí)各自阻塞,現(xiàn)在用select、poll、epoll這樣的函數(shù)來(lái)專(zhuān)門(mén)負(fù)責(zé)阻塞同時(shí)監(jiān)聽(tīng)這一萬(wàn)個(gè)請(qǐng)求的狀態(tài),一旦有數(shù)據(jù)到達(dá)了就負(fù)責(zé)通知,這樣就將之前一萬(wàn)個(gè)的各自為戰(zhàn)的等待與阻塞轉(zhuǎn)為一個(gè)專(zhuān)門(mén)的函數(shù)來(lái)負(fù)責(zé)與管理。與此同時(shí),異步阻塞IO和第二種同步非阻塞IO的區(qū)別在于:同步非阻塞IO是需要應(yīng)用程序主動(dòng)地循環(huán)去詢(xún)問(wèn)是否有操作數(shù)據(jù)可操作,而異步阻塞IO是通過(guò)像select和poll等這樣的IO多路復(fù)用函數(shù)來(lái)同時(shí)檢測(cè)多個(gè)事件句柄來(lái)告知應(yīng)用程序是否可以有數(shù)據(jù)操作。
信號(hào)驅(qū)動(dòng)IO (signal driven IO (SIGIO))
應(yīng)用程序提交read請(qǐng)求的system call,然后,kernel開(kāi)始處理相應(yīng)的IO操作,而同時(shí),應(yīng)用程序并不等kernel返回響應(yīng),就會(huì)開(kāi)始執(zhí)行其他的處理操作(應(yīng)用程序沒(méi)有被IO操作所阻塞)。當(dāng)kernel執(zhí)行完畢,返回read的響應(yīng),就會(huì)產(chǎn)生一個(gè)信號(hào)或執(zhí)行一個(gè)基于線程的回調(diào)函數(shù)來(lái)完成這次 IO 處理過(guò)程。
從理論上說(shuō),阻塞IO、IO復(fù)用和信號(hào)驅(qū)動(dòng)的IO都是同步IO模型。因?yàn)樵谶@三種模型中,IO的讀寫(xiě)操作都是在IO事件發(fā)生之后由應(yīng)用程序來(lái)完成。而POSIX規(guī)范所定義的異步IO模型則不同。對(duì)異步IO而言,用戶(hù)可以直接對(duì)IO執(zhí)行讀寫(xiě)操作,這些操作告訴內(nèi)核用戶(hù)讀寫(xiě)緩沖區(qū)的位置,以及IO操作完成后內(nèi)核通知應(yīng)用程序的方式。異步IO讀寫(xiě)操作總是立即返回,而不論IO是否阻塞的,因?yàn)檎嬷鞯淖x寫(xiě)操作已經(jīng)由內(nèi)核接管。也就是說(shuō),同步IO模型要求用戶(hù)代碼自行執(zhí)行IO操作(將數(shù)據(jù)從內(nèi)核緩沖區(qū)讀入用戶(hù)緩沖區(qū),或?qū)?shù)據(jù)從用戶(hù)緩沖區(qū)寫(xiě)入內(nèi)核緩沖區(qū)),而異步IO機(jī)制則是由內(nèi)核來(lái)執(zhí)行IO操作(數(shù)據(jù)在內(nèi)核緩沖區(qū)和用戶(hù)緩沖區(qū)之間的移動(dòng)是由內(nèi)核在后臺(tái)完成的)。你可以這樣認(rèn)為,同步IO向應(yīng)用程序通知的是IO就緒事件,而異步IO向應(yīng)用程序通知的是IO完成事件。linux環(huán)境下,aio.h頭文件中定義的函數(shù)提供了對(duì)異步IO的支持。
異步IO (asynchronous IO (the POSIX aio_functions))
異步IO與上面的異步概念是一樣的, 當(dāng)一個(gè)異步過(guò)程調(diào)用發(fā)出后,調(diào)用者不能立刻得到結(jié)果,實(shí)際處理這個(gè)調(diào)用的函數(shù)在完成后,通過(guò)狀態(tài)、通知和回調(diào)來(lái)通知調(diào)用者的輸入輸出操作。異步IO的工作機(jī)制是:告知內(nèi)核啟動(dòng)某個(gè)操作,并讓內(nèi)核在整個(gè)操作完成后通知我們,這種模型與信號(hào)驅(qū)動(dòng)的IO區(qū)別在于,信號(hào)驅(qū)動(dòng)IO是由內(nèi)核通知我們何時(shí)可以啟動(dòng)一個(gè)IO操作,這個(gè)IO操作由用戶(hù)自定義的信號(hào)函數(shù)來(lái)實(shí)現(xiàn),而異步IO模型是由內(nèi)核告知我們IO操作何時(shí)完成。為了實(shí)現(xiàn)異步IO,專(zhuān)門(mén)定義了一套以aio開(kāi)頭的API,如:aio_read.
小結(jié):前四種模型–阻塞IO、非阻塞IO、多路復(fù)用IO和信號(hào)驅(qū)動(dòng)IO都屬于同步模式,因?yàn)槠渲姓嬲腎O操作(函數(shù))都將會(huì)阻塞進(jìn)程,只有異步IO模型真正實(shí)現(xiàn)了IO操作的異步性。
IO復(fù)用
為了解釋這個(gè)名詞,首先來(lái)理解下復(fù)用這個(gè)概念,復(fù)用也就是共用的意思,這樣理解還是有些抽象,為此,咱們來(lái)理解下復(fù)用在通信領(lǐng)域的使用,在通信領(lǐng)域中為了充分利用網(wǎng)絡(luò)連接的物理介質(zhì),往往在同一條網(wǎng)絡(luò)鏈路上采用時(shí)分復(fù)用或頻分復(fù)用的技術(shù)使其在同一鏈路上傳輸多路信號(hào),到這里我們就基本上理解了復(fù)用的含義,即公用某個(gè)“介質(zhì)”來(lái)盡可能多的做同一類(lèi)(性質(zhì))的事,那IO復(fù)用的“介質(zhì)”是什么呢?為此我們首先來(lái)看看服務(wù)器編程的模型,客戶(hù)端發(fā)來(lái)的請(qǐng)求服務(wù)端會(huì)產(chǎn)生一個(gè)進(jìn)程來(lái)對(duì)其進(jìn)行服務(wù),每當(dāng)來(lái)一個(gè)客戶(hù)請(qǐng)求就產(chǎn)生一個(gè)進(jìn)程來(lái)服務(wù),然而進(jìn)程不可能無(wú)限制的產(chǎn)生,因此為了解決大量客戶(hù)端訪問(wèn)的問(wèn)題,引入了IO復(fù)用技術(shù),即:一個(gè)進(jìn)程可以同時(shí)對(duì)多個(gè)客戶(hù)請(qǐng)求進(jìn)行服務(wù)。也就是說(shuō)IO復(fù)用的“介質(zhì)”是進(jìn)程(準(zhǔn)確的說(shuō)復(fù)用的是select和poll,因?yàn)檫M(jìn)程也是靠調(diào)用select和poll來(lái)實(shí)現(xiàn)的),復(fù)用一個(gè)進(jìn)程(select和poll)來(lái)對(duì)多個(gè)IO進(jìn)行服務(wù),雖然客戶(hù)端發(fā)來(lái)的IO是并發(fā)的但是IO所需的讀寫(xiě)數(shù)據(jù)多數(shù)情況下是沒(méi)有準(zhǔn)備好的,因此就可以利用一個(gè)函數(shù)(select和poll)來(lái)監(jiān)聽(tīng)I(yíng)O所需的這些數(shù)據(jù)的狀態(tài),一旦IO有數(shù)據(jù)可以進(jìn)行讀寫(xiě)了,進(jìn)程就來(lái)對(duì)這樣的IO進(jìn)行服務(wù)。
理解完IO復(fù)用后,我們?cè)趤?lái)看下實(shí)現(xiàn)IO復(fù)用中的三個(gè)API(select、poll和epoll)的區(qū)別和聯(lián)系,select,poll,epoll都是IO多路復(fù)用的機(jī)制,IO多路復(fù)用就是通過(guò)一種機(jī)制,可以監(jiān)視多個(gè)描述符,一旦某個(gè)描述符就緒(一般是讀就緒或者寫(xiě)就緒),能夠通知應(yīng)用程序進(jìn)行相應(yīng)的讀寫(xiě)操作。但select,poll,epoll本質(zhì)上都是同步IO,因?yàn)樗麄兌夹枰谧x寫(xiě)事件就緒后自己負(fù)責(zé)進(jìn)行讀寫(xiě),也就是說(shuō)這個(gè)讀寫(xiě)過(guò)程是阻塞的,而異步IO則無(wú)需自己負(fù)責(zé)進(jìn)行讀寫(xiě),異步IO的實(shí)現(xiàn)會(huì)負(fù)責(zé)把數(shù)據(jù)從內(nèi)核拷貝到用戶(hù)空間。三者的原型如下所示:
int select(int nfds, fd_set *readfds, fd_set *writefds, fd_set *exceptfds, struct timeval *timeout);
int poll(struct pollfd *fds, nfds_t nfds, int timeout);
int epoll_wait(int epfd, struct epoll_event *events, int maxevents, int timeout);
補(bǔ)充:相關(guān)的概念說(shuō)明
用戶(hù)空間與內(nèi)核空間
現(xiàn)在操作系統(tǒng)都是采用虛擬存儲(chǔ)器,那么對(duì)32位操作系統(tǒng)而言,它的尋址空間(虛擬存儲(chǔ)空間)為4G(2的32次方)。操作系統(tǒng)的核心是內(nèi)核,獨(dú)立于普通的應(yīng)用程序,可以訪問(wèn)受保護(hù)的內(nèi)存空間,也有訪問(wèn)底層硬件設(shè)備的所有權(quán)限。為了保證用戶(hù)進(jìn)程不能直接操作內(nèi)核(kernel),保證內(nèi)核的安全,操作系統(tǒng)將虛擬空間劃分為兩部分,一部分為內(nèi)核空間,一部分為用戶(hù)空間。針對(duì)linux操作系統(tǒng)而言,將最高的1G字節(jié)(從虛擬地址0xC0000000到0xFFFFFFFF),供內(nèi)核使用,稱(chēng)為內(nèi)核空間,而將較低的3G字節(jié)(從虛擬地址0×00000000到0xBFFFFFFF),供各個(gè)進(jìn)程使用,稱(chēng)為用戶(hù)空間。
進(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ò)下面這些變化:
保存處理機(jī)上下文,包括程序計(jì)數(shù)器和其他寄存器。
更新PCB信息。
把進(jìn)程的PCB移入相應(yīng)的隊(duì)列,如就緒、在某事件阻塞等隊(duì)列。 選擇另一個(gè)進(jìn)程執(zhí)行,并更新其PCB。
更新內(nèi)存管理的數(shù)據(jù)結(jié)構(gòu)。
恢復(fù)處理機(jī)上下文。
進(jìn)程的阻塞
正在執(zhí)行的進(jìn)程,由于期待的某些事件未發(fā)生,如請(qǐng)求系統(tǒng)資源失敗、等待某種操作的完成、新數(shù)據(jù)尚未到達(dá)或無(wú)新工作做等,則由系統(tǒng)自動(dòng)執(zhí)行阻塞原語(yǔ)(Block),使自己由運(yùn)行狀態(tài)變?yōu)樽枞麪顟B(tài)??梢?jiàn),進(jìn)程的阻塞是進(jìn)程自身的一種主動(dòng)行為,也因此只有處于運(yùn)行態(tài)的進(jìn)程(獲得CPU),才可能將其轉(zhuǎn)為阻塞狀態(tài)。當(dāng)進(jìn)程進(jìn)入阻塞狀態(tài),是不占用CPU資源的。
文件描述符
文件描述符(File descriptor)是計(jì)算機(jī)科學(xué)中的一個(gè)術(shù)語(yǔ),是一個(gè)用于表述指向文件的引用的抽象化概念。
文件描述符在形式上是一個(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)。
緩存 IO
緩存 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)是非常大的。
同步與異步 & 阻塞與非阻塞
在進(jìn)行網(wǎng)絡(luò)編程時(shí),我們常常見(jiàn)到同步(Sync)/異步(Async),阻塞(Block)/非阻塞(Unblock)四種調(diào)用方式,先理解一些概念性的東西。
1.同步與異步
同步與異步同步和異步關(guān)注的是消息通信機(jī)制 (synchronous communication/ asynchronous communication)所謂同步,就是在發(fā)出一個(gè)調(diào)用時(shí),在沒(méi)有得到結(jié)果之前,該調(diào)用就不返回。但是一旦調(diào)用返回,就得到返回值了。換句話說(shuō),就是由調(diào)用者主動(dòng)等待這個(gè)調(diào)用的結(jié)果。
而異步則是相反,調(diào)用在發(fā)出之后,這個(gè)調(diào)用就直接返回了,所以沒(méi)有返回結(jié)果。換句話說(shuō),當(dāng)一個(gè)異步過(guò)程調(diào)用發(fā)出后,調(diào)用者不會(huì)立刻得到結(jié)果。而是在調(diào)用發(fā)出后,被調(diào)用者通過(guò)狀態(tài)、通知來(lái)通知調(diào)用者,或通過(guò)回調(diào)函數(shù)處理這個(gè)調(diào)用。
典型的異步編程模型比如Node.js。
2016.4.17更新:
POSIX對(duì)這兩個(gè)術(shù)語(yǔ)的定義:
同步I/O操作:導(dǎo)致請(qǐng)求進(jìn)程阻塞,直到I/O操作完成
異步I/O操作:不導(dǎo)致請(qǐng)求進(jìn)程阻塞
2. 阻塞與非阻塞
阻塞和非阻塞關(guān)注的是程序在等待調(diào)用結(jié)果(消息,返回值)時(shí)的狀態(tài)。
阻塞調(diào)用是指調(diào)用結(jié)果返回之前,當(dāng)前線程會(huì)被掛起。調(diào)用線程只有在得到結(jié)果之后才會(huì)返回。非阻塞調(diào)用指在不能立刻得到結(jié)果之前,該調(diào)用不會(huì)阻塞當(dāng)前線程。
關(guān)于阻塞/非阻塞 & 同步/異步更加形象的比喻
老張愛(ài)喝茶,廢話不說(shuō),煮開(kāi)水。 出場(chǎng)人物:老張,水壺兩把(普通水壺,簡(jiǎn)稱(chēng)水壺;會(huì)響的水壺,簡(jiǎn)稱(chēng)響水壺)。
1. 老張把水壺放到火上,立等水開(kāi)。(同步阻塞) 老張覺(jué)得自己有點(diǎn)傻
2. 老張把水壺放到火上,去客廳看電視,時(shí)不時(shí)去廚房看看水開(kāi)沒(méi)有。(同步非阻塞) 老張還是覺(jué)得自己有點(diǎn)傻,于是變高端了,買(mǎi)了把會(huì)響笛的那種水壺。水開(kāi)之后,能大聲發(fā)出嘀~~~~的噪音。
3. 老張把響水壺放到火上,立等水開(kāi)。(異步阻塞) 老張覺(jué)得這樣傻等意義不大
4. 老張把響水壺放到火上,去客廳看電視,水壺響之前不再去看它了,響了再去拿壺。(異步非阻塞) 老張覺(jué)得自己聰明了。
所謂同步異步,只是對(duì)于水壺而言。普通水壺,同步;響水壺,異步。雖然都能干活,但響水壺可以在自己完工之后,提示老張水開(kāi)了。這是普通水壺所不能及的。同步只能讓調(diào)用者去輪詢(xún)自己(情況2中),造成老張效率的低下。
所謂阻塞非阻塞,僅僅對(duì)于老張而言。立等的老張,阻塞;看視的老張,非阻塞。情況1和情況3中老張就是阻塞的,媳婦喊他都不知道。雖然3中響水壺是異步的,可對(duì)于立等的老張沒(méi)有太大的意義。所以一般異步是配合非阻塞使用的,這樣才能發(fā)揮異步的效用。