小男孩‘自慰网亚洲一区二区,亚洲一级在线播放毛片,亚洲中文字幕av每天更新,黄aⅴ永久免费无码,91成人午夜在线精品,色网站免费在线观看,亚洲欧洲wwwww在线观看

分享

select,poll,epoll實(shí)現(xiàn)分析—結(jié)合內(nèi)核源代碼

 zhouADNjj 2014-01-15

select,poll,epoll都是IO多路復(fù)用的機(jī)制。所謂I/O多路復(fù)用機(jī)制,就是說(shuō)通過(guò)一種機(jī)制,可以監(jiān)視多個(gè)描述符,一旦某個(gè)描述符就緒(一般是讀就緒或者寫(xiě)就緒),能夠通知程序進(jìn)行相應(yīng)的讀寫(xiě)操作。但select,poll,epoll本質(zhì)上都是同步I/O,因?yàn)樗麄兌夹枰谧x寫(xiě)事件就緒后自己負(fù)責(zé)進(jìn)行讀寫(xiě),也就是說(shuō)這個(gè)讀寫(xiě)過(guò)程是阻塞的,而異步I/O則無(wú)需自己負(fù)責(zé)進(jìn)行讀寫(xiě),異步I/O的實(shí)現(xiàn)會(huì)負(fù)責(zé)把數(shù)據(jù)從內(nèi)核拷貝到用戶空間。關(guān)于阻塞,非阻塞,同步,異步將在下一篇文章詳細(xì)說(shuō)明。

select和poll的實(shí)現(xiàn)比較相似,目前也有很多為人詬病的缺點(diǎn),epoll可以說(shuō)是select和poll的增強(qiáng)版。

一、select實(shí)現(xiàn)

