術(shù)語概念描述:
IO有內(nèi)存IO、網(wǎng)絡(luò)IO和磁盤IO三種蝉稳,通常我們說的IO指的是后兩者抒蚜。
阻塞和非阻塞,是函數(shù)/方法的實(shí)現(xiàn)方式耘戚,即在數(shù)據(jù)就緒之前是立刻返回還是等待嗡髓。
以文件IO為例,一個(gè)IO讀過程是文件數(shù)據(jù)從磁盤→內(nèi)核緩沖區(qū)→用戶內(nèi)存的過程。同步與異步的區(qū)別主要在于數(shù)據(jù)從內(nèi)核緩沖區(qū)→用戶內(nèi)存這個(gè)過程需不需要用戶進(jìn)程等待收津。有個(gè)數(shù)據(jù)拷貝的過程饿这,是拷貝完再通知還是在內(nèi)核緩沖區(qū)就通知。(網(wǎng)絡(luò)IO把磁盤換做網(wǎng)卡即可)
Linux IO模型
- 同步阻塞
- 同步非阻塞
- IO復(fù)用
- 信號(hào)驅(qū)動(dòng)
- 異步非阻塞
同步阻塞
去餐館吃飯撞秋,點(diǎn)一個(gè)自己最愛吃的蓋澆飯长捧,然后在原地等著一直到蓋澆飯做好,自己端到餐桌就餐部服。這就是典型的同步阻塞唆姐。當(dāng)廚師給你做飯的時(shí)候,你需要一直在那里等著廓八。
網(wǎng)絡(luò)編程中奉芦,讀取客戶端的數(shù)據(jù)需要調(diào)用recvfrom。在默認(rèn)情況下剧蹂,這個(gè)調(diào)用會(huì)一直阻塞直到數(shù)據(jù)接收完畢声功,就是一個(gè)同步阻塞的IO方式。這也是最簡(jiǎn)單的IO模型宠叼,在通常fd(文件描述句柄)較少先巴、就緒很快的情況下使用是沒有問題的其爵。
同步非阻塞
你每次點(diǎn)完飯就在那里等著,突然有一天你發(fā)現(xiàn)自己真傻伸蚯。于是摩渺,你點(diǎn)完之后,就回桌子那里坐著剂邮,然后估計(jì)差不多了摇幻,就問老板飯好了沒,如果好了就去端挥萌,沒好的話就等一會(huì)再去問绰姻,依次循環(huán)直到飯做好。這就是同步非阻塞引瀑。
這種方式在編程中對(duì)socket設(shè)置O_NONBLOCK即可狂芋。但此方式僅僅針對(duì)網(wǎng)絡(luò)IO有效,對(duì)磁盤IO并沒有作用憨栽。因?yàn)楸镜匚募蘒O就沒有被認(rèn)為是阻塞帜矾,我們所說的網(wǎng)絡(luò)IO的阻塞是因?yàn)榫W(wǎng)路IO有無限阻塞的可能,而本地文件除非是被鎖住徒像,否則是不可能無限阻塞的黍特,因此只有鎖這種情況下,O_NONBLOCK才會(huì)有作用锯蛀。而且灭衷,磁盤IO時(shí)要么數(shù)據(jù)在內(nèi)核緩沖區(qū)中直接可以返回,要么需要調(diào)用物理設(shè)備去讀取旁涤,這時(shí)候進(jìn)程的其他工作都需要等待翔曲。因此,后續(xù)的IO復(fù)用和信號(hào)驅(qū)動(dòng)IO對(duì)文件IO也是沒有意義的劈愚。
IO復(fù)用
你點(diǎn)一份飯然后循環(huán)的去問好沒好顯然有點(diǎn)得不償失瞳遍,還不如就等在那里直到準(zhǔn)備好,但是當(dāng)你點(diǎn)了好幾樣飯菜的時(shí)候菌羽,你每次都去問一下所有飯菜的狀態(tài)(未做好/已做好)肯定比你每次阻塞在那里等著好多了掠械。當(dāng)然,你問的時(shí)候是需要阻塞的注祖,一直到有準(zhǔn)備好的飯菜或者你等的不耐煩(超時(shí))猾蒂。這就引出了IO復(fù)用,也叫多路IO就緒通知是晨。這是一種進(jìn)程預(yù)先告知內(nèi)核的能力肚菠,讓內(nèi)核發(fā)現(xiàn)進(jìn)程指定的一個(gè)或多個(gè)IO條件就緒了,就通知進(jìn)程罩缴。使得一個(gè)進(jìn)程能在一連串的事件上等待蚊逢。
IO復(fù)用的實(shí)現(xiàn)方式目前主要有select层扶、poll和epoll。
select和poll的原理基本相同:
注冊(cè)待偵聽的fd(這里的fd創(chuàng)建時(shí)最好使用非阻塞)
每次調(diào)用都去檢查這些fd的狀態(tài)烙荷,當(dāng)有一個(gè)或者多個(gè)fd就緒的時(shí)候返回
返回結(jié)果中包括已就緒和未就緒的fd
相比select镜会,poll解決了單個(gè)進(jìn)程能夠打開的文件描述符數(shù)量有限制這個(gè)問題:select受限于FD_SIZE的限制,如果修改則需要修改這個(gè)宏重新編譯內(nèi)核终抽;而poll通過一個(gè)pollfd數(shù)組向內(nèi)核傳遞需要關(guān)注的事件稚叹,避開了文件描述符數(shù)量限制。此外拿诸,select和poll共同具有的一個(gè)很大的缺點(diǎn)就是包含大量fd的數(shù)組被整體復(fù)制于用戶態(tài)和內(nèi)核態(tài)地址空間之間,開銷會(huì)隨著fd數(shù)量增多而線性增大塞茅。
select和poll就類似于上面說的就餐方式亩码。但當(dāng)你每次都去詢問時(shí),老板會(huì)把所有你點(diǎn)的飯菜都輪詢一遍再告訴你情況野瘦,當(dāng)大量飯菜很長(zhǎng)時(shí)間都不能準(zhǔn)備好的情況下是很低效的描沟。于是,老板有些不耐煩了鞭光,就讓廚師每做好一個(gè)菜就記下來他吏廉。這樣每次你再去問的時(shí)候,他會(huì)直接把已經(jīng)準(zhǔn)備好的菜告訴你惰许,你再去端席覆。這就是事件驅(qū)動(dòng)IO就緒通知的方式epoll。
epoll的出現(xiàn)汹买,解決了select佩伤、poll的缺點(diǎn):
基于事件驅(qū)動(dòng)的方式,避免了每次都要把所有fd都掃描一遍晦毙。
epoll_wait只返回就緒的fd生巡。
epoll使用nmap內(nèi)存映射技術(shù)避免了內(nèi)存復(fù)制的開銷。
epoll的fd數(shù)量上限是操作系統(tǒng)的最大文件句柄數(shù)目,這個(gè)數(shù)目一般和內(nèi)存有關(guān),通常遠(yuǎn)大于1024。
此外瞳别,對(duì)于IO復(fù)用還有一個(gè)水平觸發(fā)和邊緣觸發(fā)的概念:
水平觸發(fā):當(dāng)就緒的fd未被用戶進(jìn)程處理后抄课,下一次查詢依舊會(huì)返回,這是select和poll的觸發(fā)方式窄刘。
-
邊緣觸發(fā):無論就緒的fd是否被處理,下一次不再返回。理論上性能更高遂庄,但是實(shí)現(xiàn)相當(dāng)復(fù)雜,并且任何意外的丟失事件都會(huì)造成請(qǐng)求處理錯(cuò)誤劲赠。epoll默認(rèn)使用水平觸發(fā)涛目,通過相應(yīng)選項(xiàng)可以使用邊緣觸發(fā)秸谢。
信號(hào)驅(qū)動(dòng)
上文的就餐方式還是需要你每次都去問一下飯菜狀況。于是霹肝,你再次不耐煩了估蹄,就跟老板說,哪個(gè)飯菜好了就通知我一聲吧沫换。然后就自己坐在桌子那里干自己的事情臭蚁。更甚者,你可以把手機(jī)號(hào)留給老板讯赏,自己出門垮兑,等飯菜好了直接發(fā)條短信給你。這就類似信號(hào)驅(qū)動(dòng)的IO模型漱挎。
流程如下:
開啟套接字信號(hào)驅(qū)動(dòng)IO功能
系統(tǒng)調(diào)用sigaction執(zhí)行信號(hào)處理函數(shù)(非阻塞系枪,立刻返回)
-
數(shù)據(jù)就緒(在內(nèi)核緩沖區(qū)) ,生成sigio信號(hào)磕谅,通過信號(hào)回調(diào)通知應(yīng)用來讀取數(shù)據(jù)私爷。
異步非阻塞
之前的就餐方式,到最后總是需要你自己去把飯菜端到餐桌膊夹。這下你也不耐煩了衬浑,于是就告訴老板,能不能飯好了直接端到你的面前或者送到你的家里(數(shù)據(jù)在用戶內(nèi)存就緒)放刨。這就是異步非阻塞IO了工秩。
對(duì)比信號(hào)驅(qū)動(dòng)IO,異步IO的主要區(qū)別在于:信號(hào)驅(qū)動(dòng)由內(nèi)核告訴我們何時(shí)可以開始一個(gè)IO操作(數(shù)據(jù)在內(nèi)核緩沖區(qū)中)进统,而異步IO則由內(nèi)核通知IO操作何時(shí)已經(jīng)完成(數(shù)據(jù)已經(jīng)在用戶空間中)拓诸。 異步IO又叫做事件驅(qū)動(dòng)IO,在Unix中麻昼,POSIX1003.1標(biāo)準(zhǔn)為異步方式訪問文件定義了一套庫函數(shù)奠支,定義了AIO的一系列接口。使用aio_read或者aio_write發(fā)起異步IO操作抚芦。使用aio_error檢查正在運(yùn)行的IO操作的狀態(tài)倍谜。