0、引用?
http://www.cnblogs.com/Anker/p/3265058.html
http://janfan.github.io/chinese/2015/01/05/select-poll-impl-inside-the-kernel.html
http://blog.csdn.net/lizhiguo0532/article/details/6568957
http://blog.csdn.net/shuxiaogd/article/details/50366039
https://segmentfault.com/a/1190000003063859
1、select 函數(shù)
#define __FD_SETSIZE? ? 1024
typedef struct {
unsigned long fds_bits[__FD_SETSIZE / (8 * sizeof(long))];
} __kernel_fd_set;
typedef __kernel_fd_set? ? fd_set;
int select(int nfds, fd_set *readfds, fd_set *writefds, fd_set *exceptfds, struct timeval *timeout);
void FD_CLR(int fd, fd_set *set);
int? FD_ISSET(int fd, fd_set *set);
void FD_SET(int fd, fd_set *set);
void FD_ZERO(fd_set *set);
2、select執(zhí)行流程
1、把fd全部掃描一遍
2、如果發(fā)現(xiàn)有可用的fd,跳轉到5
3、如果沒有,當前進程去睡眠xx秒
4、xx秒后進程自己醒了或者fd狀態(tài)的改變喚醒了進程,跳轉到步驟1
5、結束循環(huán)體,返回
3、select 的缺點
(1)每次調用select,都需要把fd集合從用戶態(tài)拷貝到內核態(tài),當fd很多時這個開銷會很大
(2)同時每次調用select都需要在內核遍歷傳遞進來的所有fd,當fd很多時這個開銷會很大
(3)select支持的文件描述符數(shù)量太小了,默認是1024
4、poll 的實現(xiàn)
poll的實現(xiàn)和select非常相似,只是描述fd集合的方式不同,poll使用pollfd結構而不是select的fd_set結構,其他的都差不多。
5、epoll的實現(xiàn)
int epoll_create(int size);
int epoll_ctl(int epfd, int op, int fd, struct epoll_event *event);
int epoll_wait(int epfd, struct epoll_event * events, int maxevents, int timeout);
epoll是在2.6內核中提出的,是之前的select和poll的增強版本。相對于select和poll來說,epoll更加靈活,沒有描述符限制。epoll使用一個文件描述符管理多個描述符,將用戶關系的文件描述符的事件存放到內核的一個事件表中,這樣在用戶空間和內核空間的copy只需一次。
5.1. int epoll_create(int size);
創(chuàng)建一個epoll的句柄,size用來告訴內核這個監(jiān)聽的數(shù)目一共有多大,這個參數(shù)不同于select()中的第一個參數(shù),給出最大監(jiān)聽的fd+1的值,參數(shù)size并不是限制了epoll所能監(jiān)聽的描述符最大個數(shù),只是對內核初始分配內部數(shù)據(jù)結構的一個建議。當創(chuàng)建好epoll句柄后,它就會占用一個fd值,在linux下如果查看/proc/進程id/fd/,是能夠看到這個fd的,所以在使用完epoll后,必須調用close()關閉,否則可能導致fd被耗盡。
5.2. int epoll_ctl(int epfd, int op, int fd, struct epoll_event *event);
函數(shù)是對指定描述符fd執(zhí)行op操作。
- epfd:是epoll_create()的返回值。
- op:表示op操作,用三個宏來表示:添加EPOLL_CTL_ADD,刪除EPOLL_CTL_DEL,修改EPOLL_CTL_MOD。分別添加、刪除和修改對fd的監(jiān)聽事件。
- fd:是需要監(jiān)聽的fd(文件描述符)
- epoll_event:是告訴內核需要監(jiān)聽什么事,struct epoll_event結構如下:
struct epoll_event {
__uint32_t events;? /* Epoll events */
epoll_data_t data;? /* User data variable */
};
//events可以是以下幾個宏的集合:
EPOLLIN :表示對應的文件描述符可以讀(包括對端SOCKET正常關閉);
EPOLLOUT:表示對應的文件描述符可以寫;
EPOLLPRI:表示對應的文件描述符有緊急的數(shù)據(jù)可讀(這里應該表示有帶外數(shù)據(jù)到來);
EPOLLERR:表示對應的文件描述符發(fā)生錯誤;
EPOLLHUP:表示對應的文件描述符被掛斷;
EPOLLET: 將EPOLL設為邊緣觸發(fā)(Edge Triggered)模式,這是相對于水平觸發(fā)(Level Triggered)來說的。
EPOLLONESHOT:只監(jiān)聽一次事件,當監(jiān)聽完這次事件之后,如果還需要繼續(xù)監(jiān)聽這個socket的話,需要再次把這個socket加入到EPOLL隊列里
5.3. int epoll_wait(int epfd, struct epoll_event * events, int maxevents, int timeout);
等待epfd上的io事件,最多返回maxevents個事件。
參數(shù)events用來從內核得到事件的集合,maxevents告之內核這個events有多大,這個maxevents的值不能大于創(chuàng)建epoll_create()時的size,參數(shù)timeout是超時時間(毫秒,0會立即返回,-1將不確定,也有說法說是永久阻塞)。該函數(shù)返回需要處理的事件數(shù)目,如返回0表示已超時。
5.4 epoll工作模式
epoll對文件描述符的操作有兩種模式:LT(level trigger)和ET(edge trigger)。LT模式是默認模式,LT模式與ET模式的區(qū)別如下:
LT模式:當epoll_wait檢測到描述符事件發(fā)生并將此事件通知應用程序,應用程序可以不立即處理該事件。下次調用epoll_wait時,會再次響應應用程序并通知此事件。
ET模式:當epoll_wait檢測到描述符事件發(fā)生并將此事件通知應用程序,應用程序必須立即處理該事件。如果不處理,下次調用epoll_wait時,不會再次響應應用程序并通知此事件。
1. LT模式
LT(level triggered)是缺省的工作方式,并且同時支持block和no-block socket.在這種做法中,內核告訴你一個文件描述符是否就緒了,然后你可以對這個就緒的fd進行IO操作。如果你不作任何操作,內核還是會繼續(xù)通知你的。
2. ET模式
ET(edge-triggered)是高速工作方式,只支持no-block socket。在這種模式下,當描述符從未就緒變?yōu)榫途w時,內核通過epoll告訴你。然后它會假設你知道文件描述符已經(jīng)就緒,并且不會再為那個文件描述符發(fā)送更多的就緒通知,直到你做了某些操作導致那個文件描述符不再為就緒狀態(tài)了(比如,你在發(fā)送,接收或者接收請求,或者發(fā)送接收的數(shù)據(jù)少于一定量時導致了一個EWOULDBLOCK 錯誤)。但是請注意,如果一直不對這個fd作IO操作(從而導致它再次變成未就緒),內核不會發(fā)送更多的通知(only once)
ET模式在很大程度上減少了epoll事件被重復觸發(fā)的次數(shù),因此效率要比LT模式高。epoll工作在ET模式的時候,必須使用非阻塞套接口,以避免由于一個文件句柄的阻塞讀/阻塞寫操作把處理多個文件描述符的任務餓死。
6、select /epoll?
在 select/poll中,進程只有在調用一定的方法后,內核才對所有監(jiān)視的文件描述符進行掃描,而epoll事先通過epoll_ctl()來注冊一 個文件描述符,一旦基于某個文件描述符就緒時,內核會采用類似callback的回調機制,迅速激活這個文件描述符,當進程調用epoll_wait() 時便得到通知。(此處去掉了遍歷文件描述符,而是通過監(jiān)聽回調的的機制。這正是epoll的魅力所在。)
epoll的優(yōu)點主要是一下幾個方面:
1. 監(jiān)視的描述符數(shù)量不受限制,它所支持的FD上限是最大可以打開文件的數(shù)目,這個數(shù)字一般遠大于2048,舉個例子,在1GB內存的機器上大約是10萬左 右,具體數(shù)目可以cat /proc/sys/fs/file-max察看,一般來說這個數(shù)目和系統(tǒng)內存關系很大。select的最大缺點就是進程打開的fd是有數(shù)量限制的。這對 于連接數(shù)量比較大的服務器來說根本不能滿足。雖然也可以選擇多進程的解決方案( Apache就是這樣實現(xiàn)的),不過雖然linux上面創(chuàng)建進程的代價比較小,但仍舊是不可忽視的,加上進程間數(shù)據(jù)同步遠比不上線程間同步的高效,所以也不是一種完美的方案。IO的效率不會隨著監(jiān)視fd的數(shù)量的增長而下降。epoll不同于select和poll輪詢的方式,而是通過每個fd定義的回調函數(shù)來實現(xiàn)的。只有就緒的fd才會執(zhí)行回調函數(shù)。
水平觸發(fā),只要可讀或可寫,事件會一直觸發(fā)
邊緣觸發(fā),只有從不可讀變?yōu)榭勺x、從不可寫變成可寫,事件才會觸發(fā)
基于此,水平觸發(fā),socket不需要設置為non-block,因為只要觸發(fā),就可以讀或寫。
而對于水平觸發(fā),事件一旦觸發(fā),需要用戶一直讀,直到緩沖區(qū)數(shù)據(jù)全部讀完為止才可以,如果設置為block,在沒有數(shù)據(jù)可讀時,就block了,需要用eagain錯誤來告知用戶,數(shù)據(jù)讀完了,不用再讀了。
ET 模式是一種邊沿觸發(fā)模型,在它檢測到有 I/O 事件時,通過 epoll_wait 調用會得到有事件通知的文件描述符,每于每一個被通知的文件描述符,如可讀,則必須將該文件描述符一直讀到空,讓 errno 返回 EAGAIN 為止,否則下次的 epoll_wait 不會返