轉載:http://blog.csdn.net/mr253727942/article/details/50827127
一、IO多路復用定義
IO多路復用允許應用在多個文件描述符上阻塞,并在某一個可以讀寫時通知, 一般遵循下面的設計原則:、
- IO多路復用:任何文件描述符準備好IO時進行通知
- 在文件描述符就緒前進行睡眠。
- 喚醒:哪個準備好了
- 在不阻塞的情況下處理所有IO就緒的文件描述符
- 返回第一步
Linux下提供了三種IO多路復用方案,select、poll和epoll。
二、select IO 多路復用
看一下select 函數的定義:
int select (int n,fd_set *readfds,fd_set *writefds,fd_set *exceptfds,struct timeval *timeout);FD_CLR(int fd, fd_set *set);FD_ISSET(int fd, fd_set *set);FD_SET(int fd, fd_set *set);FD_ZERO(fd_set *set);
上面的定義中可以看到select主要檢測三類文件描述符,分別等待不同的事件。
-
readfds
確認是否有可讀數據
-
writefds
確認是否有可寫數據
-
exceptefds
確認是否有異常發生或者出現帶外數據。
第一個參數n,等于所有集合中文件描述符的最大值加一。select()的調用者需要找到最大的文件描述符值加一作為第一個參數。
成功返回時,返回哪一個文件描述符,就說明該文件描述符準備好無阻塞IO。對于timeout,select操作可以設置一個超時時間,超時后即使沒有文件描述符IO就緒也會返回。
select的缺點:
1.每次調用select,都需要把fd集合從用戶態拷貝到內核態?
2。同時需要遍歷所有fd?
3。支持的文件描述符默認只有1024
三、poll IO 多路復用
poll()系統調用也是一個IO多路復用解決方案,解決了 一些select的不足,下面給出poll的定義:
#include <sys/poll.h>
int poll (struct pollfd *fds, unsigned int nfds,
int timeout);
與上面的select()使用三個文件描述符集合不同,poll()使用了一個簡單的nfds個pollfd結構體構成的數組,fds指向該數組,結構體定義如下:
#include <sys/poll.h>
struct pollfd {int fd; /* file descriptor */short events; /* requested events to watch */short revents; /* returned events witnessed */
};
每個pollfd指定了唯一一個文件描述符,每個結構體中的events字段是要堅實的文件描述符事件的一組位掩碼。revents字段是發生在該文件描述符上的事件的位掩碼,內核在返回時設置這個字段,所有events字段請求的時間都可能在revents字段中返回。下面是合法的事件:
POLLIN 沒有數據可讀
POLLRDNORM 有正常數據可讀。
POLLRDBAND 有優先數據可讀。
POLLPRI 有高優先級數據可讀。
POLLOUT 寫操作不會阻塞。
POLLWRNORM 寫正常數據不會阻塞。
POLLBAND 寫優先數據不會阻塞。
POLLMSG 有一個sigpoll消息可用。
除此之外還可能返回幾個異常信息:
POLLER 文件描述符有錯誤。
POLLHUP 文件描述符上有掛起事件。
POLLNVAL 給出的文件描述符非法。
監視一個文件描述符是否可以讀寫,可以設置events為POLLIN | POLLOUT,返回時將在revents中檢查是否有相應標志。如果設置了POLLIN,或者POLLOUT則代表可以操作相關事件。
timeout參數指定在任何IO就緒前需要等待時間的長度,負值表示永遠等待,一個零值表示調用立即返回,列出所有為準備好的IO,不等待任何時間。
四、poll()與select()的區別
poll()系統調用優于select():
- poll()不需要使用者計算最大的文件描述符值加一和傳遞該參數。
- poll()在應對較大值的文件描述符時效率更好,如果用select()監視值為900的文件描述符–內核需要檢查每個集合中的每個bit位,知道第九百個。
- select()的文件描述符集合是靜態大小,但是poll()可以創建合適大小的數組,只需要傳遞結構體數組即可。
- select()文件描述符集合會在返回時重新創建,每個調用都必須要重新初始化它們。poll()系統調用分離了events字段和revents字段,無需改變就能重用。
- 相對于poll(),select()移植性更好
- select()提供了更好的超時方案。
五、epoll IO 多路復用
上面的兩種方式中,每次調用都需要所有被監聽的文件描述符,內核必須遍歷所有的文件描述符,當文件描述符變得很大,這里的遍歷就會成為瓶頸。
epoll將監聽注冊從實際監聽中分離出來,完成了真正的事件等待。
1、先創建一個新的epoll實例:
#include <sys/epoll.h>
int efpd = epoll_create (int size)
size是告訴內核大概需要監聽的文件描述符數目。
2、控制epoll
epoll_ctl()可以向指定的epoll上下文中加入或刪除文件描述符。
#include <sys/epoll.h>
int epoll_ctl (int epfd, int op, int fd, struct
epoll_event *event);
頭文件
六、IO實現的內核內幕
主要涉及三個內核子系統:
- 虛擬文件系統(VFS)
- 頁緩存
- 頁回寫
虛擬文件系統
虛擬文件系統是linux內核的文件操作的抽象機制,允許內核在無需了解文件系統類型的情況下,使用文件系統函數和操作文件系統數據。
VFS實現這種抽象的方法是使用一種通用文件模型,它是所有linux文件系統的基礎,通用文件模型提供了linux內核文件系統必須遵循的框架,框架提供了了hooks支持讀寫、建立鏈接、同步等其他功能。
當然這種方法規定了一些共性,比如必須要有inode,super block(超級塊)和目錄條目等。
頁緩存
頁緩存是一種在內存中保存最近在磁盤文件系統上訪問過的數據的方式。頁緩存是內核尋找文件系統數據的第一目的地。只有緩存找不到時內核才會調用存儲子系統從磁盤讀數據。
linux中頁緩存大小是動態的,隨著IO操作將越來越多的數據帶入內存,頁緩存會隨之增大,消耗更多的內存,如果頁緩存確實消耗掉了所有空閑內存,頁緩存會釋放最少使用頁。
頁回寫
內核使用緩沖區來延遲寫操作,當一個進程發起寫請求,數據被拷貝到緩沖區,這時將緩沖區標記為“臟”數據,如果對同一個數據塊有新的寫請求,緩沖區就更新為新數據,把“臟”緩沖區寫入磁盤。有兩個條件會觸發這種回寫:
- 當空閑內存小于設定的閥值,會將緩沖區回寫。
- 當一個臟的緩沖區壽命超過閥值也會回寫防止數據不確定。
回寫由一些pdflush內核線程操作,當上述兩種情況發生,線程被喚醒開始刷新臟緩沖區。