來記一筆linux下的io相關(guān)的系統(tǒng)調(diào)用select,poll,epoll。這三個系統(tǒng)調(diào)用的本質(zhì)其實都是io多路復(fù)用,有人覺得epoll是信號驅(qū)動io,其實不然。
先來說下linux下的io大背景。在linux系統(tǒng)上一切皆文件,不管是讀寫文件,管道,網(wǎng)絡(luò)io,或者其他外設(shè),最后都會調(diào)用read和write系統(tǒng)調(diào)用。這兩個系統(tǒng)調(diào)用都有兩個流程,以read為列
1 把數(shù)據(jù)從外設(shè)讀到內(nèi)核的緩沖區(qū)
2 把數(shù)據(jù)從內(nèi)核地址空間拷貝到用戶地址空間。
這里的第二步是內(nèi)存操作,對io操作的block時間可以忽略。所以絕大多數(shù)io是block在第一步。 即使是使用非阻塞模式運行,返回eagain,也要在循環(huán)里面多次輪詢,浪費cpu時間。舉個不恰當?shù)睦?,比如你要去便利店買東西,需要排隊,而且每次只能買一樣?xùn)|西,你排著隊好不容易輪到你,大爺告訴你沒貨,又要從隊尾開始排。這樣的用戶體驗肯定很糟。
于是乎,有天大爺裝了個提示器,上面每次顯示一串可以立即提貨的貨物。你要做的是逐一比對是否有你自己想要的。這其實就是io多路復(fù)用。select和poll每次都是輪詢一堆描述符,然后返回準備好的描述符數(shù)量。區(qū)別在于poll要監(jiān)視的fd數(shù)量沒有上限。select和poll的這種實現(xiàn)決定了,這兩個系統(tǒng)調(diào)用返回以后,都必須要遍歷fds來獲取準備好的fd,所以當監(jiān)視的fd數(shù)量很多時候,性能降得厲害。
epoll和select,poll實現(xiàn)有些不同。epoll首先通過epoll_create創(chuàng)建一個efd,通過這個efd來監(jiān)控其他fd,也就是通過epoll_ctl函數(shù)來把fd添加到efd監(jiān)視列表里。這個列表是由kernel維護的。每次調(diào)用epoll_wait返回的fd全部都是已經(jīng)準備好的fd,可以直接內(nèi)存拷貝。這也是epoll為什么效率高于select和poll的原因,尤其是在idle fd數(shù)量較多的時候,這一優(yōu)勢更加明顯。但本質(zhì)上epoll還是io多路復(fù)用,只不過把更多的工作交給了kernel,用戶進程只輪詢efd一個fd。所以io多路復(fù)用還是有阻塞的,只不過不是在read,write上,而是阻塞在了select,poll,epoll上。
回到剛才買東西的例子,你也可以給大爺買包煙,然后請他老人家打電話通知你有你想要的東西,直接來提貨。這就是信號驅(qū)動io。如果你想體驗更好點,再送大爺包茶葉,大爺直接把貨送到你家,然后通知你。這就是aio,真正意義上的異步io。
相比windows而言linux對aio的支持比較一般,直到2.6.22才有了些庫。當然,異步io增加了kernel實現(xiàn)的復(fù)雜度,kernel做了更多的事。這和unix設(shè)計哲學(xué)不符。把更多的自由留給用戶這也是xnix系統(tǒng)上編程框架百花齊放的原因。