在 Linux 系統(tǒng)之中有一個(gè)核心武器:epoll 池,在高并發(fā)的,高吞吐的 IO 系統(tǒng)中常常見(jiàn)到 epoll 的身影。
IO 多路復(fù)用
在 Go 里最核心的是 Goroutine ,也就是所謂的協(xié)程,協(xié)程最妙的一個(gè)實(shí)現(xiàn)就是異步的代碼長(zhǎng)的跟同步代碼一樣。比如在 Go 中,網(wǎng)絡(luò) IO 的 read,write 看似都是同步代碼,其實(shí)底下都是異步調(diào)用,一般流程是:
write ( /* IO 參數(shù) */ )
請(qǐng)求入隊(duì)
等待完成
后臺(tái) loop 程序
發(fā)送網(wǎng)絡(luò)請(qǐng)求
喚醒業(yè)務(wù)方
Go 配合協(xié)程在網(wǎng)絡(luò) IO 上實(shí)現(xiàn)了異步流程的代碼同步化。核心就是用 epoll 池來(lái)管理網(wǎng)絡(luò) fd 。
實(shí)現(xiàn)形式上,后臺(tái)的程序只需要 1 個(gè)就可以負(fù)責(zé)管理多個(gè) fd 句柄,負(fù)責(zé)應(yīng)對(duì)所有的業(yè)務(wù)方的 IO 請(qǐng)求。這種一對(duì)多的 IO 模式我們就叫做 IO 多路復(fù)用。
多路是指?多個(gè)業(yè)務(wù)方(句柄)并發(fā)下來(lái)的 IO 。
復(fù)用是指?復(fù)用這一個(gè)后臺(tái)處理程序。
站在 IO 系統(tǒng)設(shè)計(jì)人員的角度,業(yè)務(wù)方咱們沒(méi)辦法提要求,因?yàn)闃I(yè)務(wù)是上帝,只有你服從的份,他們要?jiǎng)?chuàng)建多個(gè) fd,那么你就需要負(fù)責(zé)這些 fd 的處理,并且最好還要并發(fā)起來(lái)。
業(yè)務(wù)方?jīng)]法提要求,那么只能要求后臺(tái) loop 程序了!
要求什么呢?快!快!快!這就是最核心的要求,處理一定要快,要給每一個(gè) fd 通道最快的感受,要讓每一個(gè) fd 覺(jué)得,你只在給他一個(gè)人跑腿。
那有人又問(wèn)了,那我一個(gè) IO 請(qǐng)求(比如 write )對(duì)應(yīng)一個(gè)線程來(lái)處理,這樣所有的 IO 不都并發(fā)了嗎?是可以,但是有瓶頸,線程數(shù)一旦多了,性能是反倒會(huì)差的。
這里不再對(duì)比多線程和 IO 多路復(fù)用實(shí)現(xiàn)高并發(fā)之間的區(qū)別,詳細(xì)的可以去了解下 nginx 和 redis 高并發(fā)的秘密。
1 最樸實(shí)的實(shí)現(xiàn)方式?
我不用任何其他系統(tǒng)調(diào)用,能否實(shí)現(xiàn) IO 多路復(fù)用?
可以的。那么寫個(gè) for 循環(huán),每次都嘗試 IO 一下,讀/寫到了就處理,讀/寫不到就 sleep 下。這樣我們不就實(shí)現(xiàn)了 1 對(duì)多的 IO 多路復(fù)用嘛。
while True:
for each 句柄數(shù)組 {
read/write(fd, /* 參數(shù) */)
}
sleep(1s)
慢著,有個(gè)問(wèn)題,上面的程序可能會(huì)被卡死在第三行,使得整個(gè)系統(tǒng)不得運(yùn)行,為什么?
默認(rèn)情況下,我們 create 出的句柄是阻塞類型的。我們讀數(shù)據(jù)的時(shí)候,如果數(shù)據(jù)還沒(méi)準(zhǔn)備好,是會(huì)需要等待的,當(dāng)我們寫數(shù)據(jù)的時(shí)候,如果還沒(méi)準(zhǔn)備好,默認(rèn)也會(huì)卡住等待。所以,在上面?zhèn)未a第三行是可能被直接卡死,而導(dǎo)致整個(gè)線程都得到不到運(yùn)行。
舉個(gè)例子,現(xiàn)在有 11,12,13 這 3 個(gè)句柄,現(xiàn)在 11 讀寫都沒(méi)有準(zhǔn)備好,只要 read/write(11, /*參數(shù)*/) 就會(huì)被卡住,但 12,13 這兩個(gè)句柄都準(zhǔn)備好了,那遍歷句柄數(shù)組 11,12,13 的時(shí)候就會(huì)卡死在前面,后面 12,13 則得不到運(yùn)行。這不符合我們的預(yù)期,因?yàn)槲覀?IO 多路復(fù)用的 loop 線程是公共服務(wù),不能因?yàn)橐粋€(gè) fd 就直接癱瘓。
那這個(gè)問(wèn)題怎么解決?
只需要把 fd 都設(shè)置成非阻塞模式。這樣 read/write 的時(shí)候,如果數(shù)據(jù)沒(méi)準(zhǔn)備好,返回 EAGIN 的錯(cuò)誤即可,不會(huì)卡住線程,從而整個(gè)系統(tǒng)就運(yùn)轉(zhuǎn)起來(lái)了。比如上面句柄 11 還未就緒,那么 read/write(11, /*參數(shù)*/) 不會(huì)阻塞,只會(huì)報(bào)個(gè) EAGIN 的錯(cuò)誤,這種錯(cuò)誤需要特殊處理,然后 loop 線程可以繼續(xù)執(zhí)行 12,13 的讀寫。
以上就是最樸實(shí)的 IO 多路復(fù)用的實(shí)現(xiàn)了。但好像在生產(chǎn)環(huán)境沒(méi)見(jiàn)過(guò)這種 IO 多路復(fù)用的實(shí)現(xiàn)?為什么?
因?yàn)檫€不夠高級(jí)。for 循環(huán)每次要定期 sleep 1s,這個(gè)會(huì)導(dǎo)致吞吐能力極差,因?yàn)楹芸赡茉趧偤靡?sleep 的時(shí)候,所有的 fd 都準(zhǔn)備好 IO 數(shù)據(jù),而這個(gè)時(shí)候卻要硬生生的等待 1s,可想而知。。。
那有同學(xué)又要質(zhì)疑了,那 for 循環(huán)里面就不 sleep 嘛,這樣不就能及時(shí)處理了嗎?
及時(shí)是及時(shí)了,但是 CPU 估計(jì)要跑飛了。不加 sleep ,那在沒(méi)有 fd 需要處理的時(shí)候,估計(jì) CPU 都要跑到 100% 了。這個(gè)也是無(wú)法接受的。
糾結(jié)了,那 sleep 吞吐不行,不 sleep 浪費(fèi) cpu,怎么辦?
這種情況用戶態(tài)很難有所作為,只能求助內(nèi)核來(lái)提供機(jī)制協(xié)助來(lái)。因?yàn)閮?nèi)核才能及時(shí)的管理這些事件的通知和調(diào)度。
我們?cè)偈崂硐?IO 多路復(fù)用的需求和原理。IO 多路復(fù)用就是 1 個(gè)線程處理 多個(gè) fd 的模式。我們的要求是:這個(gè) “1” 就要盡可能的快,避免一切無(wú)效工作,要把所有的時(shí)間都用在處理句柄的 IO 上,不能有任何空轉(zhuǎn),sleep 的時(shí)間浪費(fèi)。
有沒(méi)有一種工具,我們把一籮筐的 fd 放到里面,只要有一個(gè) fd 能夠讀寫數(shù)據(jù),后臺(tái) loop 線程就要立馬喚醒,全部馬力跑起來(lái)。其他時(shí)間要把 cpu 讓出去。
能做到嗎?能,但這種需求只能內(nèi)核提供機(jī)制滿足你。
2 這事 Linux 內(nèi)核必須要給個(gè)說(shuō)法?
是的,想要不用 sleep 這種辣眼睛的實(shí)現(xiàn),Linux 內(nèi)核必須出手了,畢竟 IO 的處理都是內(nèi)核之中,數(shù)據(jù)好沒(méi)好內(nèi)核最清楚。
內(nèi)核一口氣提供了 3 種工具 select,poll,epoll 。
為什么有 3 種?
歷史不斷改進(jìn),矬 -》 較矬 -》 臥槽、高效 的演變而已。
Linux 還有其他方式可以實(shí)現(xiàn) IO 多路復(fù)用嗎?
好像沒(méi)有了!
這 3 種到底是做啥的?
這 3 種都能夠管理 fd 的可讀可寫事件,在所有 fd 不可讀不可寫無(wú)所事事的時(shí)候,可以阻塞線程,切走 cpu 。fd 有情況的時(shí)候,都要線程能夠要能被喚醒。
而這三種方式以 epoll 池的效率最高。為什么效率最高?
其實(shí)很簡(jiǎn)單,這里不詳說(shuō),其實(shí)無(wú)非就是 epoll 做的無(wú)用功最少,select 和 poll 或多或少都要多余的拷貝,盲猜(遍歷才知道)fd ,所以效率自然就低了。
舉個(gè)例子,以 select 和 epoll 來(lái)對(duì)比舉例,池子里管理了 1024 個(gè)句柄,loop 線程被喚醒的時(shí)候,select 都是蒙的,都不知道這 1024 個(gè) fd 里誰(shuí) IO 準(zhǔn)備好了。這種情況怎么辦?只能遍歷這 1024 個(gè) fd ,一個(gè)個(gè)測(cè)試。假如只有一個(gè)句柄準(zhǔn)備好了,那相當(dāng)于做了 1 千多倍的無(wú)效功。
epoll 則不同,從 epoll_wait 醒來(lái)的時(shí)候就能精確的拿到就緒的 fd 數(shù)組,不需要任何測(cè)試,拿到的就是要處理的。
epoll 池原理
下面我們看一下 epoll 池的使用和原理。
1 epoll 涉及的系統(tǒng)調(diào)用
epoll 的使用非常簡(jiǎn)單,只有下面 3 個(gè)系統(tǒng)調(diào)用。
epoll_create
epollctl
epollwait
就這?是的,就這么簡(jiǎn)單。
epollcreate 負(fù)責(zé)創(chuàng)建一個(gè)池子,一個(gè)監(jiān)控和管理句柄 fd 的池子;
epollctl 負(fù)責(zé)管理這個(gè)池子里的 fd 增、刪、改;
epollwait 就是負(fù)責(zé)打盹的,讓出 CPU 調(diào)度,但是只要有“事”,立馬會(huì)從這里喚醒;
2 epoll 高效的原理
Linux 下,epoll 一直被吹爆,作為高并發(fā) IO 實(shí)現(xiàn)的秘密武器。其中原理其實(shí)非常樸實(shí):epoll 的實(shí)現(xiàn)幾乎沒(méi)有做任何無(wú)效功。 我們從使用的角度切入來(lái)一步步分析下。
首先,epoll 的第一步是創(chuàng)建一個(gè)池子。這個(gè)使用 epoll_create 來(lái)做:
原型:
int epoll_create(int size);
示例:
epollfd = epoll_create(1024);
if (epollfd == -1) {
perror(“epoll_create”);
exit(EXIT_FAILURE);
}
這個(gè)池子對(duì)我們來(lái)說(shuō)是黑盒,這個(gè)黑盒是用來(lái)裝 fd 的,我們暫不糾結(jié)其中細(xì)節(jié)。我們拿到了一個(gè) epollfd ,這個(gè) epollfd 就能唯一代表這個(gè) epoll 池。注意,這里又有一個(gè)細(xì)節(jié):用戶可以創(chuàng)建多個(gè) epoll 池。
然后,我們就要往這個(gè) epoll 池里放 fd 了,這就要用到 epoll_ctl 了
原型:
int epoll_ctl(int epfd, int op, int fd, struct epoll_event *event);
示例:
if (epoll_ctl(epollfd, EPOLL_CTL_ADD, 11, &ev) == -1) {
perror(“epoll_ctl: listen_sock”);
exit(EXIT_FAILURE);
}
上面,我們就把句柄 11 放到這個(gè)池子里了,op(EPOLL_CTL_ADD)表明操作是增加、修改、刪除,event 結(jié)構(gòu)體可以指定監(jiān)聽(tīng)事件類型,可讀、可寫。
第一個(gè)跟高效相關(guān)的問(wèn)題來(lái)了,添加 fd 進(jìn)池子也就算了,如果是修改、刪除呢?怎么做到快速?
這里就涉及到你怎么管理 fd 的數(shù)據(jù)結(jié)構(gòu)了。
最常見(jiàn)的思路:用 list ,可以嗎?功能上可以,但是性能上拉垮。list 的結(jié)構(gòu)來(lái)管理元素,時(shí)間復(fù)雜度都太高 O(n),每次要一次次遍歷鏈表才能找到位置。池子越大,性能會(huì)越慢。
那有簡(jiǎn)單高效的數(shù)據(jù)結(jié)構(gòu)嗎?
有,紅黑樹(shù)。Linux 內(nèi)核對(duì)于 epoll 池的內(nèi)部實(shí)現(xiàn)就是用紅黑樹(shù)的結(jié)構(gòu)體來(lái)管理這些注冊(cè)進(jìn)程來(lái)的句柄 fd。紅黑樹(shù)是一種平衡二叉樹(shù),時(shí)間復(fù)雜度為 O(log n),就算這個(gè)池子就算不斷的增刪改,也能保持非常穩(wěn)定的查找性能。
現(xiàn)在思考第二個(gè)高效的秘密:怎么才能保證數(shù)據(jù)準(zhǔn)備好之后,立馬感知呢?
epoll_ctl 這里會(huì)涉及到一點(diǎn)。秘密就是:回調(diào)的設(shè)置。在 epoll_ctl 的內(nèi)部實(shí)現(xiàn)中,除了把句柄結(jié)構(gòu)用紅黑樹(shù)管理,另一個(gè)核心步驟就是設(shè)置 poll 回調(diào)。
思考來(lái)了:poll 回調(diào)是什么?怎么設(shè)置?
先說(shuō)說(shuō) file_operations-》poll 是什么?
在 文件描述符 fd 究竟是什么 說(shuō)過(guò),Linux 設(shè)計(jì)成一切皆是文件的架構(gòu),這個(gè)不是說(shuō)說(shuō)而已,而是隨處可見(jiàn)。實(shí)現(xiàn)一個(gè)文件系統(tǒng)的時(shí)候,就要實(shí)現(xiàn)這個(gè)文件調(diào)用,這個(gè)結(jié)構(gòu)體用 struct file_operations 來(lái)表示。這個(gè)結(jié)構(gòu)體有非常多的函數(shù),精簡(jiǎn)了一些,如下:
struct file_operations {
ssize_t (*read) (struct file *, char __user *, size_t, loff_t *);
ssize_t (*write) (struct file *, const char __user *, size_t, loff_t *);
__poll_t (*poll) (struct file *, struct poll_table_struct *);
int (*open) (struct inode *, struct file *);
int (*fsync) (struct file *, loff_t, loff_t, int datasync);
// 。。。。
};
你看到了 read,write,open,fsync,poll 等等,這些都是對(duì)文件的定制處理操作,對(duì)于文件的操作其實(shí)都是在這個(gè)框架內(nèi)實(shí)現(xiàn)邏輯而已,比如 ext2 如果有對(duì) read/write 做定制化,那么就會(huì)是 ext2_read,ext2_write,ext4 就會(huì)是 ext4_read,ext4_write。在 open 具體“文件”的時(shí)候會(huì)賦值對(duì)應(yīng)文件系統(tǒng)的 file_operations 給到 file 結(jié)構(gòu)體。
那我們很容易知道 read 是文件系統(tǒng)定制 fd 讀的行為調(diào)用,write 是文件系統(tǒng)定制 fd 寫的行為調(diào)用,file_operations-》poll 呢?
這個(gè)是定制監(jiān)聽(tīng)事件的機(jī)制實(shí)現(xiàn)。通過(guò) poll 機(jī)制讓上層能直接告訴底層,我這個(gè) fd 一旦讀寫就緒了,請(qǐng)底層硬件(比如網(wǎng)卡)回調(diào)的時(shí)候自動(dòng)把這個(gè) fd 相關(guān)的結(jié)構(gòu)體放到指定隊(duì)列中,并且喚醒操作系統(tǒng)。
舉個(gè)例子:網(wǎng)卡收發(fā)包其實(shí)走的異步流程,操作系統(tǒng)把數(shù)據(jù)丟到一個(gè)指定地點(diǎn),網(wǎng)卡不斷的從這個(gè)指定地點(diǎn)掏數(shù)據(jù)處理。請(qǐng)求響應(yīng)通過(guò)中斷回調(diào)來(lái)處理,中斷一般拆分成兩部分:硬中斷和軟中斷。poll 函數(shù)就是把這個(gè)軟中斷回來(lái)的路上再加點(diǎn)料,只要讀寫事件觸發(fā)的時(shí)候,就會(huì)立馬通知到上層,采用這種事件通知的形式就能把浪費(fèi)的時(shí)間窗就完全消失了。
劃重點(diǎn):這個(gè) poll 事件回調(diào)機(jī)制則是 epoll 池高效最核心原理。
劃重點(diǎn):epoll 池管理的句柄只能是支持了 file_operations-》poll 的文件 fd。換句話說(shuō),如果一個(gè)“文件”所在的文件系統(tǒng)沒(méi)有實(shí)現(xiàn) poll 接口,那么就用不了 epoll 機(jī)制。
第二個(gè)問(wèn)題:poll 怎么設(shè)置?
在 epoll_ctl 下來(lái)的實(shí)現(xiàn)中,有一步是調(diào)用 vfs_poll 這個(gè)里面就會(huì)有個(gè)判斷,如果 fd 所在的文件系統(tǒng)的 file_operations 實(shí)現(xiàn)了 poll ,那么就會(huì)直接調(diào)用,如果沒(méi)有,那么就會(huì)報(bào)告響應(yīng)的錯(cuò)誤碼。
static inline __poll_t vfs_poll(struct file *file, struct poll_table_struct *pt)
{
if (unlikely(!file-》f_op-》poll))
return DEFAULT_POLLMASK;
return file-》f_op-》poll(file, pt);
}
你肯定好奇 poll 調(diào)用里面究竟是實(shí)現(xiàn)了什么?
總結(jié)概括來(lái)說(shuō):掛了個(gè)鉤子,設(shè)置了喚醒的回調(diào)路徑。epoll 跟底層對(duì)接的回調(diào)函數(shù)是:ep_poll_callback,這個(gè)函數(shù)其實(shí)很簡(jiǎn)單,做兩件事情:
把事件就緒的 fd 對(duì)應(yīng)的結(jié)構(gòu)體放到一個(gè)特定的隊(duì)列(就緒隊(duì)列,ready list);
喚醒 epoll ,活來(lái)啦!
當(dāng) fd 滿足可讀可寫的時(shí)候就會(huì)經(jīng)過(guò)層層回調(diào),最終調(diào)用到這個(gè)回調(diào)函數(shù),把對(duì)應(yīng) fd 的結(jié)構(gòu)體放入就緒隊(duì)列中,從而把 epoll 從 epoll_wait 出喚醒。
這個(gè)對(duì)應(yīng)結(jié)構(gòu)體是什么?
結(jié)構(gòu)體叫做 epitem ,每個(gè)注冊(cè)到 epoll 池的 fd 都會(huì)對(duì)應(yīng)一個(gè)。
就緒隊(duì)列需要用很高級(jí)的數(shù)據(jù)結(jié)構(gòu)嗎?
就緒隊(duì)列就簡(jiǎn)單了,因?yàn)闆](méi)有查找的需求了呀,只要是在就緒隊(duì)列中的 epitem ,都是事件就緒的,必須處理的。所以就緒隊(duì)列就是一個(gè)最簡(jiǎn)單的雙指針鏈表。
小結(jié)下:epoll 之所以做到了高效,最關(guān)鍵的兩點(diǎn):
內(nèi)部管理 fd 使用了高效的紅黑樹(shù)結(jié)構(gòu)管理,做到了增刪改之后性能的優(yōu)化和平衡;
epoll 池添加 fd 的時(shí)候,調(diào)用 file_operations-》poll ,把這個(gè) fd 就緒之后的回調(diào)路徑安排好。通過(guò)事件通知的形式,做到最高效的運(yùn)行;
epoll 池核心的兩個(gè)數(shù)據(jù)結(jié)構(gòu):紅黑樹(shù)和就緒列表。紅黑樹(shù)是為了應(yīng)對(duì)用戶的增刪改需求,就緒列表是 fd 事件就緒之后放置的特殊地點(diǎn),epoll 池只需要遍歷這個(gè)就緒鏈表,就能給用戶返回所有已經(jīng)就緒的 fd 數(shù)組;
3 哪些 fd 可以用 epoll 來(lái)管理?
再來(lái)思考另外一個(gè)問(wèn)題:由于并不是所有的 fd 對(duì)應(yīng)的文件系統(tǒng)都實(shí)現(xiàn)了 poll 接口,所以自然并不是所有的 fd 都可以放進(jìn) epoll 池,那么有哪些文件系統(tǒng)的 file_operations 實(shí)現(xiàn)了 poll 接口?
首先說(shuō),類似 ext2,ext4,xfs 這種常規(guī)的文件系統(tǒng)是沒(méi)有實(shí)現(xiàn)的,換句話說(shuō),這些你最常見(jiàn)的、真的是文件的文件系統(tǒng)反倒是用不了 epoll 機(jī)制的。
那誰(shuí)支持呢?
最常見(jiàn)的就是網(wǎng)絡(luò)套接字:socket 。網(wǎng)絡(luò)也是 epoll 池最常見(jiàn)的應(yīng)用地點(diǎn)。Linux 下萬(wàn)物皆文件,socket 實(shí)現(xiàn)了一套 socket_file_operations 的邏輯( net/socket.c ):
static const struct file_operations socket_file_ops = {
.read_iter = sock_read_iter,
.write_iter = sock_write_iter,
.poll = sock_poll,
// 。。。
};
我們看到 socket 實(shí)現(xiàn)了 poll 調(diào)用,所以 socket fd 是天然可以放到 epoll 池管理的。
還有支持的嗎?
有的,很多。其實(shí) Linux 下還有兩個(gè)很典型的 fd ,常常也會(huì)放到 epoll 池里。
eventfd:eventfd 實(shí)現(xiàn)非常簡(jiǎn)單,故名思義就是專門用來(lái)做事件通知用的。使用系統(tǒng)調(diào)用 eventfd 創(chuàng)建,這種文件 fd 無(wú)法傳輸數(shù)據(jù),只用來(lái)傳輸事件,常常用于生產(chǎn)消費(fèi)者模式的事件實(shí)現(xiàn);
timerfd:這是一種定時(shí)器 fd,使用 timerfd_create 創(chuàng)建,到時(shí)間點(diǎn)觸發(fā)可讀事件;
小結(jié)一下:
ext2,ext4,xfs 等這種真正的文件系統(tǒng)的 fd ,無(wú)法使用 epoll 管理;
socket fd,eventfd,timerfd 這些實(shí)現(xiàn)了 poll 調(diào)用的可以放到 epoll 池進(jìn)行管理;
其實(shí),在 Linux 的模塊劃分中,eventfd,timerfd,epoll 池都是文件系統(tǒng)的一種模塊實(shí)現(xiàn)。
思考
前面我們已經(jīng)思考了很多知識(shí)點(diǎn),有一些簡(jiǎn)單有趣的知識(shí)點(diǎn),提示給讀者朋友,這里只拋磚引玉。
問(wèn)題:?jiǎn)魏?CPU 能實(shí)現(xiàn)并行嗎?
不行。
問(wèn)題:?jiǎn)尉€程能實(shí)現(xiàn)高并發(fā)嗎?
可以。
問(wèn)題:那并發(fā)和并行的區(qū)別是?
一個(gè)看的是時(shí)間段內(nèi)的執(zhí)行情況,一個(gè)看的是時(shí)間時(shí)刻的執(zhí)行情況。
問(wèn)題:?jiǎn)尉€程如何做到高并發(fā)?
IO 多路復(fù)用唄,今天講的 epoll 池就是了。
問(wèn)題:?jiǎn)尉€程實(shí)現(xiàn)并發(fā)的有開(kāi)源的例子嗎?
redis,nginx 都是非常好的學(xué)習(xí)例子。當(dāng)然還有我們 Golang 的 runtime 實(shí)現(xiàn)也盡顯高并發(fā)的設(shè)計(jì)思想。
總結(jié)
IO 多路復(fù)用的原始實(shí)現(xiàn)很簡(jiǎn)單,就是一個(gè) 1 對(duì)多的服務(wù)模式,一個(gè) loop 對(duì)應(yīng)處理多個(gè) fd ;
IO 多路復(fù)用想要做到真正的高效,必須要內(nèi)核機(jī)制提供。因?yàn)?IO 的處理和完成是在內(nèi)核,如果內(nèi)核不幫忙,用戶態(tài)的程序根本無(wú)法精確的抓到處理時(shí)機(jī);
fd 記得要設(shè)置成非阻塞的哦,切記;
epoll 池通過(guò)高效的內(nèi)部管理結(jié)構(gòu),并且結(jié)合操作系統(tǒng)提供的 poll 事件注冊(cè)機(jī)制,實(shí)現(xiàn)了高效的 fd 事件管理,為高并發(fā)的 IO 處理提供了前提條件;
epoll 全名 eventpoll,在 Linux 內(nèi)核下以一個(gè)文件系統(tǒng)模塊的形式實(shí)現(xiàn),所以有人常說(shuō) epoll 其實(shí)本身就是文件系統(tǒng)也是對(duì)的;
socketfd,eventfd,timerfd 這三種”文件“fd 實(shí)現(xiàn)了 poll 接口,所以網(wǎng)絡(luò) fd,事件fd,定時(shí)器fd 都可以使用 epoll_ctl 注冊(cè)到池子里。我們最常見(jiàn)的就是網(wǎng)絡(luò)fd的多路復(fù)用;
ext2,ext4,xfs 這種真正意義的文件系統(tǒng)反倒沒(méi)有提供 poll 接口實(shí)現(xiàn),所以不能用 epoll 池來(lái)管理其句柄。那文件就無(wú)法使用 epoll 機(jī)制了嗎?不是的,有一個(gè)庫(kù)叫做 libaio ,通過(guò)這個(gè)庫(kù)我們可以間接的讓文件使用 epoll 通知事件,以后詳說(shuō),此處不表;
后記
epoll 池使用很簡(jiǎn)潔,但實(shí)現(xiàn)不簡(jiǎn)單。還是那句話,Linux 內(nèi)核幫你包圓了。今天并沒(méi)有羅列太多源碼實(shí)現(xiàn),以很小的思考點(diǎn)為題展開(kāi),簡(jiǎn)單講了一些 epoll 的思考。
編輯:jq
-
Linux
+關(guān)注
關(guān)注
87文章
11314瀏覽量
209807
原文標(biāo)題:深入理解 Linux 的 epoll 機(jī)制
文章出處:【微信號(hào):LinuxHub,微信公眾號(hào):Linux愛(ài)好者】歡迎添加關(guān)注!文章轉(zhuǎn)載請(qǐng)注明出處。
發(fā)布評(píng)論請(qǐng)先 登錄
相關(guān)推薦
評(píng)論