(一)IO復用是Linux中的IO模型之一,IO復用就是進程告訴內核需要監視的IO條件,使得內核一旦發現進程指定的一個或多個IO條件就緒,就通過進程處理,從而不會在單個IO上阻塞了,Linux中,提供了select、poll、epoll三種接口來實現IO復用
(二)select:
缺點:
單個進程能夠監視的文件描述符的數量存在最大限制,通常是1024。由於select采用輪詢的方式掃描文件描述符,文件描述符數量越多,性能越差;
內核/用戶空間內存拷貝,select需要大量的句柄數據結構,產生巨大開銷;
select返回的是含有整個句柄的數組,應用程序需要遍歷整個數組才能發現哪些句柄發生事件;
select的觸發方式為水平觸發,應用程序如果沒有完成對一個已經就緒的文件描述符進行IO操作,那么下次select調用還會將這些文件描述符通知進程;
(三)poll:
使用鏈表保存文件描述符,沒有了文件描述符的限制,但其他的三個缺點依然存在
(四)epoll:
上面所說的select缺點都不存在,epoll使用了一個文件描述符管理了多個文件描述符。拿select模型為例,假設我們服務器需要支持100萬個並發鏈接,則在_FD_SETSIZE為1024的情況下,則我們至少需要開辟1K個進程才能實現100萬的並發連接,除了進程上下文切換的時間消耗,從內核到用戶空間的拷貝,數據輪詢,是系統難以承受的,因此,基於select模型的服務器程序,要達到10萬級別的並發訪問,是一個很難完成的任務。
epoll的設計與select完全不同,epoll通過在Linux內核申請一個簡易的文件系統(文件一般使用什么數據結構實現?B+樹),把原先的select/epoll調用分為3個部分:
調用epoll_create()建立了一個epoll對象(在epoll文件系統中為這個句柄對象分配資源)
調用epoll_ctl()向epoll對象添加這100萬個連接的套接字(ip地址+端口號)
調用epoll_wait()收集發生事件的連接
epoll機制實現思路:
當某一進程調用epoll_create()方法時,Linux內核會創建一個eventpoll結構體,這個結構體中有兩個成員與epoll的使用密切相關
struct eventpoll
{
//............
//紅黑樹的根節點,這棵樹的每個節點代表着用戶告訴內核需要監控的文件描述符
struct rb_root rbr;
//調用epoll_wait()函數后滿足條件的文件描述符會被添加到rdlist中,也是后期返回用戶的事件
struct list_head rdlist;
};
每一個epoll對象都有一個獨立的eventpoll結構體,用於存放epoll_ctl()方法向epoll對象中添加進來的事件,這些事件都會存放在這顆紅黑樹中,紅黑樹的增刪查改的效率為logn,其中n為數的高度。
而所有添加到epoll對象中的事件都會與設備(網卡)驅動程序建立回調關系,當相應的事件發生時會調用這個回調方法,這個回調方法在內核中叫做ep_poll_back,它會將發生的事件添加到rdlist雙鏈表中。
在一個epoll對象中,對於每一個事件,都會建立一個epitem結構體如下:
struct epitem
{
struct rb_node rbn;//紅黑樹節點
struct list_head rdllink;//雙向鏈表節點
struct epoll_filefd ffd;//事件句柄信息
struct eventpoll *ep;//指向其所屬的event對象
struct epoll_event event;//期待發生的事件類型
};
當調用epoll_wait()檢查是否有事件發生時,只需要檢查eventpoll對象中的rdlist雙鏈表中是否有epitem元素即可,如果rdlist不為空,則把發生的事件復制到用戶態,同時將事件數量返回給用戶。
從上面的講解可知:通過紅黑樹和雙鏈表數據結構並結合回調機制,才有如此高效的epoll
epoll在使用ET模式的時候一定是非阻塞的,如果是阻塞的話,一個任務監控多個文件描述符時,當一個描述符阻塞就會使其他的描述符所在的任務失敗(餓死)
————————————————
版權聲明:本文為CSDN博主「_kean」的原創文章,遵循CC 4.0 BY-SA版權協議,轉載請附上原文出處鏈接及本聲明。
原文鏈接:https://blog.csdn.net/C1029323236/java/article/details/90551959