天天看點

一種了解同步/異步,阻塞/非阻塞,Linux IO 模型,select /poll /epoll 的方法

強迫症不能忍受這種極其繞的概念而不給個說法。同步(synchronous)/異步(asynchronous),阻塞(blocking)/非阻塞(non-blocking),阻塞IO/非阻塞IO/同步IO/異步IO/IO複用(IO Multiplexing),select/poll/epoll這些概念困擾我許久,下面給出這一階段我個人的了解。

線程是程式執行中一個單一的順序控制流程,是程式執行流的最小單元,是處理器排程和分派的基本機關。用線程執行程式流的過程去了解同步異步,阻塞非阻塞。同步異步關注的是流執行過程需不需要等待外部調用的結果,而阻塞非阻塞關注的是外部調用對流本身産生的影響。

線程的執行過程中,産生一個外部調用,如果需要等待該調用傳回才能繼續線程流則叫做同步,不需要等待結果傳回線程流可以繼續往下執行的情況叫做異步。

區分:線程流向下執行需不需要等待系統調用的結果。

線程執行過程中,産生一個外部調用後,會不會把該線程流“堵”住,會“堵”對應的是阻塞,反之為非阻塞。

上一節中對同步/異步,阻塞/非阻塞的描述隻能說能夠恰好區分它們,如果不是在計算機領域而是生活中,道理也類似。然而計算機中的某些專業術語又需要放在專門的情景中去看,例如下面将要提到的Linux IO模型,建議了解模型本身,而不是摳同步/異步與阻塞非阻塞的字眼,因為會發現就算是非阻塞模型也有阻塞的部分,同步IO與異步IO的差別是IO操作的時候會不會讓process阻塞。

下面對模型描述的最終來源為:

Richard Stevens的“UNIX:registered: Network Programming Volume 1, Third Edition: The Sockets Networking ”,6.2節“I/O Models ”

A synchronous I/O operation causes the requesting process to be blocked until that I/O operation completes;

An asynchronous I/O operation does not cause the requesting process to be blocked;

用IO操作中有阻塞來判斷,5種IO模型中4種屬于同步IO,分别是阻塞IO模型,非阻塞IO模型,IO複用模型,信号驅動IO模型。

阻塞IO是socket的預設設定,其模型如下圖所示,程式調用recvfrom産生一個系統調用,kernel收到該調用請求後有兩個步驟,第一是等待資料準備好,第二是将資料從核心空間拷貝到使用者空間然後傳回OK,使用者空間收到系統調用傳回後才會繼續程式流的執行。

一種了解同步/異步,阻塞/非阻塞,Linux IO 模型,select /poll /epoll 的方法

socket使用非阻塞IO模型需要對socket進行另行設定,非阻塞IO模型如下所示。核心收到系統調用後,若資料未準備好立即傳回error,使用者程序收到error會繼續産生系統調用,直到資料準備好了并被拷貝到使用者空間。

一種了解同步/異步,阻塞/非阻塞,Linux IO 模型,select /poll /epoll 的方法

select/poll/epoll對應的是IO複用模型,優勢是能夠監聽多個socket,模型圖如下所示。使用者程序調用select産生系統調用,kernel會監聽所有select負責的socket,一旦有一個socket資料準備好了,kernel即傳回,使用者再去recvfrom産生系統調用将資料從核心空間讀到使用者空間。

一種了解同步/異步,阻塞/非阻塞,Linux IO 模型,select /poll /epoll 的方法

使用者程式注冊一個信号handler,然後繼續做後續的事情,當核心資料準備好了會發送一個信号,程式調用recvfrom進行系統調用将資料從核心空間拷貝到使用者空間。

一種了解同步/異步,阻塞/非阻塞,Linux IO 模型,select /poll /epoll 的方法

用IO操作中無阻塞來判斷,5種IO模型中隻有異步IO。

異步IO模型如下,aio_read産生系統調用,kernel在資料準備好後将資料從核心空間拷貝到使用者空間後傳回一個信号告知read資料成功,整個過程程式調用aio_read後就繼續執行其他部分直到收到信号,調用handler處理。

一種了解同步/異步,阻塞/非阻塞,Linux IO 模型,select /poll /epoll 的方法

Kernel有兩個過程,等待資料準備好和拷貝資料到使用者空間,使用者程式的阻塞一般有兩種情況,select的阻塞和socket IO的阻塞,5中IO模型的對比如下。

一種了解同步/異步,阻塞/非阻塞,Linux IO 模型,select /poll /epoll 的方法

Select/poll/epoll能夠同時監聽多個檔案描述符fd,當有fd的讀寫操作完成時會傳回這些fd,可以對應于IO複用模型中的系統調用查詢fd是否準備好資料的那一部分。

