linux內(nèi)核select/poll,epoll實(shí)現(xiàn)與區(qū)別
下面文章在這段時(shí)間內(nèi)研究 select/poll/epoll的內(nèi)核實(shí)現(xiàn)的一點(diǎn)心得體會(huì):
select,poll,epoll都是多路復(fù)用IO的函數(shù),簡(jiǎn)單說就是在一個(gè)線程里,可以同時(shí)處理多個(gè)文件描述符的讀寫。
select/poll的實(shí)現(xiàn)很類似,epoll是從select/poll擴(kuò)展而來,主要是為了解決select/poll天生的缺陷。
epoll在內(nèi)核版本2.6以上才出現(xiàn)的新的函數(shù),而他們?cè)趌inux內(nèi)核中的實(shí)現(xiàn)都是十分相似。
這三種函數(shù)都需要設(shè)備驅(qū)動(dòng)提供poll回調(diào)函數(shù),對(duì)于套接字而言,他們是 tcp_poll,udp_poll和datagram_poll;
對(duì)于自己開發(fā)的設(shè)備驅(qū)動(dòng)而言,是自己實(shí)現(xiàn)的poll接口函數(shù)。
select實(shí)現(xiàn)(2.6的內(nèi)核,其他版本的內(nèi)核,應(yīng)該都相差不多)
應(yīng)用程序調(diào)用select,進(jìn)入內(nèi)核調(diào)用sys_select,做些簡(jiǎn)單初始化工作,接著進(jìn)入 core_sys_select,
此函數(shù)主要工作是把描述符集合從用戶空間復(fù)制到內(nèi)核空間, 最終進(jìn)入do_select,完成其主要的功能。
do_select里,調(diào)用 poll_initwait,主要工作是注冊(cè)poll_wait的回調(diào)函數(shù)為__pollwait,
當(dāng)在設(shè)備驅(qū)動(dòng)的poll回調(diào)函數(shù)里調(diào)用poll_wait,其實(shí)就是調(diào)用__pollwait,
__pollwait的主要工作是把當(dāng)前進(jìn)程掛載到等待隊(duì)列里,當(dāng)?shù)却氖录絹砭蜁?huì)喚醒此進(jìn)程。
接著執(zhí)行for循環(huán),循環(huán)里首先遍歷每個(gè)文件描述符,調(diào)用對(duì)應(yīng)描述符的poll回調(diào)函數(shù),檢測(cè)是否就緒,
遍歷完所有描述符之后,只要有描述符處于就緒狀態(tài),信號(hào)中斷,出錯(cuò)或者超時(shí),就退出循環(huán),
否則會(huì)調(diào)用schedule_xxx函數(shù),讓當(dāng)前進(jìn)程睡眠,一直到超時(shí)或者有描述符就緒被喚醒。
接著又會(huì)再次遍歷每個(gè)描述符,調(diào)用poll再次檢測(cè)。
如此循環(huán),直到符合條件才會(huì)退出。
以下是 2.6.31內(nèi)核的有關(guān)select函數(shù)的部分片段:
他們調(diào)用關(guān)系:
select --> sys_select --> core_sys_select --> do_select
int do_select(int n, fd_set_bits *fds, struct timespec *end_time) { ktime_t expire, *to = NULL; struct poll_wqueues table; poll_table *wait; int retval, i, timed_out = 0; unsigned long slack = 0; ///這里為了獲得集合中的最大描述符,這樣可減少循環(huán)中遍歷的次數(shù)。 ///也就是為什么linux中select第一個(gè)參數(shù)為何如此重要了 rcu_read_lock(); retval = max_select_fd(n, fds); rcu_read_unlock(); if (retval < 0) return retval; n = retval; ////初始化 poll_table結(jié)構(gòu),其中一個(gè)重要任務(wù)是把 __pollwait函數(shù)地址賦值給它, poll_initwait(&table); wait = &table.pt; if (end_time && !end_time->tv_sec && !end_time->tv_nsec) { wait = NULL; timed_out = 1; } if (end_time && !timed_out) slack = estimate_accuracy(end_time); retval = 0; ///主循環(huán),將會(huì)在這里完成描述符的狀態(tài)輪訓(xùn) for (;;) { unsigned long *rinp, *routp, *rexp, *inp, *outp, *exp; inp = fds->in; outp = fds->out; exp = fds->ex; rinp = fds->res_in; routp = fds->res_out; rexp = fds->res_ex; for (i = 0; i < n; ++rinp, ++routp, ++rexp) { unsigned long in, out, ex, all_bits, bit = 1, mask, j; unsigned long res_in = 0, res_out = 0, res_ex = 0; const struct file_operations *f_op = NULL; struct file *file = NULL; ///select中 fd_set 以及 do_select 中的 fd_set_bits 參數(shù),都是按照位來保存描述符,意思是比如申請(qǐng)一個(gè)1024位的內(nèi)存, ///如果第 28位置1,說明此集合有 描述符 28, in = *inp++; out = *outp++; ex = *exp++; all_bits = in | out | ex; // 檢測(cè)讀寫異常3個(gè)集合中有無描述符 if (all_bits == 0) { i += __NFDBITS; continue; } for (j = 0; j < __NFDBITS; ++j, ++i, bit <<= 1) { int fput_needed; if (i >= n) break; if (!(bit & all_bits)) continue; file = fget_light(i, &fput_needed); ///通過 描述符 index 獲得 struct file結(jié)構(gòu)指針, if (file) { f_op = file->f_op; //通過 struct file 獲得 file_operations,這是操作文件的回調(diào)函數(shù)集合。 mask = DEFAULT_POLLMASK; if (f_op && f_op->poll) { wait_key_set(wait, in, out, bit); mask = (*f_op->poll)(file, wait); //調(diào)用我們的設(shè)備中實(shí)現(xiàn)的 poll函數(shù), //因此,為了能讓select正常工作,在我們?cè)O(shè)備驅(qū)動(dòng)中,必須要提供poll的實(shí)現(xiàn), } fput_light(file, fput_needed); if ((mask & POLLIN_SET) && (in & bit)) { res_in |= bit; retval++; wait = NULL; /// 此處包括以下的,把wait設(shè)置為NULL,是因?yàn)闄z測(cè)到mask = (*f_op->poll)(file, wait); 描述符已經(jīng)就緒 /// 無需再把當(dāng)前進(jìn)程添加到等待隊(duì)列里,do_select 遍歷完所有描述符之后就會(huì)退出。 } if ((mask & POLLOUT_SET) && (out & bit)) { res_out |= bit; retval++; wait = NULL; } if ((mask & POLLEX_SET) && (ex & bit)) { res_ex |= bit; retval++; wait = NULL; } } } if (res_in) *rinp = res_in; if (res_out) *routp = res_out; if (res_ex) *rexp = res_ex; cond_resched(); } wait = NULL; //已經(jīng)遍歷完一遍,該加到等待隊(duì)列的,都已經(jīng)加了,無需再加,因此設(shè)置為NULL if (retval || timed_out || signal_pending(current)) //描述符就緒,超時(shí),或者信號(hào)中斷就退出循環(huán) break; if (table.error) {//出錯(cuò)退出循環(huán) retval = table.error; break; } /* * If this is the first loop and we have a timeout * given, then we convert to ktime_t and set the to * pointer to the expiry value. */ if (end_time && !to) { expire = timespec_to_ktime(*end_time); to = &expire; } /////讓進(jìn)程休眠,直到超時(shí),或者被就緒的描述符喚醒, if (!poll_schedule_timeout(&table, TASK_INTERRUPTIBLE, to, slack)) timed_out = 1; } poll_freewait(&table); return retval; } void poll_initwait(struct poll_wqueues *pwq) { init_poll_funcptr(&pwq->pt, __pollwait); //設(shè)置poll_table的回調(diào)函數(shù)為 __pollwait,這樣當(dāng)我們?cè)隍?qū)動(dòng)中調(diào)用poll_wait 就會(huì)調(diào)用到 __pollwait ........ } static void __pollwait(struct file *filp, wait_queue_head_t *wait_address, poll_table *p) { ................... init_waitqueue_func_entry(&entry->wait, pollwake); // 設(shè)置喚醒進(jìn)程調(diào)用的回調(diào)函數(shù),當(dāng)在驅(qū)動(dòng)中調(diào)用 wake_up喚醒隊(duì)列時(shí)候, // pollwake會(huì)被調(diào)用,這里其實(shí)就是調(diào)用隊(duì)列的默認(rèn)函數(shù) default_wake_function // 用來喚醒睡眠的進(jìn)程。 add_wait_queue(wait_address, &entry->wait); //加入到等待隊(duì)列 } int core_sys_select(int n, fd_set __user *inp, fd_set __user *outp, fd_set __user *exp, struct timespec *end_time) { ........ //把描述符集合從用戶空間復(fù)制到內(nèi)核空間 if ((ret = get_fd_set(n, inp, fds.in)) || (ret = get_fd_set(n, outp, fds.out)) || (ret = get_fd_set(n, exp, fds.ex))) ......... ret = do_select(n, &fds, end_time); ............. ////把do_select返回集合,從內(nèi)核空間復(fù)制到用戶空間 if (set_fd_set(n, inp, fds.res_in) || set_fd_set(n, outp, fds.res_out) || set_fd_set(n, exp, fds.res_ex)) ret = -EFAULT; ............ }
poll的實(shí)現(xiàn)跟select基本差不多,按照
poll --> do_sys_poll --> do_poll --> do_pollfd 的調(diào)用序列
其中do_pollfd是對(duì)每個(gè)描述符調(diào)用 其回調(diào)poll狀態(tài)輪訓(xùn)。
poll比select的好處就是沒有描述多少限制,select 有1024 的限制,描述符不能超過此值,poll不受限制。
我們從上面代碼分析,可以總結(jié)出select/poll天生的缺陷:
1)每次調(diào)用select/poll都需要要把描述符集合從用戶空間copy到內(nèi)核空間,檢測(cè)完成之后,又要把檢測(cè)的結(jié)果集合從內(nèi)核空間copy到用戶空間
當(dāng)描述符很多,而且select經(jīng)常被喚醒,這種開銷會(huì)比較大
2)如果說描述符集合來回復(fù)制不算什么,那么多次的全部描述符遍歷就比較恐怖了,
我們?cè)趹?yīng)用程序中,每次調(diào)用select/poll 都必須首先遍歷描述符,把他們加到fd_set集合里,這是應(yīng)用層的第一次遍歷,
接著進(jìn)入內(nèi)核空間,至少進(jìn)行一次遍歷和調(diào)用每個(gè)描述符的poll回調(diào)檢測(cè),一般可能是2次遍歷,第一次沒發(fā)現(xiàn)就緒描述符,
加入等待隊(duì)列,第二次是被喚醒,接著再遍歷一遍。再回到應(yīng)用層,我們還必須再次遍歷所有描述符,用 FD_ISSET檢測(cè)結(jié)果集。
如果描述符很多,這種遍歷就很消耗CPU資源了。
3)描述符多少限制,當(dāng)然poll沒有限制,select卻有1024的硬性限制,除了修改內(nèi)核增加1024限制外沒別的辦法。
既然有這么些缺點(diǎn) ,那不是 select/poll變得一無是處了,那就大錯(cuò)特錯(cuò)了。
他們依然是代碼移植的最好函數(shù),因?yàn)閹缀跛衅脚_(tái)都有對(duì)它們的實(shí)現(xiàn)提供接口。
在描述符不是太多,他們依然十分出色的完成多路復(fù)用IO,
而且如果每個(gè)連接上的描述符都處于活躍狀態(tài),他們的效率其實(shí)跟epoll也差不了多少。
曾經(jīng)使用多個(gè)線程+每個(gè)線程采用poll的辦法開發(fā)TCP服務(wù)器,處理文件收發(fā),連接達(dá)到幾千個(gè),
當(dāng)時(shí)的瓶頸已經(jīng)不在網(wǎng)絡(luò)IO,而在磁盤IO了。
我們?cè)賮砜磂poll為了解決select/poll天生的缺陷,是如何實(shí)現(xiàn)的。
epoll只是select/poll的擴(kuò)展,他不是在linux內(nèi)核中另起爐灶,做顛覆性的設(shè)計(jì)的,他只是在select的基礎(chǔ)上來解決他們的缺陷。
他的底層依然需要設(shè)備驅(qū)動(dòng)提供poll回調(diào)來作為狀態(tài)檢測(cè)基礎(chǔ)。
epoll分為三個(gè)函數(shù) epoll_create,epoll_ctl, epoll_wait 。
他們的實(shí)現(xiàn)在 eventpoll.c代碼里。
epoll_create創(chuàng)建epoll設(shè)備,用來管理所有添加進(jìn)去的描述符,epoll_ctl 用來添加新的描述符,修改或者刪除描述符。
epoll_wait等待描述符事件。
epoll_wait的等待已經(jīng)不再是輪訓(xùn)方式的等待了,epoll內(nèi)部有個(gè)描述符就緒隊(duì)列,epoll_wait只檢測(cè)這個(gè)隊(duì)列即可,
他采用睡眠一會(huì)檢測(cè)一下的方式,如果發(fā)現(xiàn)描述符就緒隊(duì)列不為空,就把此隊(duì)列中的描述符copy到用戶空間,然后返回。
描述符就緒隊(duì)列里的數(shù)據(jù)又是從何而來的?
原來使用 epoll_ctl添加新描述符時(shí)候,epoll_ctl內(nèi)核實(shí)現(xiàn)里會(huì)修改兩個(gè)回調(diào)函數(shù),
一個(gè)是 poll_table結(jié)構(gòu)里的qproc回調(diào)函數(shù)指針,
在 select中是 __pollwait函數(shù),在epoll中換成 ep_ptable_queue_proc,
當(dāng)在epoll_ctl中調(diào)用新添加的描述符的poll回調(diào)時(shí)候,底層驅(qū)動(dòng)就會(huì)調(diào)用 poll_wait添加等待隊(duì)列,
底層驅(qū)動(dòng)調(diào)用poll_wait時(shí)候,
其實(shí)就是調(diào)用ep_ptable_queue_proc,此函數(shù)會(huì)修改等待隊(duì)列的回調(diào)函數(shù)為 ep_poll_callback, 并加入到等待隊(duì)列頭里;
一旦底層驅(qū)動(dòng)發(fā)現(xiàn)數(shù)據(jù)就緒,就會(huì)調(diào)用wake_up喚醒等待隊(duì)列,從而 ep_poll_callback將被調(diào)用,
在ep_poll_callback中 會(huì)把這個(gè)就緒的描述符添加到 epoll的描述符就緒隊(duì)列里,并同時(shí)喚醒 epoll_wait 所在的進(jìn)程。
如此這般,就是epoll的內(nèi)核實(shí)現(xiàn)的精髓。
看他是如何解決 select/poll的缺陷的, 首先他通過 epoll_ctl的EPOLL_CTL_ADD命令把描述符添加進(jìn)epoll內(nèi)部管理器里,
只需添加一次即可,直到用 epoll_ctl的EPOLL_CTL_DEL命令刪除此描述符為止,
而不像select/poll是每次執(zhí)行都必須添加,很顯然大量減少了描述符在內(nèi)核和用戶空間不斷的來回copy的開銷。
其次雖然 epoll_wait內(nèi)部也是循環(huán)檢測(cè),但是它只需檢測(cè)描述符就緒隊(duì)列是否為空即可,
比起select/poll必須輪訓(xùn)每個(gè)描述符的poll,其開銷簡(jiǎn)直可以忽略不計(jì)。
他同時(shí)也沒描述符多少的限制,只要你機(jī)器的內(nèi)存夠大,就能容納非常多的描述符。
以下是 epoll相關(guān)部分內(nèi)核代碼片段:
struct epitem { /* RB tree node used to link this structure to the eventpoll RB tree */ struct rb_node rbn; // 紅黑樹節(jié)點(diǎn), struct epoll_filefd ffd; // 存儲(chǔ)此變量對(duì)應(yīng)的描述符 struct epoll_event event; //用戶定義的結(jié)構(gòu) /*其他成員*/ }; struct eventpoll { /*其他成員*/ ....... /* Wait queue used by file->poll() */ wait_queue_head_t poll_wait; /* List of ready file descriptors */ struct list_head rdllist; ///描述符就緒隊(duì)列,掛載的是 epitem結(jié)構(gòu) /* RB tree root used to store monitored fd structs */ struct rb_root rbr; /// 存儲(chǔ) 新添加的 描述符的紅黑樹根, 此成員用來存儲(chǔ)添加進(jìn)來的所有描述符。掛載的是epitem結(jié)構(gòu) ......... }; //epoll_create SYSCALL_DEFINE1(epoll_create1, int, flags) { int error; struct eventpoll *ep = NULL; /*其他代碼*/ ...... //分配 eventpoll結(jié)構(gòu),這個(gè)結(jié)構(gòu)是epoll的靈魂,他包含了所有需要處理得數(shù)據(jù)。 error = ep_alloc(&ep); if (error < 0) return error; error = anon_inode_getfd("[eventpoll]", &eventpoll_fops, ep, flags & O_CLOEXEC); ///打開 eventpoll 的描述符,并把 ep存儲(chǔ)到 file->private_data變量里。 if (error < 0) ep_free(ep); return error; } SYSCALL_DEFINE4(epoll_ctl, int, epfd, int, op, int, fd, struct epoll_event __user *, event) { /*其他代碼*/ ..... ep = file->private_data; ...... epi = ep_find(ep, tfile, fd); ///從 eventpoll的 rbr里查找描述符是 fd 的 epitem, error = -EINVAL; switch (op) { case EPOLL_CTL_ADD: if (!epi) { epds.events |= POLLERR | POLLHUP; error = ep_insert(ep, &epds, tfile, fd); // 在這個(gè)函數(shù)里添加新描述符,同時(shí)修改重要的回調(diào)函數(shù)。 //同時(shí)還調(diào)用描述符的poll,查看就緒狀態(tài) } else error = -EEXIST; break; /*其他代碼*/ ........ } static int ep_insert(struct eventpoll *ep, struct epoll_event *event, struct file *tfile, int fd) { ..... /*其他代碼*/ init_poll_funcptr(&epq.pt, ep_ptable_queue_proc);//設(shè)置 poll_tabe回調(diào)函數(shù)為 ep_ptable_queue_proc //ep_ptable_queue_proc會(huì)設(shè)置等待隊(duì)列的回調(diào)指針為 ep_epoll_callback,同時(shí)添加等待隊(duì)列。 ........ /*其他代碼*/ revents = tfile->f_op->poll(tfile, &epq.pt); //調(diào)用描述符的poll回調(diào),在此函數(shù)里 ep_ptable_queue_proc會(huì)被調(diào)用 ....... /*其他代碼*/ ep_rbtree_insert(ep, epi); //把新生成關(guān)于epitem添加到紅黑樹里 ...... /*其他代碼*/ if ((revents & event->events) && !ep_is_linked(&epi->rdllink)) { list_add_tail(&epi->rdllink, &ep->rdllist); //如果 上邊的poll調(diào)用,檢測(cè)到描述符就緒,添加本描述符到就緒隊(duì)列里。 if (waitqueue_active(&ep->wq)) wake_up_locked(&ep->wq); if (waitqueue_active(&ep->poll_wait)) pwake++; } ...... /*其他代碼*/ /* We have to call this outside the lock */ if (pwake) ep_poll_safewake(&ep->poll_wait); // 如果描述符就緒隊(duì)列不為空,則喚醒 epoll_wait所在的進(jìn)程。 ......... /*其他代碼*/ } //這個(gè)函數(shù)設(shè)置等待隊(duì)列回調(diào)函數(shù)為 ep_poll_callback, //這樣到底層有數(shù)據(jù)喚醒等待隊(duì)列時(shí)候,ep_poll_callback就會(huì)被調(diào)用,從而把就緒的描述符加到就緒隊(duì)列。 static void ep_ptable_queue_proc(struct file *file, wait_queue_head_t *whead, poll_table *pt) { struct epitem *epi = ep_item_from_epqueue(pt); struct eppoll_entry *pwq; if (epi->nwait >= 0 && (pwq = kmem_cache_alloc(pwq_cache, GFP_KERNEL))) { init_waitqueue_func_entry(&pwq->wait, ep_poll_callback); pwq->whead = whead; pwq->base = epi; add_wait_queue(whead, &pwq->wait); list_add_tail(&pwq->llink, &epi->pwqlist); epi->nwait++; } else { /* We have to signal that an error occurred */ epi->nwait = -1; } } static int ep_poll_callback(wait_queue_t *wait, unsigned mode, int sync, void *key) { int pwake = 0; unsigned long flags; struct epitem *epi = ep_item_from_wait(wait); struct eventpoll *ep = epi->ep; ......... /*其他代碼*/ if (!ep_is_linked(&epi->rdllink)) list_add_tail(&epi->rdllink, &ep->rdllist); // 把當(dāng)前就緒的描述epitem結(jié)構(gòu)添加到就緒隊(duì)列里 ......... /*其他代碼*/ if (pwake) ep_poll_safewake(&ep->poll_wait); //如果隊(duì)列不為空,喚醒 epoll_wait所在進(jìn)程 ......... /*其他代碼*/ } epoll_wait內(nèi)核代碼里主要是調(diào)用ep_poll,列出ep_poll部分代碼片段: static int ep_poll(struct eventpoll *ep, struct epoll_event __user *events, int maxevents, long timeout) { int res, eavail; unsigned long flags; long jtimeout; wait_queue_t wait; ......... /*其他代碼*/ if (list_empty(&ep->rdllist)) { init_waitqueue_entry(&wait, current); wait.flags |= WQ_FLAG_EXCLUSIVE; __add_wait_queue(&ep->wq, &wait); // 如果檢測(cè)到就緒隊(duì)列為空,添加當(dāng)前進(jìn)程到等待隊(duì)列,并執(zhí)行否循環(huán) for (;;) { set_current_state(TASK_INTERRUPTIBLE); if (!list_empty(&ep->rdllist) || !jtimeout) //如果就緒隊(duì)列不為空,或者超時(shí)則退出循環(huán) break; if (signal_pending(current)) { //如果信號(hào)中斷,退出循環(huán) res = -EINTR; break; } spin_unlock_irqrestore(&ep->lock, flags); jtimeout = schedule_timeout(jtimeout);//睡眠,知道被喚醒或者超時(shí)為止。 spin_lock_irqsave(&ep->lock, flags); } __remove_wait_queue(&ep->wq, &wait); set_current_state(TASK_RUNNING); } ......... /*其他代碼*/ if (!res && eavail && !(res = ep_send_events(ep, events, maxevents)) && jtimeout) goto retry; // ep_send_events主要任務(wù)是把就緒隊(duì)列的就緒描述符copy到用戶空間的 epoll_event數(shù)組里, return res; }
可以看到 ep_poll既epoll_wait的循環(huán)是相當(dāng)輕松的循環(huán),他只是簡(jiǎn)單檢測(cè)就緒隊(duì)列而已,因此他的開銷很小。
我們最后看看描述符就緒時(shí)候,是如何通知給select/poll/epoll的,以網(wǎng)絡(luò)套接字的TCP協(xié)議來進(jìn)行說明。
tcp協(xié)議對(duì)應(yīng)的 poll回調(diào)是tcp_poll, 對(duì)應(yīng)的等待隊(duì)列頭是 struct sock結(jié)構(gòu)里 sk_sleep成員,
在tcp_poll中會(huì)把 sk_sleep加入到等待隊(duì)列,等待數(shù)據(jù)就緒。
當(dāng)物理網(wǎng)卡接收到數(shù)據(jù)包,引發(fā)硬件中斷,驅(qū)動(dòng)在中斷ISR例程里,構(gòu)建skb包,把數(shù)據(jù)copy進(jìn)skb,接著調(diào)用netif_rx
把skb掛載到CPU相關(guān)的 input_pkt_queue隊(duì)列,同時(shí)引發(fā)軟中斷,在軟中斷的net_rx_action回調(diào)函數(shù)里從input_pkt_queue里取出
skb數(shù)據(jù)包,通過分析,調(diào)用協(xié)議相關(guān)的回調(diào)函數(shù),這樣層層傳遞,一直到struct sock,此結(jié)構(gòu)里的 sk_data_ready回調(diào)指針被調(diào)用
sk_data_ready指向 sock_def_readable 函數(shù),sock_def_readable函數(shù)其實(shí)就是 wake_up 喚醒 sock結(jié)構(gòu)里 的 sk_sleep。
以上機(jī)制,對(duì) select/poll/epoll都是一樣的,接下來喚醒 sk_sleep方式就不一樣了,因?yàn)樗麄冎赶蛄瞬煌幕卣{(diào)函數(shù)。
在 select/poll實(shí)現(xiàn)中,等待隊(duì)列回調(diào)函數(shù)是 pollwake其實(shí)就是調(diào)用default_wake_function,喚醒被select阻塞住的進(jìn)程。
epoll實(shí)現(xiàn)中,等待回調(diào)函數(shù)是 ep_poll_callback, 此回調(diào)函數(shù)只是把就緒描述符加入到epoll的就緒隊(duì)列里。
所以呢 select/poll/epoll其實(shí)他們?cè)趦?nèi)核實(shí)現(xiàn)中,差別也不是太大,其實(shí)都差不多。
epoll雖然效率不錯(cuò),可以跟windows平臺(tái)中的完成端口比美,但是移植性太差,
目前幾乎就只有l(wèi)inux平臺(tái)才實(shí)現(xiàn)了epoll而且必須是2.6以上的內(nèi)核版本。
上一篇:VC獲取當(dāng)前路徑及程序名的實(shí)現(xiàn)代碼
欄 目:C語言
下一篇:計(jì)時(shí)器的time_t和clock_t 的兩種實(shí)現(xiàn)方法(推薦)
本文標(biāo)題:linux內(nèi)核select/poll,epoll實(shí)現(xiàn)與區(qū)別
本文地址:http://mengdiqiu.com.cn/a1/Cyuyan/1983.html
您可能感興趣的文章
- 01-10深入解析Linux下\r\n的問題
- 01-10Linux線程管理必備:解析互斥量與條件變量的詳解
- 01-10Linux C 獲取進(jìn)程退出值的實(shí)現(xiàn)代碼
- 01-10解析Linux下的時(shí)間函數(shù):設(shè)置以及獲取時(shí)間的方法
- 01-10深入探討linux下進(jìn)程的最大線程數(shù)、進(jìn)程最大數(shù)、進(jìn)程打開的文
- 01-10基于linux下獲取時(shí)間函數(shù)的詳解
- 01-10linux c 查找使用庫的cflags與libs的方法詳解
- 01-10深入探討Linux靜態(tài)庫與動(dòng)態(tài)庫的詳解(一看就懂)
- 01-10Linux下semop等待信號(hào)時(shí)出現(xiàn)Interrupted System Call錯(cuò)誤(EINTR)解決方法
- 01-10linux c 獲取本機(jī)公網(wǎng)IP的實(shí)現(xiàn)方法


