linux的poll
❶ linux 下 poll 函數的調用返回結果為什麼不準確
fds[0].fd 與 fds[1].fd 同樣是sock這個描述符, 其中在fds[0] 中注冊POLLIN和POLLRDNORM事件,
在fds[1]中監聽POLLOUT和POLLWRNORM事件, 這樣做就成功了, 不會出現上述 "同時在一個sock描述符注冊可讀可寫事件,
導致監聽結果與實際不符"的現象。
❷ Linux內核中select,poll和epoll的區別
在Linux Socket伺服器短編程時,為了處理大量客戶的連接請求,需要使用非阻塞I/O和復用,select、poll
和epoll是Linux API提供的I/O復用方式,自從Linux 2.6中加入了epoll之後,在高性能伺服器領域得到廣泛的
應用,現在比較出名的nginx就是使用epoll來實現I/O復用支持高並發,目前在高並 發的場景下,nginx越來越
收到歡迎。
select:
下面是select的函數介面:
[cpp] view plain
int select (int n, fd_set *readfds, fd_set *writefds, fd_set *exceptfds, struct timeval *timeout);
select 函數監視的文件描述符分3類,分別是writefds、readfds、和exceptfds。調用後select函數會阻塞,直
到有描述副就緒(有數據 可讀、可寫、或者有except),或者超時(timeout指定等待時間,如果立即返回設為
null即可),函數返回。當select函數返回後,可以 通過遍歷fdset,來找到就緒的描述符。
select目前幾乎在所有的平台上支持,其良好跨平台支持也是它的一個優點。select的一 個缺點在於單個進程
能夠監視的文件描述符的數量存在最大限制,在Linux上一般為1024,可以通過修改宏定義甚至重新編譯內核的
方式提升這一限制,但 是這樣也會造成效率的降低。
poll:
[cpp] view plain
int poll (struct pollfd *fds, unsigned int nfds, int timeout);
不同與select使用三個點陣圖來表示三個fdset的方式,poll使用一個 pollfd的指針實現。
[cpp] view plain
struct pollfd {
int fd; /* file descriptor */
short events; /* requested events to watch */
short revents; /* returned events witnessed */
};
pollfd結構包含了要監視的event和發生的event,不再使用select「參數-值」傳遞的方式。同時,pollfd並沒有
最大數量限制(但是數量過大後性能也是會下降)。 和select函數一樣,poll返回後,需要輪詢pollfd來獲取
就緒的描述符。
從上面看,select和poll都需要在返回後,通過遍歷文件描述符來獲取已經就緒的socket。事實上,同時連接的
大量客戶端在一時刻可能只有很少的處於就緒狀態,因此隨著監視的描述符數量的增長,其效率也會線性下降。
epoll:
epoll的介面如下:
[cpp] view plain
int epoll_create(int size);
int epoll_ctl(int epfd, int op, int fd, struct epoll_event *event);
typedef union epoll_data {
void *ptr;
int fd;
__uint32_t u32;
__uint64_t u64;
} epoll_data_t;
struct epoll_event {
__uint32_t events; /* Epoll events */
epoll_data_t data; /* User data variable */
};
int epoll_wait(int epfd, struct epoll_event * events, int maxevents, int timeout);
主要是epoll_create,epoll_ctl和epoll_wait三個函數。epoll_create函數創建epoll文件描述符,參數size並
'不是限制了epoll所能監聽的描述符最大個數,只是對內核初始分配內部數據結構的一個建議。返回是epoll描
述符。-1表示創建失敗。epoll_ctl 控制對指定描述符fd執行op操作,event是與fd關聯的監聽事件。op操作
有三種:添加EPOLL_CTL_ADD,刪除EPOLL_CTL_DEL,修改EPOLL_CTL_MOD。分別添加、刪除和
修改對fd的監聽事件。epoll_wait 等待epfd上的io事件,最多返回maxevents個事件。
在 select/poll中,進程只有在調用一定的方法後,內核才對所有監視的文件描述符進行掃描,而epoll事先通
過epoll_ctl()來注冊一 個文件描述符,一旦基於某個文件描述符就緒時,內核會採用類似callback的回調機制,
迅速激活這個文件描述符,當進程調用epoll_wait() 時便得到通知。
epoll的優點主要是一下幾個方面:
1. 監視的描述符數量不受限制,它所支持的FD上限是最大可以打開文件的數目,這個數字一般遠大於2048,
舉個例子,在1GB內存的機器上大約是10萬左 右,具體數目可以cat /proc/sys/fs/file-max察看,一般來說這個
數目和系統內存關系很大。select的最大缺點就是進程打開的fd是有數量限制的。這對 於連接數量比較大的
伺服器來說根本不能滿足。雖然也可以選擇多進程的解決方案( Apache就是這樣實現的),不過雖然linux上面
創建進程的代價比較小,但仍舊是不可忽視的,加上進程間數據同步遠比不上線程間同步的高效,所以也 不是
一種完美的方案。
2. IO的效率不會隨著監視fd的數量的增長而下降。epoll不同於select和poll輪詢的方式,而是通過每個fd定義的
回調函數來實現的。只有就緒的fd才會執行回調函數。
3.支持電平觸發和邊沿觸發(只告訴進程哪些文件描述符剛剛變為就緒狀態,它只說一遍,如果我們沒有採取
行動,那麼它將不會再次告知,這種方式稱為邊緣觸發)兩種方式,理論上邊緣觸發的性能要更高一些,但是
代碼實現相當復雜。
4.mmap加速內核與用戶空間的信息傳遞。epoll是通過內核於用戶空間mmap同一塊內存,避免了無畏的內存拷貝。
❸ Linux select/poll/epoll 原理(一)實現基礎
本序列涉及的 Linux 源碼都是基於 linux-4.14.143 。
1.1 文件抽象
在 Linux 內核里,文件是一個抽象,設備是個文件,網路套接字也是個文件。
文件抽象必須支持的能力定義在 file_operations 結構體里。
在 Linux 里,一個打開的文件對應一個文件描述符 file descriptor/FD,FD 其實是一個整數,內核把進程打開的文件維護在一個數組里,FD 對應的是數組的下標。
文件抽象的能力定義:
1.2 文件 poll 操作
poll 函數的原型:
文件抽象 poll 函數的具體實現必須完成兩件事(這兩點算是規范了):
1. 在 poll 函數敢興趣的等待隊列上調用 poll_wait 函數,以接收到喚醒;具體的實現必須把 poll_table 類型的參數作為透明對象來使用,不需要知道它的具體結構。
2. 返回比特掩碼,表示當前可立即執行而不會阻塞的操作。
下面是某個驅動的 poll 實現示例,來自:https://www.oreilly.com/library/view/linux-device-drivers/0596000081/ch05s03.html:
poll 函數接收的 poll_table 只有一個隊列處理函數 _qproc 和感興趣的事件屬性 _key。
文件抽象的具體實現在構建時會初始化一個或多個 wait_queue_head_t 類型的事件等待隊列 。
poll 等待的過程:
事件發生時的喚醒過程:
一個小困惑:
❹ Linux的poll機制是什麼,誰能用通俗易懂的話給我講講,感激不盡
你可以理解為一種比select更底層的用於等待多個文件描述符的機制。
如果你連select都不知道是啥,還是看看基礎吧
又一次看到那傢伙給了個不貼邊的回答
❺ I/O--多路復用的三種機制Select,Poll和Epoll對比
select、poll 和 epoll 都是 Linux API 提供的 IO 復用方式。
多進程和多線程技術相比,I/O多路復用技術的最大優勢是系統開銷小,系統不必創建進程/線程,也不必維護這些進程/線程,從而大大減小了系統的開銷。
我們先分析一下select函數
int select(int maxfdp1,fd_set *readset,fd_set *writeset,fd_set *exceptset,const struct timeval *timeout);
【參數說明】
int maxfdp1 指定待測試的文件描述字個數,它的值是待測試的最大描述字加1。
fd_set *readset , fd_set *writeset , fd_set *exceptset
fd_set可以理解為一個集合,這個集合中存放的是文件描述符(file descriptor),即文件句柄。中間的三個參數指定我們要讓內核測試讀、寫和異常條件的文件描述符集合。如果對某一個的條件不感興趣,就可以把它設為空指針。
const struct timeval *timeout timeout告知內核等待所指定文件描述符集合中的任何一個就緒可花多少時間。其timeval結構用於指定這段時間的秒數和微秒數。
【返回值】
int 若有就緒描述符返回其數目,若超時則為0,若出錯則為-1
select()的機制中提供一種fd_set的數據結構,實際上是一個long類型的數組,每一個數組元素都能與一打開的文件句柄(不管是Socket句柄,還是其他文件或命名管道或設備句柄)建立聯系,建立聯系的工作由程序員完成,當調用select()時,由內核根據IO狀態修改fd_set的內容,由此來通知執行了select()的進程哪一Socket或文件可讀。
從流程上來看,使用select函數進行IO請求和同步阻塞模型沒有太大的區別,甚至還多了添加監視socket,以及調用select函數的額外操作,效率更差。但是,使用select以後最大的優勢是用戶可以在一個線程內同時處理多個socket的IO請求。用戶可以注冊多個socket,然後不斷地調用select讀取被激活的socket,即可達到在同一個線程內同時處理多個IO請求的目的。而在同步阻塞模型中,必須通過多線程的方式才能達到這個目的。
poll的機制與select類似,與select在本質上沒有多大差別,管理多個描述符也是進行輪詢,根據描述符的狀態進行處理,但是poll沒有最大文件描述符數量的限制。也就是說,poll只解決了上面的問題3,並沒有解決問題1,2的性能開銷問題。
下面是pll的函數原型:
poll改變了文件描述符集合的描述方式,使用了pollfd結構而不是select的fd_set結構,使得poll支持的文件描述符集合限制遠大於select的1024
【參數說明】
struct pollfd *fds fds是一個struct pollfd類型的數組,用於存放需要檢測其狀態的socket描述符,並且調用poll函數之後fds數組不會被清空;一個pollfd結構體表示一個被監視的文件描述符,通過傳遞fds指示 poll() 監視多個文件描述符。其中,結構體的events域是監視該文件描述符的事件掩碼,由用戶來設置這個域,結構體的revents域是文件描述符的操作結果事件掩碼,內核在調用返回時設置這個域
nfds_t nfds 記錄數組fds中描述符的總數量
【返回值】
int 函數返回fds集合中就緒的讀、寫,或出錯的描述符數量,返回0表示超時,返回-1表示出錯;
epoll在Linux2.6內核正式提出,是基於事件驅動的I/O方式,相對於select來說,epoll沒有描述符個數限制,使用一個文件描述符管理多個描述符,將用戶關心的文件描述符的事件存放到內核的一個事件表中,這樣在用戶空間和內核空間的只需一次。
Linux中提供的epoll相關函數如下:
1. epoll_create 函數創建一個epoll句柄,參數size表明內核要監聽的描述符數量。調用成功時返回一個epoll句柄描述符,失敗時返回-1。
2. epoll_ctl 函數注冊要監聽的事件類型。四個參數解釋如下:
epoll_event 結構體定義如下:
3. epoll_wait 函數等待事件的就緒,成功時返回就緒的事件數目,調用失敗時返回 -1,等待超時返回 0。
epoll是Linux內核為處理大批量文件描述符而作了改進的poll,是Linux下多路復用IO介面select/poll的增強版本,它能顯著提高程序在大量並發連接中只有少量活躍的情況下的系統CPU利用率。原因就是獲取事件的時候,它無須遍歷整個被偵聽的描述符集,只要遍歷那些被內核IO事件非同步喚醒而加入Ready隊列的描述符集合就行了。
epoll除了提供select/poll那種IO事件的水平觸發(Level Triggered)外,還提供了邊緣觸發(Edge Triggered),這就使得用戶空間程序有可能緩存IO狀態,減少epoll_wait/epoll_pwait的調用,提高應用程序效率。
LT和ET原本應該是用於脈沖信號的,可能用它來解釋更加形象。Level和Edge指的就是觸發點,Level為只要處於水平,那麼就一直觸發,而Edge則為上升沿和下降沿的時候觸發。比如:0->1 就是Edge,1->1 就是Level。
ET模式很大程度上減少了epoll事件的觸發次數,因此效率比LT模式下高。
一張圖總結一下select,poll,epoll的區別:
epoll是Linux目前大規模網路並發程序開發的首選模型。在絕大多數情況下性能遠超select和poll。目前流行的高性能web伺服器Nginx正式依賴於epoll提供的高效網路套接字輪詢服務。但是,在並發連接不高的情況下,多線程+阻塞I/O方式可能性能更好。
既然select,poll,epoll都是I/O多路復用的具體的實現,之所以現在同時存在,其實他們也是不同 歷史 時期的產物
❻ Linux系統I/O模型及select、poll、epoll原理和應用
理解Linux的IO模型之前,首先要了解一些基本概念,才能理解這些IO模型設計的依據
操作系統使用虛擬內存來映射物理內存,對於32位的操作系統來說,虛擬地址空間為4G(2^32)。操作系統的核心是內核,為了保護用戶進程不能直接操作內核,保證內核安全,操作系統將虛擬地址空間劃分為內核空間和用戶空間。內核可以訪問全部的地址空間,擁有訪問底層硬體設備的許可權,普通的應用程序需要訪問硬體設備必須通過 系統調用 來實現。
對於Linux系統來說,將虛擬內存的最高1G位元組的空間作為內核空間僅供內核使用,低3G位元組的空間供用戶進程使用,稱為用戶空間。
又被稱為標准I/O,大多數文件系統的默認I/O都是緩存I/O。在Linux系統的緩存I/O機制中,操作系統會將I/O的數據緩存在頁緩存(內存)中,也就是數據先被拷貝到內核的緩沖區(內核地址空間),然後才會從內核緩沖區拷貝到應用程序的緩沖區(用戶地址空間)。
這種方式很明顯的缺點就是數據傳輸過程中需要再應用程序地址空間和內核空間進行多次數據拷貝操作,這些操作帶來的CPU以及內存的開銷是非常大的。
由於Linux系統採用的緩存I/O模式,對於一次I/O訪問,以讀操作舉例,數據先會被拷貝到內核緩沖區,然後才會從內核緩沖區拷貝到應用程序的緩存區,當一個read系統調用發生的時候,會經歷兩個階段:
正是因為這兩個狀態,Linux系統才產生了多種不同的網路I/O模式的方案
Linux系統默認情況下所有socke都是blocking的,一個讀操作流程如下:
以UDP socket為例,當用戶進程調用了recvfrom系統調用,如果數據還沒准備好,應用進程被阻塞,內核直到數據到來且將數據從內核緩沖區拷貝到了應用進程緩沖區,然後向用戶進程返回結果,用戶進程才解除block狀態,重新運行起來。
阻塞模行下只是阻塞了當前的應用進程,其他進程還可以執行,不消耗CPU時間,CPU的利用率較高。
Linux可以設置socket為非阻塞的,非阻塞模式下執行一個讀操作流程如下:
當用戶進程發出recvfrom系統調用時,如果kernel中的數據還沒准備好,recvfrom會立即返回一個error結果,不會阻塞用戶進程,用戶進程收到error時知道數據還沒准備好,過一會再調用recvfrom,直到kernel中的數據准備好了,內核就立即將數據拷貝到用戶內存然後返回ok,這個過程需要用戶進程去輪詢內核數據是否准備好。
非阻塞模型下由於要處理更多的系統調用,因此CPU利用率比較低。
應用進程使用sigaction系統調用,內核立即返回,等到kernel數據准備好時會給用戶進程發送一個信號,告訴用戶進程可以進行IO操作了,然後用戶進程再調用IO系統調用如recvfrom,將數據從內核緩沖區拷貝到應用進程。流程如下:
相比於輪詢的方式,不需要多次系統調用輪詢,信號驅動IO的CPU利用率更高。
非同步IO模型與其他模型最大的區別是,非同步IO在系統調用返回的時候所有操作都已經完成,應用進程既不需要等待數據准備,也不需要在數據到來後等待數據從內核緩沖區拷貝到用戶緩沖區,流程如下:
在數據拷貝完成後,kernel會給用戶進程發送一個信號告訴其read操作完成了。
是用select、poll等待數據,可以等待多個socket中的任一個變為可讀,這一過程會被阻塞,當某個套接字數據到來時返回,之後再用recvfrom系統調用把數據從內核緩存區復制到用戶進程,流程如下:
流程類似阻塞IO,甚至比阻塞IO更差,多使用了一個系統調用,但是IO多路復用最大的特點是讓單個進程能同時處理多個IO事件的能力,又被稱為事件驅動IO,相比於多線程模型,IO復用模型不需要線程的創建、切換、銷毀,系統開銷更小,適合高並發的場景。
select是IO多路復用模型的一種實現,當select函數返回後可以通過輪詢fdset來找到就緒的socket。
優點是幾乎所有平台都支持,缺點在於能夠監聽的fd數量有限,Linux系統上一般為1024,是寫死在宏定義中的,要修改需要重新編譯內核。而且每次都要把所有的fd在用戶空間和內核空間拷貝,這個操作是比較耗時的。
poll和select基本相同,不同的是poll沒有最大fd數量限制(實際也會受到物理資源的限制,因為系統的fd數量是有限的),而且提供了更多的時間類型。
總結:select和poll都需要在返回後通過輪詢的方式檢查就緒的socket,事實上同時連的大量socket在一個時刻只有很少的處於就緒狀態,因此隨著監視的描述符數量的變多,其性能也會逐漸下降。
epoll是select和poll的改進版本,更加靈活,沒有描述符限制。epoll使用一個文件描述符管理多個描述符,將用戶關系的文件描述符的事件存放到內核的一個事件表中,這樣在用戶空間和內核空間的只需一次。
epoll_create()用來創建一個epoll句柄。
epoll_ctl() 用於向內核注冊新的描述符或者是改變某個文件描述符的狀態。已注冊的描述符在內核中會被維護在一棵紅黑樹上,通過回調函數內核會將 I/O 准備好的描述符加入到一個就緒鏈表中管理。
epoll_wait() 可以從就緒鏈表中得到事件完成的描述符,因此進程不需要通過輪詢來獲得事件完成的描述符。
當epoll_wait檢測到描述符IO事件發生並且通知給應用程序時,應用程序可以不立即處理該事件,下次調用epoll_wait還會再次通知該事件,支持block和nonblocking socket。
當epoll_wait檢測到描述符IO事件發生並且通知給應用程序時,應用程序需要立即處理該事件,如果不立即處理,下次調用epoll_wait不會再次通知該事件。
ET模式在很大程度上減少了epoll事件被重復觸發的次數,因此效率要比LT模式高。epoll工作在ET模式的時候,必須使用nonblocking socket,以避免由於一個文件句柄的阻塞讀/阻塞寫操作把處理多個文件描述符的任務餓死。
【segmentfault】 Linux IO模式及 select、poll、epoll詳解
【GitHub】 CyC2018/CS-Notes
❼ poll基於什麼存儲
poll是基於鏈表存儲。
poll機制監測的文件句柄數沒有限制,不同於select(一般監測數量1024,可以通過cat /proc/sys/fs/file_max查看),poll是基於鏈表存儲的。poll是Linux中的字元設備驅動中的一個函數。Linux 2.5.44版本後,poll被epoll取代。和select實現的功能差不多,poll的作用是把當前的文件指針掛到等待隊列。
poll本質上和select沒有區別,它將用戶傳入的數組拷貝到內核空間,然後查詢每個fd對應的設備狀態,如果設備就緒則在設備等待隊列中加入一項並繼續遍歷,如果遍歷完所有的fd後沒有發現就緒設備,則掛起當前進程,直到設備就緒或者主動超時,被喚醒後它又要再次遍歷fd。這個過程經歷了多次無謂的遍歷。
poll是基於鏈表來存儲的,所以它沒有最大連接數的限制,但同樣有一個缺點:
(1)、大量的fd的數組被整體復制於用戶態和內核地址空間之間,而不管這樣的復制是不是有意義。
(2)、poll還有一個特點是「水平觸發」,如果報告了fd後,沒有被處理,那麼下次poll時會再次報告該fd。