select在使用者層使用某個結構辨別被監聽的fd以及監聽的狀态,每一個fd用1bit表示,為1表示這個檔案是被監聽的,0表示不監聽。in, out, ex指向的bit數組表示對應的讀,寫,異常檔案的描述符。res_in, res_out,res_ex指向的bit數組表示對應的讀,寫,異常檔案的描述符的檢測結果。

這個結構被拷貝到核心層,

對所有的fd注冊回調函數__pollwait

調用fd的poll方法周遊整個FD_SESIZET所有的fd,檢查是不是自己需要監聽的,如果監聽的fd發生了感興趣的事(檔案讀寫操作完成或者異常,參考使用者态預先的設定),則poll方法傳回一個描述讀寫操作是否就緒的mask掩碼,根據mask掩碼給fd_set指派。poll->poll_wait->__pollwait會把目前程序挂到對應檔案的inode中的fd的等待隊列。

如果一輪周遊無果則挂起,直到逾時或者有裝置驅動發生自身資源可讀寫後将其從等待隊列喚醒,則執行新一輪的周遊。

把fd_set從核心空間拷貝到使用者空間并将程序從各個等待隊列中删除。

poll的實作機制與select類似,不一樣的是poll的使用中使用者态直接提供了需要監聽的fd的資訊,pollfd結構記錄被監聽的fd和它的狀态。

另外poll使用poll_list結構來記錄監聽的fd,每一個poll_list節點都包含一個pollfd數組,參數被拷貝到核心後,poll_list被周遊,換言之pollfd數組被周遊,與select一樣,所有的fd都被周遊。

epoll将過程細化為一組系統調用,包括1個epoll_create,多個epoll_ctrl,1個epoll_wait。核心針對epoll操作添加了一個檔案系統”eventpollfs”,每一個或者多個要監視的fd都有一個對應的eventpollfs檔案系統的inode節點,主要資訊儲存在eventpoll結構體中,而被監視的檔案的重要資訊則儲存在epitem結構體中。在執行epoll_create和epoll_ctrl時把使用者态的資訊儲存到核心态了,之後即使反複地調用epoll_wait,也不會像select/poll那樣重複地拷貝參數,掃描fd,反複地把目前程序放入/放出等待隊列。

epoll_create()的功能是建立一個eventpollfs檔案系統的inode節點,主要資訊儲存在eventpoll結構體中,eventpoll記錄了eventpollfs檔案系統的inde節點的重要資訊,其中成員rbr儲存該epoll檔案節點監視的所有描述符,組織的方式是一棵紅黑樹,被監視的檔案的重要資訊則儲存在epitem結構體中。

Epoll_ctl實作一系列操作,它調用ep_find()從eventpoll中的紅黑樹獲得epitem結構體,根據op參數的不同而選擇不同的操作。當op為EPOLL_CTL_ADD,一般情況下epitem無法在eventpoll的紅黑樹中找到,是以調用ep_insert建立一個epitem結構體并插入到對應的紅黑樹中,

然後ep_insert調用init_poll_funcptr注冊一個回調函數ep_ptable_queue_proc,該函數會在調用f_op->poll時被執行。Ep_ptable_queue_proc函數配置設定一個epoll等待隊列結點epoll_entry,一方面把它挂到檔案操作的等待隊列中,另一方面把它挂到epitem的隊列中。此外它還注冊了一個等待隊列的回調函數ep_poll_callback,ep_poll_callback在完成操作完成,喚醒目前等待程序之前被調用,會把epitem放到eventpoll的完成隊列,然後喚醒等待程序。

epoll_wait的工作是等待檔案操作完成并傳回。它的主體是ep_poll(),該函數檢查eventpoll中有沒有已經完成的事件,有的話就把結果傳回。沒有的話調用schedule_timeout()進入休眠,直到程序被再度喚醒或者逾時。

對比

select/poll的弱點在于需要輪詢周遊fd,當監聽fd多時開銷大;而epoll依賴于回調函數,當活躍fd過多時開銷就大。

監聽fd的個數

消息傳遞的方式

是否周遊所有fd

适用場景

select

最大為FD_SETSIZE,修改需要重新編譯核心

核心與使用者空間的拷貝

連結數少且活躍

poll

無限制,基于連結清單存儲

連接配接數少且活躍

epoll

很大

共享記憶體

活躍socket少

其實我并不是很相信自己寫的某些部分,例如poll的細節,畢竟是整合的别人的資料,但是目前為止确實能解釋我的某些疑惑,以後有空有機會再去深挖求證。

[1]http://blog.csdn.net/hguisu/article/details/7453390

[2]http://blog.csdn.net/historyasamirror/article/details/5778378