數(shù)據(jù)結(jié)構(gòu)
- [include/types/fd.h]
- /* info about one given fd */
- struct fdtab {
- struct {
- int (*f)(int fd); /* read/write function */
- struct buffer *b; /* read/write buffer */
- } cb[DIR_SIZE];
- void *owner; /* the session (or proxy) associated with this fd */
- struct { /* used by pollers which support speculative polling */
- unsigned char e; /* read and write events status. 4 bits*/
- unsigned int s1; /* Position in spec list+1. 0=not in list. */
- } spec;
- unsigned short flags; /* various flags precising the exact status of this fd */
- unsigned char state; /* the state of this fd */
- unsigned char ev; /* event seen in return of poll() : FD_POLL_* */
- };
-
- struct poller {
- void *private; /* any private data for the poller */
- int REGPRM2 (*is_set)(const int fd, int dir); /* check if <fd> is being polled for dir <dir> */
- int REGPRM2 (*set)(const int fd, int dir); /* set polling on <fd> for <dir> */
- int REGPRM2 (*clr)(const int fd, int dir); /* clear polling on <fd> for <dir> */
- int REGPRM2 (*cond_s)(const int fd, int dir); * set polling on <fd> for <dir> if unset */
- int REGPRM2 (*cond_c)(const int fd, int dir); /* clear polling on <fd> for <dir> if set */
- void REGPRM1 (*rem)(const int fd); /* remove any polling on <fd> */
- void REGPRM1 (*clo)(const int fd); /* mark <fd> as closed */
- void REGPRM2 (*poll)(struct poller *p, int exp); /* the poller itself */
- int REGPRM1 (*init)(struct poller *p); /* poller initialization */
- void REGPRM1 (*term)(struct poller *p); /* termination of this poller */
- int REGPRM1 (*test)(struct poller *p); /* pre-init check of the poller */
- int REGPRM1 (*fork)(struct poller *p); /* post-fork re-opening */
- const char *name; /* poller name */
- int pref; /* try pollers with higher preference first */
- };
-
- [src/fd.c]
- struct fdtab *fdtab = NULL; /* array of all the file descriptors */
- struct fdinfo *fdinfo = NULL; /* less-often used infos for file descriptors */
- int maxfd; /* # of the highest fd + 1 */
- int totalconn; /* total # of terminated sessions */
- int actconn; /* # of active sessions */
-
- struct poller pollers[MAX_POLLERS];
- struct poller cur_poller;
- int nbpollers = 0;
在看到fdtab和poller的結(jié)構(gòu)體時,然后查看ev_epoll.c的時候可能會奇怪為什么會設(shè)置成這樣。但是如果先查看ev_sepoll.c的話可能很多疑惑都沒有了。
sepoll
在Haproxy中,作者在epoll上將模型推進至sepoll(我不知道是否在此之前就有人提出或者使用這種方法),從理論上來說,這種模型的總體效率應(yīng)該比epoll更好,雖然說它是基于epoll的,因為它能夠減少較多與epoll相關(guān)的昂貴的系統(tǒng)調(diào)用。
sepoll,作者在代碼注釋中稱為speculative I/O。Sepoll的原理就是,對于剛accept完的套接字描述符,一般都是直接能夠讀取導(dǎo)數(shù)據(jù)的;對于connect完的描述符,一般都是可寫的;即使是對于在傳輸數(shù)據(jù)的鏈接,它也是能提升效率的,因為假設(shè)對于某一條鏈接的某端已經(jīng)處于epoll的等待隊列中,那么另一端也是需要做出反應(yīng)的,要么發(fā)送數(shù)據(jù),要么接收數(shù)據(jù),這依賴于(讀/寫)緩沖區(qū)的水位。
當然,作者也描述了sepoll的缺點,那就是這可能會導(dǎo)致在epoll隊列中的可用事件缺少而變得饑餓(starve the polled events)(我對此處饑餓的理解是,有足夠資源的時候不給予需要的人;poll本來就是用于處理多個描述符專用,假設(shè)只處理幾個描述符,那么poll根本就提升不了多少性能,因為它本身也是系統(tǒng)調(diào)用,因此需要保持poll隊列含有一定數(shù)量的fd,否則就是出現(xiàn)饑餓情況),作者說實驗證明,當epoll隊列出現(xiàn)饑餓的情況時,壓力會轉(zhuǎn)到spec I/O上面,此時由于每次去讀取或者寫入,但是都失敗,陷入惡性循環(huán),會嚴重的降低系統(tǒng)性能(spec list描述符較多,一直輪詢肯定會導(dǎo)致性能問題)。用于解決此問題的方法,可以通過減少epoll一次處理的事件來解決這個問題(對spec list的不能使用這個方法,因為實驗顯示,spec list中2/3的fd是新的,只有1/3的fd是老的)。作者說這是基于以下兩點事實,第一,對于位于spec list的fd,不能也將它們注冊在epoll中等待;第二是,即使在系統(tǒng)壓力非常大的時候,我們基本上也不會同時對同一個fd進行讀與寫的流操作。作者所說的后面一個事實我認為是這樣的,對于客戶端,一個請求都是將請求數(shù)據(jù)發(fā)送完成之后,后端才會對其進行響應(yīng);對于服務(wù)器,都是接收玩請求之后,才會發(fā)回響應(yīng)數(shù)據(jù)。
作者說第一個事實意味著在饑餓期間,poll等待隊列中不會有超過一半的fd。否則的話,說明spec list中的fd比poll list少,那么也就沒有饑餓情況。第二個事實意味著我們只對最大數(shù)量描述符的一半事件感興趣(每個描述符要么讀,要么寫)。
減少poll list一次處理的數(shù)量用于解決poll list饑餓的情況,可以這么理解,假設(shè)每個fd經(jīng)過一次讀和一次寫之后就被銷毀,那么對于第二個事實,在進行讀的時候,poll list的fd不會減少,影響不大,但是在寫的時候,由于讀與寫都已經(jīng)完成了,那么可能這一次會導(dǎo)致大量的fd被移除,而補充又跟不上,這就可能會導(dǎo)致饑餓;但是由于第一個事實限制每次可處理的最大數(shù)量,那么一次讀寫完成被撤掉的fd數(shù)量就減少了,而且把poll list中的fd分成了兩部分,錯開了它們移出poll list的時間,減少了一次被移除的fd數(shù)量,那么就應(yīng)該能夠使后續(xù)的fd補充跟上。
那么對于fd本來就不多,導(dǎo)致poll list分配到的很少導(dǎo)致的饑餓怎么辦?此時由于fd不多,spec list的fd也不多,,對性能的影響不是很大,基本上忽略了。
作者最后說明,如果我們能夠在負載高峰時段保證poll list擁有maxsock/2/2數(shù)量的事件,這意味著我們應(yīng)該給poll list分配maxsock/4的事件,就不會受饑餓的影響。Maxsock/2/2來源作者沒有明確說明,不過從上面的的解釋來看,第一除2應(yīng)該是表示如果poll list如果有不小于maxsock/2的fd,那么就不會受饑餓的影響;第二個除2暫時還不能確定,假如是根據(jù)第二個事實來的,那也不是很合理,因為一個sock肯定包含兩個事件,一次處理只做一個事件的話,那么時間數(shù)量也是和sock數(shù)量本身一樣的。
接下來看看sepoll的處理流程。
- [src/ev_sepoll.c]
- #define FD_EV_IN_SL 1
- #define FD_EV_IN_PL 4
-
- #define FD_EV_IDLE 0
- #define FD_EV_SPEC (FD_EV_IN_SL)
- #define FD_EV_WAIT (FD_EV_IN_PL)
- #define FD_EV_STOP (FD_EV_IN_SL|FD_EV_IN_PL)
-
- /* Those match any of R or W for Spec list or Poll list */
- #define FD_EV_RW_SL (FD_EV_IN_SL | (FD_EV_IN_SL << 1))
- #define FD_EV_RW_PL (FD_EV_IN_PL | (FD_EV_IN_PL << 1))
- #define FD_EV_MASK_DIR (FD_EV_IN_SL|FD_EV_IN_PL)
-
- #define FD_EV_IDLE_R 0
- #define FD_EV_SPEC_R (FD_EV_IN_SL)
- #define FD_EV_WAIT_R (FD_EV_IN_PL)
- #define FD_EV_STOP_R (FD_EV_IN_SL|FD_EV_IN_PL)
- #define FD_EV_MASK_R (FD_EV_IN_SL|FD_EV_IN_PL)
-
- #define FD_EV_IDLE_W (FD_EV_IDLE_R << 1)
- #define FD_EV_SPEC_W (FD_EV_SPEC_R << 1)
- #define FD_EV_WAIT_W (FD_EV_WAIT_R << 1)
- #define FD_EV_STOP_W (FD_EV_STOP_R << 1)
- #define FD_EV_MASK_W (FD_EV_MASK_R << 1)
-
- #define FD_EV_MASK (FD_EV_MASK_W | FD_EV_MASK_R)
從以上宏定義可以看出,對于位于spec list的讀寫事件分別對應(yīng)的最低兩位;對于位于poll list的讀寫事件位于第三、四位。
- [src/ev_sepoll.c]_do_poll()
- REGPRM2 static void _do_poll(struct poller *p, int exp)
- {
- static unsigned int last_skipped;
- static unsigned int spec_processed;
- int status, eo;
- int fd, opcode;
- int count;
- int spec_idx;
- int wait_time;
- int looping = 0;
-
- re_poll_once:
- /* Here we have two options :
- * - either walk the list forwards and hope to match more events
- * - or walk it backwards to minimize the number of changes and
- * to make better use of the cache.
- * Tests have shown that walking backwards improves perf by 0.2%.
- */
首先處理的是位于spec list的fd,作者說從后面遍歷spec list能夠提高0.2%的效率,這是因為spec list總是把最新的fd存儲在最后,而對于最新的fd,基本上很可能是直接可讀或者可寫的。
- [src/ev_sepoll.c]
- status = 0;
- spec_idx = nbspec;
- while (likely(spec_idx > 0)) {
- int done;
-
- spec_idx--;
- fd = spec_list[spec_idx];
- eo = fdtab[fd].spec.e; /* save old events */
-
- if (looping && --fd_created < 0) {
- /* we were just checking the newly created FDs */
- break;
- }
拿到fd,然后根據(jù)fd從fdtab中拿到對應(yīng)的信息。如果這是第二次處理循環(huán),只是為了檢查由于listen fd進行accept之后新創(chuàng)建的fd,因此作者專門使用一個變量fd_created用于記錄新創(chuàng)建的fd數(shù)量,當新的fd處理完成之后,直接跳出循環(huán)了。
- [src/ev_sepoll.c]_do_poll()
- /*
- * Process the speculative events.
- *
- * Principle: events which are marked FD_EV_SPEC are processed
- * with their assigned function. If the function returns 0, it
- * means there is nothing doable without polling first. We will
- * then convert the event to a pollable one by assigning them
- * the WAIT status.
- */
作者說明規(guī)則是處理標志了FD_EV_SPEC事件的,并且調(diào)用他們指定的函數(shù),如果函數(shù)返回0,那么表示現(xiàn)在沒有任何事可做,我們應(yīng)該先對其進行一個poll等待先。
- [src/ev_sepoll.c]_do_poll()
- #ifdef DEBUG_DEV
- if (fdtab[fd].state == FD_STCLOSE) {
- fprintf(stderr,"fd=%d, fdtab[].ev=%x, fdtab[].spec.e=%x, .s=%d, idx=%d\n",
- fd, fdtab[fd].ev, fdtab[fd].spec.e, fdtab[fd].spec.s1, spec_idx);
- }
- #endif
- done = 0;
- fdtab[fd].ev &= FD_POLL_STICKY;
- if ((eo & FD_EV_MASK_R) == FD_EV_SPEC_R) {
- /* The owner is interested in reading from this FD */
- if (fdtab[fd].state != FD_STERROR) {
- /* Pretend there is something to read */
- fdtab[fd].ev |= FD_POLL_IN;
- if (!fdtab[fd].cb[DIR_RD].f(fd))
- fdtab[fd].spec.e ^= (FD_EV_WAIT_R ^ FD_EV_SPEC_R);
- else
- done = 1;
- }
- }
- else if ((eo & FD_EV_MASK_R) == FD_EV_STOP_R) {
- /* This FD was being polled and is now being removed. */
- fdtab[fd].spec.e &= ~FD_EV_MASK_R;
- }
-
- if ((eo & FD_EV_MASK_W) == FD_EV_SPEC_W) {
- /* The owner is interested in writing to this FD */
- if (fdtab[fd].state != FD_STERROR) {
- /* Pretend there is something to write */
- fdtab[fd].ev |= FD_POLL_OUT;
- if (!fdtab[fd].cb[DIR_WR].f(fd))
- fdtab[fd].spec.e ^= (FD_EV_WAIT_W ^ FD_EV_SPEC_W);
- else
- done = 1;
- }
- }
- else if ((eo & FD_EV_MASK_W) == FD_EV_STOP_W) {
- /* This FD was being polled and is now being removed. */
- fdtab[fd].spec.e &= ~FD_EV_MASK_W;
- }
對于位于spec fd的讀事件,當函數(shù)返回0時,去掉FD_EV_SPEC_R事件,轉(zhuǎn)為FD_EV_SPEC_WAIT_R事件,表示這個描述符應(yīng)該放入poll等待隊列。函數(shù)返回不為0,那么表示此次spec處理時成功的,那么依然將其留在spec隊列中,記錄成功標志。在處理相應(yīng)事件的時候還用fdtab[fd].ev記錄下了相應(yīng)fd被處理的事件。
對于被標志為停止了的fd,那么將其相應(yīng)的讀事件全部清空。
寫事件的處理與讀事件的處理相同。
- [src/ev_sepoll.c]_do_poll()
- status += done;
- /* one callback might already have closed the fd by itself */
- if (fdtab[fd].state == FD_STCLOSE)
- continue;
前面只要讀或者寫成功,那么表示此次的spec處理是成功的,因此對其進行數(shù)量統(tǒng)計,當然有可能對應(yīng)的fd在其相應(yīng)的讀或者寫函數(shù)中已經(jīng)關(guān)閉,那么以下的事情就沒必要做了。
- [src/ev_sepoll.c]_do_poll()
- /* Now, we will adjust the event in the poll list. Indeed, it
- * is possible that an event which was previously in the poll
- * list now goes out, and the opposite is possible too. We can
- * have opposite changes for READ and WRITE too.
- */
- if ((eo ^ fdtab[fd].spec.e) & FD_EV_RW_PL) {
- /* poll status changed*/
- if ((fdtab[fd].spec.e & FD_EV_RW_PL) == 0) {
- /* fd removed from poll list */
- opcode = EPOLL_CTL_DEL;
- }
- else if ((eo & FD_EV_RW_PL) == 0) {
- /* new fd in the poll list */
- opcode = EPOLL_CTL_ADD;
- }
- else {
- /* fd status changed */
- opcode = EPOLL_CTL_MOD;
- }
-
- /* construct the epoll events based on new state */
- ev.events = 0;
- if (fdtab[fd].spec.e & FD_EV_WAIT_R)
- ev.events |= EPOLLIN;
-
- if (fdtab[fd].spec.e & FD_EV_WAIT_W)
- ev.events |= EPOLLOUT;
-
- ev.data.fd = fd;
- epoll_ctl(epoll_fd, opcode, fd, &ev);
- }
對于此處的表達式結(jié)果,結(jié)合以上三種情況即可知道其結(jié)果。首先是對于done的情況,此時o^fdtab[fd].spec.e==0,所以不會進入分支;接著是對于函數(shù)返回值為0的情況,這種情況下,FD_EV_SPEC的事件被清除,FD_EV_POLL的事件被設(shè)置,因此結(jié)果為不為0,會進入分支,進入分支后,易知內(nèi)部分支會進入第二分支,也就是將fd加到epoll中;第三種是FD_EV_STOP類型導(dǎo)致事件被清空,計算結(jié)果不為0,進入分支,由于spec.e被清零,因此進入第一個分支,也就是從epoll list中移除fd。
在進行操作判斷之后,然后對poll list的fd進行相應(yīng)的操作。
- [src/ev_sepoll.c]_do_poll()
- if (!(fdtab[fd].spec.e & FD_EV_RW_SL)) {
- /* This fd switched to combinations of either WAIT or
- * IDLE. It must be removed from the spec list.
- */
- release_spec_entry(fd);
- continue;
- }
- }
在對poll list更新之后,還需要檢查fd新的事件中是否已經(jīng)不再包含spec的事件,如果是,那么需要將fd從fdtab中移除。至此spec的循環(huán)處理已經(jīng)結(jié)束。
總結(jié)一下上面的流程。從后往前遍歷spec list,根據(jù)對fd有興趣的事件調(diào)用相應(yīng)函數(shù)進行數(shù)據(jù)的輸入和輸出(所有的fd都是非阻塞形式的),如果調(diào)用成功,那么相應(yīng)的fd仍然保留于spec list中,并統(tǒng)計在spec中成功處理的fd數(shù)量;若失敗,那么需要將其放入poll list去等待,因為在等待數(shù)據(jù)到來之前在spec list中并不能做什么;如果描述符已經(jīng)被停止使用,那么將會從poll list或者spec list中移除。
- [src/ev_sepoll.c]_do_poll()
- /* It may make sense to immediately return here if there are enough
- * processed events, without passing through epoll_wait() because we
- * have exactly done a poll.
- * Measures have shown a great performance increase if we call the
- * epoll_wait() only the second time after speculative accesses have
- * succeeded. This reduces the number of unsucessful calls to
- * epoll_wait() by a factor of about 3, and the total number of calls
- * by about 2.
- * However, when we do that after having processed too many events,
- * events waiting in epoll() starve for too long a time and tend to
- * become themselves eligible for speculative polling. So we try to
- * limit this practise to reasonable situations.
- */
-
- spec_processed += status;
-
- if (looping) {
- last_skipped++;
- return;
- }
-
- if (status >= MIN_RETURN_EVENTS && spec_processed < absmaxevents) {
- /* We have processed at least MIN_RETURN_EVENTS, it's worth
- * returning now without checking epoll_wait().
- */
- if (++last_skipped <= 1) {
- tv_update_date(0, 1);
- return;
- }
- }
- last_skipped = 0;
如果是第二次處理,也就是再回來處理新建的fd,那將last_skipped++并返回,這是為什么呢?因為之前作者描述過,一次對poll隊列處理的數(shù)量減少點,既然要減少,之前做過一次了,那么這次就不再檢查了。
last_skipped是用來標志當處理數(shù)量符合最小可返回數(shù)量時是否返回,如果本次返回是由于第二次處理而導(dǎo)致返回,那么下次出現(xiàn)處理數(shù)量達到最小可返回數(shù)量時不再返回。
如果spec處理成功的數(shù)量超過最小可以返回的數(shù)量并且spec_proc處理的數(shù)量不超過poll list最大的事件數(shù),那么要是之前沒設(shè)置跳過標志則返回。
第一次__do_poll循環(huán),并且已處理數(shù)量不足以返回,那么將下次跳過標志清空。
以下流程除了最后的判斷是否新建了fd而決定是否跳轉(zhuǎn)到__do_poll開始再做一次spec處理之外,其他的流程和其他的I/O模型基本上是一致,因此對于其他的I/O模型不再解釋。
- [src/ev_sepoll.c]_do_poll()
- if (nbspec || status || run_queue || signal_queue_len) {
- /* Maybe we have processed some events that we must report, or
- * maybe we still have events in the spec list, or there are
- * some tasks left pending in the run_queue, so we must not
- * wait in epoll() otherwise we will delay their delivery by
- * the next timeout.
- */
- wait_time = 0;
- }
- else {
- if (!exp)
- wait_time = MAX_DELAY_MS;
- else if (tick_is_expired(exp, now_ms))
- wait_time = 0;
- else {
- wait_time = TICKS_TO_MS(tick_remain(now_ms, exp)) + 1;
- if (wait_time > MAX_DELAY_MS)
- wait_time = MAX_DELAY_MS;
- }
- }
要是之前的處理沒有能夠返回,那么接下來就需要真正的對epoll進行處理了,但是在處理之前則需要計算epoll_wait調(diào)用應(yīng)該等待的時間。
如果spec隊列還有fd(事件)存在,或者是spec已經(jīng)有處理成功需要回去報告,或者是任務(wù)可執(zhí)行隊列有需要執(zhí)行的任務(wù),或者是信號隊列有未決信號需要處理,那么對于epoll_wait的操作使用無阻塞的。
如果沒有設(shè)置超時時間,那么將等待時間設(shè)置為程序允許的最大值。
如果給定的超時時間已經(jīng)到期,那么對epoll_wait的調(diào)用也是無阻塞。
如果給定的超時時間還沒到,那么計算余下的時間,如果余下的時間比程序允許的最大值還大那么將其設(shè)置為程序允許的最大值。
- [src/ev_sepoll.c]_do_poll()
- /* now let's wait for real events. We normally use maxpollevents as a
- * high limit, unless <nbspec> is already big, in which case we need
- * to compensate for the high number of events processed there.
- */
- fd = MIN(absmaxevents, spec_processed);
- fd = MAX(global.tune.maxpollevents, fd);
- fd = MIN(maxfd, fd);
- /* we want to detect if an accept() will create new speculative FDs here */
- //從此處可以看出,listen fd是放在epoll等待隊列中的。
- fd_created = 0;
- spec_processed = 0;
- status = epoll_wait(epoll_fd, epoll_events, fd, wait_time);
- tv_update_date(wait_time, status);
對于wait使用的數(shù)量,作者說明一般是使用maxpollevents作為限制的,除非spec list已經(jīng)非常大了,那么才需要對其大處理量進行補償。
epoll_wait返回之后需要對時間進行更新,如果是超時返回,那么需要將等待時間加上,否則根據(jù)返回值適當調(diào)整。
- [src/ev_sepoll.c]_do_poll()
- for (count = 0; count < status; count++) {
- int e = epoll_events[count].events;
- fd = epoll_events[count].data.fd;
-
- /* it looks complicated but gcc can optimize it away when constants
- * have same values.
- */
- DPRINTF(stderr, "%s:%d: fd=%d, ev=0x%08x, e=0x%08x\n",
- __FUNCTION__, __LINE__,
- fd, fdtab[fd].ev, e);
-
- fdtab[fd].ev &= FD_POLL_STICKY;
- fdtab[fd].ev |=
- ((e & EPOLLIN ) ? FD_POLL_IN : 0) |
- ((e & EPOLLPRI) ? FD_POLL_PRI : 0) |
- ((e & EPOLLOUT) ? FD_POLL_OUT : 0) |
- ((e & EPOLLERR) ? FD_POLL_ERR : 0) |
- ((e & EPOLLHUP) ? FD_POLL_HUP : 0);
-
- if ((fdtab[fd].spec.e & FD_EV_MASK_R) == FD_EV_WAIT_R) {
- if (fdtab[fd].state == FD_STCLOSE || fdtab[fd].state == FD_STERROR)
- continue;
- if (fdtab[fd].ev & (FD_POLL_IN|FD_POLL_HUP|FD_POLL_ERR))
- fdtab[fd].cb[DIR_RD].f(fd);
- }
-
- if ((fdtab[fd].spec.e & FD_EV_MASK_W) == FD_EV_WAIT_W) {
- if (fdtab[fd].state == FD_STCLOSE || fdtab[fd].state == FD_STERROR)
- continue;
- if (fdtab[fd].ev & (FD_POLL_OUT|FD_POLL_ERR))
- fdtab[fd].cb[DIR_WR].f(fd);
- }
- }
-
- if (fd_created) {
- /* we have created some fds, certainly in return of an accept(),
- * and they're marked as speculative. If we can manage to perform
- * a read(), we're almost sure to collect all the request at once
- * and avoid several expensive wakeups. So let's try now. Anyway,
- * if we fail, the tasks are still woken up, and the FD gets marked
- * for poll mode.
- */
-
- looping = 1;
- goto re_poll_once;
- }
- }
與spec list的處理一樣,對于出現(xiàn)的事件會在fdtab[fd].ev中保存下來。
對epoll的相應(yīng)事件處理完成之后。因為讀事件中包括accept,因此可能創(chuàng)建了新的鏈接。如果創(chuàng)建了新的fd,那么可以轉(zhuǎn)回去直接用spec對他們進行處理,這第二次輪詢只處理新建的fd。
在epoll_wait調(diào)用之前將fd_created置為了0,那么是什么地方對其進行更改呢?是在poller的fd_set函數(shù)中,fd_set函數(shù)會在event_accept()函數(shù)中調(diào)用。后者源代碼位于src/client.c中。
Poller的初始化
之前看完了poller的處理流程,那么看看poller是如何初始化的。
- [src/fd.c]
- int init_pollers()
- {
- int p;
- struct poller *bp;
-
-
- do {
- bp = NULL;
- for (p = 0; p < nbpollers; p++)
- if (!bp || (pollers[p].pref > bp->pref))
- bp = &pollers[p];
-
- if (!bp || bp->pref == 0)
- break;
-
- if (bp->init(bp)) {
- memcpy(&cur_poller, bp, sizeof(*bp));
- return 1;
- }
- } while (!bp || bp->pref == 0);
- return 0;
- }
很簡單的代碼,僅僅是遍歷poller全局數(shù)組pollers來查找pref值最大的一個poller,并將其設(shè)置為cur_poller。
那么pollers的值如何來的呢?通過查看ev_*.c的代碼可知,每一個文件均有如下函數(shù),
- __attribute__((constructor))
- static void _do_register(void)
- {
- ...
- }
這是使用了GCC的特性。GCC編譯之后的代碼將會在main函數(shù)運行之前將帶有此特性的函數(shù)先運行。因此,pollers數(shù)組就是通過每個I/O模型的_do_register函數(shù)來初始化的。
|