1、使用copy_from_user從用戶空間拷貝fd_set到內(nèi)核空間

    2、注冊(cè)回調(diào)函數(shù)__pollwait

    3、遍歷所有fd,調(diào)用其對(duì)應(yīng)的poll方法(對(duì)于socket,這個(gè)poll方法是sock_poll,sock_poll根據(jù)情況會(huì)調(diào)用到tcp_poll,udp_poll或者datagram_poll)

    4、以tcp_poll為例,其核心實(shí)現(xiàn)就是__pollwait,也就是上面注冊(cè)的回調(diào)函數(shù)。

    5、__pollwait的主要工作就是把current(當(dāng)前進(jìn)程)掛到設(shè)備的等待隊(duì)列中,不同的設(shè)備有不同的等待隊(duì)列,對(duì)于tcp_poll來(lái)說(shuō),其等待隊(duì)列是sk->sk_sleep(注意把進(jìn)程掛到等待隊(duì)列中并不代表進(jìn)程已經(jīng)睡眠了)。在設(shè)備收到一條消息(網(wǎng)絡(luò)設(shè)備)或填寫(xiě)完文件數(shù)據(jù)(磁盤(pán)設(shè)備)后,會(huì)喚醒設(shè)備等待隊(duì)列上睡眠的進(jìn)程,這時(shí)current便被喚醒了。

    6、poll方法返回時(shí)會(huì)返回一個(gè)描述讀寫(xiě)操作是否就緒的mask掩碼,根據(jù)這個(gè)mask掩碼給fd_set賦值。

    7、如果遍歷完所有的fd,還沒(méi)有返回一個(gè)可讀寫(xiě)的mask掩碼,則會(huì)調(diào)用schedule_timeout是調(diào)用select的進(jìn)程(也就是current)進(jìn)入睡眠。當(dāng)設(shè)備驅(qū)動(dòng)發(fā)生自身資源可讀寫(xiě)后,會(huì)喚醒其等待隊(duì)列上睡眠的進(jìn)程。如果超過(guò)一定的超時(shí)時(shí)間(schedule_timeout指定),還是沒(méi)人喚醒,則調(diào)用select的進(jìn)程會(huì)重新被喚醒獲得CPU,進(jìn)而重新遍歷fd,判斷有沒(méi)有就緒的fd。

    8、把fd_set從內(nèi)核空間拷貝到用戶空間。

    總結(jié):

    select的幾大缺點(diǎn):

   (1)每次調(diào)用select,都需要把fd集合從用戶態(tài)拷貝到內(nèi)核態(tài),這個(gè)開(kāi)銷(xiāo)在fd很多時(shí)會(huì)很大

   (2)同時(shí)每次調(diào)用select都需要在內(nèi)核遍歷傳遞進(jìn)來(lái)的所有fd,這個(gè)開(kāi)銷(xiāo)在fd很多時(shí)也很大

   (3)select支持的文件描述符數(shù)量太小了,默認(rèn)是1024

    select源碼分析推薦看http://zhangyafeikimi./blog/248815

    二、poll實(shí)現(xiàn)

    poll的實(shí)現(xiàn)和select非常相似,只是描述fd集合的方式不同,poll使用pollfd結(jié)構(gòu)而不是select的fd_set結(jié)構(gòu)。其他的都差不多。

    三、epoll實(shí)現(xiàn)

    epoll既然是對(duì)select和poll的改進(jìn),就應(yīng)該能避免上述的三個(gè)缺點(diǎn)。那epoll都是怎么解決的呢?在此之前,我們先看一下epoll和select和poll的調(diào)用接口上的不同,select和poll都只提供了一個(gè)函數(shù)——select或者poll函數(shù)。而epoll提供了三個(gè)函數(shù),epoll_create,epoll_ctl和epoll_wait,epoll_create是創(chuàng)建一個(gè)epoll句柄;epoll_ctl是注冊(cè)要監(jiān)聽(tīng)的事件類(lèi)型;epoll_wait則是等待事件的產(chǎn)生。

    對(duì)于第一個(gè)缺點(diǎn),epoll的解決方案在epoll_ctl函數(shù)中。每次注冊(cè)新的事件到epoll句柄中時(shí)(在epoll_ctl中指定EPOLL_CTL_ADD),會(huì)把所有的fd拷貝進(jìn)內(nèi)核,而不是在epoll_wait的時(shí)候重復(fù)拷貝。epoll保證了每個(gè)fd在整個(gè)過(guò)程中只會(huì)拷貝一次。

    對(duì)于第二個(gè)缺點(diǎn),epoll的解決方案不像select或poll一樣每次都把current輪流加入fd對(duì)應(yīng)的設(shè)備等待隊(duì)列中,而只在epoll_ctl時(shí)把current掛一遍(這一遍必不可少)并為每個(gè)fd指定一個(gè)回調(diào)函數(shù),當(dāng)設(shè)備就緒,喚醒等待隊(duì)列上的等待者時(shí),就會(huì)調(diào)用這個(gè)回調(diào)函數(shù),而這個(gè)回調(diào)函數(shù)會(huì)把就緒的fd加入一個(gè)就緒鏈表)。epoll_wait的工作實(shí)際上就是在這個(gè)就緒鏈表中查看有沒(méi)有就緒的fd(利用schedule_timeout()實(shí)現(xiàn)睡一會(huì),判斷一會(huì)的效果,和select實(shí)現(xiàn)中的第7步是類(lèi)似的)。

    說(shuō)明一下這個(gè)回調(diào)機(jī)制的原理,其實(shí)很簡(jiǎn)單,看一下select和epoll在把current加入fd對(duì)應(yīng)的設(shè)備等待隊(duì)列時(shí)使用的代碼:

    select:

  1. static void __pollwait(struct file *filp, wait_queue_head_t *wait_address,  
  2.                 poll_table *p)  
  3. {  
  4.     struct poll_table_entry *entry = poll_get_entry(p);  
  5.     if (!entry)  
  6.         return;  
  7.     get_file(filp);  
  8.     entry->filp = filp;  
  9.     entry->wait_address = wait_address;  
  10.     init_waitqueue_entry(&entry->wait, current);  
  11.     add_wait_queue(wait_address, &entry->wait);  
  12. }  

    其中init_waitqueue_entry實(shí)現(xiàn)如下:

  1. static inline void init_waitqueue_entry(wait_queue_t *q, struct task_struct *p)  
  2. {  
  3.     q->flags = 0;  
  4.     q->private = p;  
  5.     q->func = default_wake_function;  
  6. }  

    上面的代碼是說(shuō)建立一個(gè)poll_table_entry結(jié)構(gòu)entry,首先把current設(shè)置為entry->wait的private成員,同時(shí)把default_wake_function設(shè)為entry->wait的func成員,然后把entry->wait鏈入到wait_address中(這個(gè)wait_address就是設(shè)備的等待隊(duì)列,在tcp_poll中就是sk_sleep)。


    再看一下epoll:

  1. /* 
  2.  * This is the callback that is used to add our wait queue to the 
  3.  * target file wakeup lists. 
  4.  */  
  5. static void ep_ptable_queue_proc(struct file *file, wait_queue_head_t *whead,  
  6.                  poll_table *pt)  
  7. {  
  8.     struct epitem *epi = ep_item_from_epqueue(pt);  
  9.     struct eppoll_entry *pwq;  
  10.   
  11.     if (epi->nwait >= 0 && (pwq = kmem_cache_alloc(pwq_cache, GFP_KERNEL))) {  
  12.         init_waitqueue_func_entry(&pwq->wait, ep_poll_callback);  
  13.         pwq->whead = whead;  
  14.         pwq->base = epi;  
  15.         add_wait_queue(whead, &pwq->wait);  
  16.         list_add_tail(&pwq->llink, &epi->pwqlist);  
  17.         epi->nwait++;  
  18.     } else {  
  19.         /* We have to signal that an error occurred */  
  20.         epi->nwait = -1;  
  21.     }  
  22. }  