閱讀排行
- 1C語言 while語句的用法詳解
- 2java 實(shí)現(xiàn)簡(jiǎn)單圣誕樹的示例代碼(圣誕
- 3利用C語言實(shí)現(xiàn)“百馬百擔(dān)”問題方法
- 4C語言中計(jì)算正弦的相關(guān)函數(shù)總結(jié)
- 5c語言計(jì)算三角形面積代碼
- 6什么是 WSH(腳本宿主)的詳細(xì)解釋
- 7C++ 中隨機(jī)函數(shù)random函數(shù)的使用方法
- 8正則表達(dá)式匹配各種特殊字符
- 9C語言十進(jìn)制轉(zhuǎn)二進(jìn)制代碼實(shí)例
- 10C語言查找數(shù)組里數(shù)字重復(fù)次數(shù)的方法
本欄相關(guān)
- 04-02c語言函數(shù)調(diào)用后清空內(nèi)存 c語言調(diào)用
- 04-02func函數(shù)+在C語言 func函數(shù)在c語言中
- 04-02c語言的正則匹配函數(shù) c語言正則表達(dá)
- 04-02c語言用函數(shù)寫分段 用c語言表示分段
- 04-02c語言中對(duì)數(shù)函數(shù)的表達(dá)式 c語言中對(duì)
- 04-02c語言編寫函數(shù)冒泡排序 c語言冒泡排
- 04-02c語言沒有round函數(shù) round c語言
- 04-02c語言分段函數(shù)怎么求 用c語言求分段
- 04-02C語言中怎么打出三角函數(shù) c語言中怎
- 04-02c語言調(diào)用函數(shù)求fibo C語言調(diào)用函數(shù)求
隨機(jī)閱讀
- 04-02jquery與jsp,用jquery
- 01-10SublimeText編譯C開發(fā)環(huán)境設(shè)置
- 08-05DEDE織夢(mèng)data目錄下的sessions文件夾有什
- 01-10使用C語言求解撲克牌的順子及n個(gè)骰子
- 01-11ajax實(shí)現(xiàn)頁面的局部加載
- 01-11Mac OSX 打開原生自帶讀寫NTFS功能(圖文
- 08-05織夢(mèng)dedecms什么時(shí)候用欄目交叉功能?
- 01-10delphi制作wav文件的方法
- 01-10C#中split用法實(shí)例總結(jié)
- 08-05dedecms(織夢(mèng))副欄目數(shù)量限制代碼修改