IO 多路復(fù)用 select vs pool vs epool
前言
- 在類 unix linux 系統(tǒng)中每一個(gè)進(jìn)程都存在一個(gè)文件描述符表,表指向具體的文件,比如 socket, 設(shè)備(devices), 和其他操作系統(tǒng)對(duì)象。
IO多路復(fù)用(IO Multiplexing )出現(xiàn)的背景
- 多個(gè)io資源協(xié)同工作的系統(tǒng),具有典型的兩個(gè)階段,初始化階段,然后進(jìn)入等待模式,等待客戶端發(fā)起請(qǐng)求并對(duì)其進(jìn)行相應(yīng)。
- 簡(jiǎn)單的實(shí)現(xiàn)是對(duì)每一個(gè)客戶端socket 請(qǐng)求創(chuàng)建一個(gè)thread 線程,在read 的時(shí)候block 阻塞,直至一個(gè)請(qǐng)求被發(fā)送并且接受到一個(gè)寫入(write)的響應(yīng)。
- 這種工作模式在客戶端client 比較少的時(shí)候是ok 的, 但是如果在大規(guī)模的客戶端請(qǐng)求調(diào)度中,為每個(gè)client 都創(chuàng)建一個(gè)線程是非常不好的。
io 多路復(fù)用應(yīng)運(yùn)而生。
IO Multiplexing 模式的支持
實(shí)現(xiàn)思想是: 采用內(nèi)核機(jī)制來(lái)輪詢一組文件描述符,在linux 中有以下三種支持。
它們的實(shí)現(xiàn)思路一樣,都是創(chuàng)建一組標(biāo)志讀(read) 寫(write)的文件描述符,并告知內(nèi)核,并用一個(gè)線程阻塞一個(gè)函數(shù)調(diào)用,直到一個(gè)文件描述符的操作(read/write)可用。
- select()
- pool()
- epool()
select() 系統(tǒng)調(diào)用
select () 指令
int select(int nfds, fd_set *readfds, fd_set *writefds, fd_set *exceptfds, struct timeval *timeout);
對(duì)一個(gè)select 指令的調(diào)用會(huì)被阻塞,直到給定的文件描述符執(zhí)行IO 操作,或則在指定的過(guò)期時(shí)間內(nèi)失效,被監(jiān)視的文件描述符分為三組。
- readfds: 監(jiān)視 readfds集中列出的文件描述符,以查看是否有數(shù)據(jù)可讀取。
- writefds: 監(jiān)視 writefds 集中列出的文件描述符,已查看是否寫入操作完成且沒有發(fā)生阻塞。
- exceptfds : 監(jiān)視是否出現(xiàn)異常,或則帶外數(shù)據(jù)(OOB)可用。
select 沒有監(jiān)察到上述事件就會(huì)返回null。成功返回則修改每個(gè)集合。使其僅包含已經(jīng)準(zhǔn)備就緒的文件描述符。
因?yàn)樾枰嬷?select() 最大的文件描述符編號(hào),這是fd_sets 的內(nèi)部實(shí)現(xiàn)。
【在fd_set 中 一個(gè)fd(file description) 文件描述占用 1bit, fd_set 是length 為32 的整數(shù)數(shù)組,(32 x 4byte x 8bit = 1024bit) ,假設(shè)存在 8個(gè)文件描述符,最大的文件描述符值為1000, 那么將會(huì)在0 ~ 1000 中找到監(jiān)視的文件描述符】
這也是一個(gè)弊端,因?yàn)樾枰诿看屋喸兊鷷r(shí)重新構(gòu)建文件描述符集。
select () 總結(jié)
- 在每次調(diào)用前需要重新構(gòu)建每個(gè)fd_set 集合。
- 函數(shù)調(diào)用監(jiān)視文件描述符最大值N 范圍內(nèi)的任何位,時(shí)間復(fù)雜度為 O(N)。
- 需要輪詢遍歷文件描述符集,檢查是否存在于從 select() 返回的集合中。
- select 最大的優(yōu)勢(shì)是輕便,可移植,任何unix 系統(tǒng)都支持。
pool() 系統(tǒng)調(diào)用
int poll (struct pollfd *fds, unsigned int nfds, int timeout);
與select () 采用3個(gè) 基于標(biāo)記位的文件描述符集不同,pool 采用一個(gè)pollfd 結(jié)構(gòu),原型更簡(jiǎn)單。
struct pollfd {
int fd;
short events;
short revents;
};
為每一個(gè)文件描述符構(gòu)建一個(gè) pollfd 類型的兌對(duì)象,并填充請(qǐng)求(request)事件,然后輪詢返回響應(yīng),檢查返回事件(revents)字段。
與selelct() 類似,需要檢測(cè)每一個(gè)pollfd 對(duì)象去看它的文件描述符是否已準(zhǔn)備好,,但是不需要在每次輪詢時(shí)重新構(gòu)建文件描述符集。
epool() 系統(tǒng)調(diào)用
pool() ,select() 工作時(shí),我們需要在用戶空間管理任何事情,在每次調(diào)用發(fā)送 文件描述符集時(shí)陷入阻塞等待。添加另外的socket , 我們需要把它加入到 集合中并再一次調(diào)用 pool() select()。
epool() 幫助我們?cè)趦?nèi)核中創(chuàng)建和管理上下文,可以分為以下三步:
- 調(diào)用 epool_create 在內(nèi)核中創(chuàng)建上下文。
- 調(diào)用epool_ctl 在上線文中添加或刪除文件描述符。
- 采用epool_wait 等待上下文中的事件。
pool() vs select()
- pool() 不需要用戶去計(jì)算文件描述符最大數(shù)+1。
- pool() 對(duì)于很大數(shù)值的文件描述符更有效率。
- select() 的文件描述符集是固定的大小。
- 采用select() 系統(tǒng)調(diào)用,文件描述符集在返回后被重新構(gòu)造,因此接下來(lái)的調(diào)用必須重新實(shí)例化,pool() 將輸入 (event 字段)和 輸出 (output )分開,從而允許文件描述符集可以重用而無(wú)需改動(dòng)。
- select() 更輕便, 一些unix 系統(tǒng)不支持 pool()。
epool vs pool vs select
- 在等待io 響應(yīng)的時(shí)候可以添加或刪除文件描述符。
- epool_wait 僅返回具有已經(jīng)準(zhǔn)備的文件描述符的對(duì)象。
- epool 擁有更高的性能,時(shí)間復(fù)雜度為 O(1)。
- epool 可以表現(xiàn)為水平觸發(fā),和邊緣觸發(fā)。
- epool 是linux 特有的,不具有很好的可移植性。