其中init_waitqueue_func_entry的實(shí)現(xiàn)如下:

  1. static inline void init_waitqueue_func_entry(wait_queue_t *q,  
  2.                     wait_queue_func_t func)  
  3. {  
  4.     q->flags = 0;  
  5.     q->private = NULL;  
  6.     q->func = func;  
  7. }  
    可以看到,總體和select的實(shí)現(xiàn)是類(lèi)似的,只不過(guò)它是創(chuàng)建了一個(gè)eppoll_entry結(jié)構(gòu)pwq,只不過(guò)pwq->wait的func成員被設(shè)置成了回調(diào)函數(shù)ep_poll_callback(而不是default_wake_function,所以這里并不會(huì)有喚醒操作,而只是執(zhí)行回調(diào)函數(shù)),private成員被設(shè)置成了NULL。最后吧pwq->wait鏈入到whead中(也就是設(shè)備等待隊(duì)列中)。這樣,當(dāng)設(shè)備等待隊(duì)列中的進(jìn)程被喚醒時(shí),就會(huì)調(diào)用ep_poll_callback了。

    再梳理一下,當(dāng)epoll_wait時(shí),它會(huì)判斷就緒鏈表中有沒(méi)有就緒的fd,如果沒(méi)有,則把current進(jìn)程加入一個(gè)等待隊(duì)列(file->private_data->wq)中,并在一個(gè)while(1)循環(huán)中判斷就緒隊(duì)列是否為空,并結(jié)合schedule_timeout實(shí)現(xiàn)睡一會(huì),判斷一會(huì)的效果。如果current進(jìn)程在睡眠中,設(shè)備就緒了,就會(huì)調(diào)用回調(diào)函數(shù)。在回調(diào)函數(shù)中,會(huì)把就緒的fd放入就緒鏈表,并喚醒等待隊(duì)列(file->private_data->wq)中的current進(jìn)程,這樣epoll_wait又能繼續(xù)執(zhí)行下去了。

    對(duì)于第三個(gè)缺點(diǎn),epoll沒(méi)有這個(gè)限制,它所支持的FD上限是最大可以打開(kāi)文件的數(shù)目,這個(gè)數(shù)字一般遠(yuǎn)大于2048,舉個(gè)例子,在1GB內(nèi)存的機(jī)器上大約是10萬(wàn)左右,具體數(shù)目可以cat /proc/sys/fs/file-max察看,一般來(lái)說(shuō)這個(gè)數(shù)目和系統(tǒng)內(nèi)存關(guān)系很大。


    總結(jié):

   1、select,poll實(shí)現(xiàn)需要自己不斷輪詢(xún)所有fd集合,直到設(shè)備就緒,期間可能要睡眠和喚醒多次交替。而epoll其實(shí)也需要調(diào)用epoll_wait不斷輪詢(xún)就緒鏈表,期間也可能多次睡眠和喚醒交替,但是它是設(shè)備就緒時(shí),調(diào)用回調(diào)函數(shù),把就緒fd放入就緒鏈表中,并喚醒在epoll_wait中進(jìn)入睡眠的進(jìn)程。雖然都要睡眠和交替,但是select和poll在“醒著”的時(shí)候要遍歷整個(gè)fd集合,而epoll在“醒著”的時(shí)候只要判斷一下就緒鏈表是否為空就行了,這節(jié)省了大量的CPU時(shí)間。這就是回調(diào)機(jī)制帶來(lái)的性能提升。

   2、select,poll每次調(diào)用都要把fd集合從用戶態(tài)往內(nèi)核態(tài)拷貝一次,并且要把current往設(shè)備等待隊(duì)列中掛一次,而epoll只要一次拷貝,而且把current往等待隊(duì)列上掛也只掛一次(在epoll_wait的開(kāi)始,注意這里的等待隊(duì)列并不是設(shè)備等待隊(duì)列,只是一個(gè)epoll內(nèi)部定義的等待隊(duì)列)。這也能節(jié)省不少的開(kāi)銷(xiāo)。

   



    本站是提供個(gè)人知識(shí)管理的網(wǎng)絡(luò)存儲(chǔ)空間,所有內(nèi)容均由用戶發(fā)布,不代表本站觀點(diǎn)。請(qǐng)注意甄別內(nèi)容中的聯(lián)系方式、誘導(dǎo)購(gòu)買(mǎi)等信息,謹(jǐn)防詐騙。如發(fā)現(xiàn)有害或侵權(quán)內(nèi)容,請(qǐng)點(diǎn)擊一鍵舉報(bào)。
    轉(zhuǎn)藏 分享 獻(xiàn)花(0

    0條評(píng)論

    發(fā)表

    請(qǐng)遵守用戶 評(píng)論公約

    類(lèi)似文章 更多