select poll epoll都是IO多路復用機制。這裏的複用其實能夠理解爲複用的線程,即一個(或者較少的)線程完成多個IO的讀寫。這裏總結下這三個函數的區別。html
1 select的函數原型是node
int select(int nfds, fd_set *restrict readfds, fd_set *restrict writefds, fd_set *restrict errorfds, struct timeval *restrict timeout);
使用的時候須要將fd_set從用戶空間copy到內核空間。select的使用方式相似以下socket
while true { select(streams[]) for i in streams[] { //須要遍歷全部的fd_set if i has data // 判斷是否有數據 read until unavailable } }
2 select的核心是do_select()。do_select首先會註冊回調函數__pollwait,__pollwait會在被調用的時候將當前進程添加到設備的等待隊列裏。ide
do_select會在一個for循環裏調用設備的f_op->poll。而該函數有兩個做用,一個是調用poll_wait()函數,一個是檢測設備當前狀態。而poll_wait會調用回調函數__pollwait,將當前進程加入到設備等待隊列裏。函數
設備本身實現了當有讀寫的時候會喚醒等待隊列裏的進程。若是當前沒有設備可讀寫,那麼do_select()就將當前進程睡眠。設備會在有讀寫的時候喚醒進程。喚醒後設備必須從新輪詢一遍全部的設備,調用poll來檢測設備當前的狀態以肯定哪些可寫可讀。.net
int do_select(int n, fd_set_bits *fds, struct timespec *end_time) { struct poll_wqueues table; poll_table *wait; poll_initwait(&table); // 註冊回調函數__pollwait wait = &table.pt; // … for (;;) { // … for (i = 0; i < n; ++rinp, ++routp, ++rexp) { // … struct fd f; f = fdget(i); if (f.file) { const struct file_operations *f_op; // 重要 f_op = f.file->f_op; // 重要 mask = DEFAULT_POLLMASK; if (f_op->poll) { wait_key_set(wait, in, out, bit, busy_flag); // 對每一個fd進行I/O事件檢測 mask = (*f_op->poll)(f.file, wait); // 函數指針,每一個設備自定義本身的poll。每一個設備擁有一個struct file_operations結構體,這個結構體裏定義了各類用於操做設備的函數指針,具體怎麼操做是設備本身定義的 } fdput(f); // … } } // 退出循環體 if (retval || timed_out || signal_pending(current)) break; // 沒有可讀寫,讓進程進入休眠 if (!poll_schedule_timeout(&table, TASK_INTERRUPTIBLE, to, slack)) timed_out = 1; } }
3 file 結構線程
struct file { struct path f_path; struct inode *f_inode; /* cached value */ const struct file_operations *f_op; // … } __attribute__((aligned(4))); /* lest something weird decides that 2 is OK */
4 file_operations結構指針
struct file_operations { struct module *owner; loff_t (*llseek) (struct file *, loff_t, int); ssize_t (*read) (struct file *, char __user *, size_t, loff_t *); ssize_t (*write) (struct file *, const char __user *, size_t, loff_t *); ssize_t (*aio_read) (struct kiocb *, const struct iovec *, unsigned long, loff_t); ssize_t (*aio_write) (struct kiocb *, const struct iovec *, unsigned long, loff_t); ssize_t (*read_iter) (struct kiocb *, struct iov_iter *); ssize_t (*write_iter) (struct kiocb *, struct iov_iter *); int (*iterate) (struct file *, struct dir_context *); // select()輪詢設備fd的操做函數, poll調用poll_wait, 而poll_wait會調用回調函數__pollwait, __pollwait將當前進程加到等待隊列裏 unsigned int (*poll) (struct file *, struct poll_table_struct *); // … };
5 簡單總結來說,select會遍歷fd_set,調用f_op->poll(此poll非select/poll的poll),若是有可讀/寫的fd則返回可讀/寫的fd,若是沒有則在每一個fd的等待隊列中加入當前進程,當前進程進入睡眠。當有fd可讀/寫的時候會喚醒當前進程,當前進行從新遍歷fd_set,返回可讀/寫的全部fd。rest
6 從中也能夠看出select的幾大缺點:code
poll的實現原理和select相似,只是接口的方式不一樣。
1 epoll的函數原型是
int epoll_create(int size); // 建立一個epoll對象,通常epollfd = epoll_create() int epoll_ctl(int epfd, int op, int fd, struct epoll_event *event); // (epoll_add/epoll_del的合體),往epoll對象中增長/刪除某一個流的某一個事件好比epoll_ctl(epollfd, EPOLL_CTL_ADD, socket, EPOLLIN);//有緩衝區內有數據時epoll_wait返回epoll_ctl(epollfd, EPOLL_CTL_DEL, socket, EPOLLOUT);//緩衝區可寫入時epoll_wait返回 int epoll_wait(int epfd, struct epoll_event *events,int maxevents, int timeout); // 等待直到註冊的事件發生
epoll的使用方式相似以下:
while true { active_stream[] = epoll_wait(epollfd) // 只返回可讀/寫的fd,而不是像select同樣,返回全部的fd for i in active_stream[] { read or write till unavailable } }
2 epoll_create。epoll會向內核註冊一個文件系統,調用epoll_create時:
3 epoll_ctl。 當咱們執行epoll_ctl時,除了把socket放到epoll文件系統裏file對象對應的紅黑樹上以外,還會給內核中斷處理程序註冊一個回調函數,告訴內核,若是這個句柄的中斷到了,就把它放到準備就緒list鏈表裏。因此,當一個socket上有數據到了,內核在把網卡上的數據copy到內核中後就來把socket插入到list裏了
4 epoll_wait。 epoll_wait調用時,僅僅觀察這個list鏈表裏有沒有數據便可。有數據就返回,沒有數據就sleep
5 可見,