日韩av黄I国产麻豆传媒I国产91av视频在线观看I日韩一区二区三区在线看I美女国产在线I麻豆视频国产在线观看I成人黄色短片

歡迎訪問 生活随笔!

生活随笔

當(dāng)前位置: 首頁(yè) > 编程资源 > 编程问答 >内容正文

编程问答

Epoll详解及源码分析

發(fā)布時(shí)間:2023/11/30 编程问答 57 豆豆
生活随笔 收集整理的這篇文章主要介紹了 Epoll详解及源码分析 小編覺得挺不錯(cuò)的,現(xiàn)在分享給大家,幫大家做個(gè)參考.

文章來源:http://blog.csdn.net/chen19870707/article/details/42525887

  • Author:Echo Chen(陳斌)

  • Email:chenb19870707@gmail.com

  • Blog:Blog.csdn.net/chen19870707

  • Date:Jan.7th, 2015

?

1.什么是epoll

epoll是當(dāng)前在Linux下開發(fā)大規(guī)模并發(fā)網(wǎng)絡(luò)程序的熱門人選,epoll 在Linux2.6內(nèi)核中正式引入,和select相似,都是I/O多路復(fù)用(IO multiplexing)技術(shù),按照man手冊(cè)的說法:是為處理大批量句柄而作了改進(jìn)的poll。

Linux下有以下幾個(gè)經(jīng)典的服務(wù)器模型:

?

①Apache模型(Process Per Connection,簡(jiǎn)稱PPC) 和 TPC(Thread Per Connection)模型

這兩種模型思想類似,就是讓每一個(gè)到來的連接都有一個(gè)進(jìn)程/線程來服務(wù)。這種模型的代價(jià)是它要時(shí)間和空間。連接較多時(shí),進(jìn)程/線程切換的開銷比較大。因此這類模型能接受的最大連接數(shù)都不會(huì)高,一般在幾百個(gè)左右。

?

②select模型

最大并發(fā)數(shù)限制:因?yàn)橐粋€(gè)進(jìn)程所打開的fd(文件描述符)是有限制的,由FD_SETSIZE設(shè)置,默認(rèn)值是1024/2048,因此select模型的最大并發(fā)數(shù)就被相應(yīng)限制了。

效率問題:select每次調(diào)用都會(huì)線性掃描全部的fd集合,這樣效率就會(huì)呈現(xiàn)線性下降,把FD_SETSIZE改大可能造成這些fd都超時(shí)了。

內(nèi)核/用戶空間內(nèi)存拷貝問題:如何讓內(nèi)核把fd消息通知給用戶空間呢?在這個(gè)問題上select采取了內(nèi)存拷貝方法。?

?

③poll模型

雖然解決了select 最大并發(fā)數(shù)的限制,但是依然存在select的效率問題,select缺點(diǎn)的2和3它都沒有改掉。

?

④epoll模型

對(duì)比其他模型的問題,epoll的改進(jìn)如下:

1.支持一個(gè)進(jìn)程打開大數(shù)目的socket描述符(FD)?
??? select 最不能忍受的是一個(gè)進(jìn)程所打開的FD是有一定限制的,由FD_SETSIZE設(shè)置,默認(rèn)值是2048。對(duì)于那些需要支持的上萬連接數(shù)目的IM服務(wù)器來說顯然太少了。這時(shí)候你一是可以選擇修改這個(gè)宏然后重新編譯內(nèi)核,不過資料也同時(shí)指出這樣會(huì)帶來網(wǎng)絡(luò)效率的下降,二是可以選擇多進(jìn)程的解決方案(傳統(tǒng)的 Apache方案),不過雖然linux上面創(chuàng)建進(jìn)程的代價(jià)比較小,但仍舊是不可忽視的,加上進(jìn)程間數(shù)據(jù)同步遠(yuǎn)比不上線程間同步的高效,所以也不是一種完美的方案。不過 epoll則沒有這個(gè)限制,它所支持的FD上限是最大可以打開文件的數(shù)目,這個(gè)數(shù)字一般遠(yuǎn)大于2048,舉個(gè)例子,在1GB內(nèi)存的機(jī)器上大約是10萬左右,具體數(shù)目可以cat /proc/sys/fs/file-max察看,一般來說這個(gè)數(shù)目和系統(tǒng)內(nèi)存關(guān)系很大。?
??
???? 2.IO效率不隨FD數(shù)目增加而線性下降?
??? 傳統(tǒng)的select/poll另一個(gè)致命弱點(diǎn)就是當(dāng)你擁有一個(gè)很大的socket集合,不過由于網(wǎng)絡(luò)延時(shí),任一時(shí)間只有部分的socket是"活躍"的,但是select/poll每次調(diào)用都會(huì)線性掃描全部的集合,導(dǎo)致效率呈現(xiàn)線性下降。但是epoll不存在這個(gè)問題,它只會(huì)對(duì)"活躍"的socket進(jìn)行操作---這是因?yàn)樵趦?nèi)核實(shí)現(xiàn)中epoll是根據(jù)每個(gè)fd上面的callback函數(shù)實(shí)現(xiàn)的。那么,只有"活躍"的socket才會(huì)主動(dòng)的去調(diào)用 callback函數(shù),其他idle狀態(tài)socket則不會(huì),在這點(diǎn)上,epoll實(shí)現(xiàn)了一個(gè)"偽"AIO,因?yàn)檫@時(shí)候推動(dòng)力在os內(nèi)核。在一些 benchmark中,如果所有的socket基本上都是活躍的---比如一個(gè)高速LAN環(huán)境,epoll并不比select/poll有什么效率,相反,如果過多使用epoll_ctl,效率相比還有稍微的下降。但是一旦使用idle connections模擬WAN環(huán)境,epoll的效率就遠(yuǎn)在select/poll之上了。
??
3.使用mmap加速內(nèi)核與用戶空間的消息傳遞?
??? 這點(diǎn)實(shí)際上涉及到epoll的具體實(shí)現(xiàn)了。無論是select,poll還是epoll都需要內(nèi)核把FD消息通知給用戶空間,如何避免不必要的內(nèi)存拷貝就很重要,在這點(diǎn)上,epoll是通過內(nèi)核于用戶空間mmap同一塊內(nèi)存實(shí)現(xiàn)的。而如果你想我一樣從2.5內(nèi)核就關(guān)注epoll的話,一定不會(huì)忘記手工 mmap這一步的。
??
4.內(nèi)核微調(diào)?
????? 這一點(diǎn)其實(shí)不算epoll的優(yōu)點(diǎn)了,而是整個(gè)linux平臺(tái)的優(yōu)點(diǎn)。也許你可以懷疑linux平臺(tái),但是你無法回避linux平臺(tái)賦予你微調(diào)內(nèi)核的能力。比如,內(nèi)核TCP/IP協(xié)議棧使用內(nèi)存池管理sk_buff結(jié)構(gòu),那么可以在運(yùn)行時(shí)期動(dòng)態(tài)調(diào)整這個(gè)內(nèi)存pool(skb_head_pool)的大小--- 通過echo XXXX>/proc/sys/net/core/hot_list_length完成。再比如listen函數(shù)的第2個(gè)參數(shù)(TCP完成3次握手的數(shù)據(jù)包隊(duì)列長(zhǎng)度),也可以根據(jù)你平臺(tái)內(nèi)存大小動(dòng)態(tài)調(diào)整。更甚至在一個(gè)數(shù)據(jù)包面數(shù)目巨大但同時(shí)每個(gè)數(shù)據(jù)包本身大小卻很小的特殊系統(tǒng)上嘗試最新的NAPI網(wǎng)卡驅(qū)動(dòng)架構(gòu)。
?

2.Epoll API?

epoll只有epoll_create,epoll_ctl,epoll_wait 3個(gè)系統(tǒng)調(diào)用。

1: #include? <sys/epoll.h> 2:? 3: int? epoll_create(int? size); 4:? 5: int? epoll_ctl(int epfd, int op, int fd, structepoll_event *event); 6:? 7: int? epoll_wait(int epfd, struct epoll_event* events, int maxevents. int timeout); 8:? 9:?

① int epoll_create(int size);

創(chuàng)建一個(gè)epoll的句柄。自從linux2.6.8之后,size參數(shù)是被忽略的。需要注意的是,當(dāng)創(chuàng)建好epoll句柄后,它就是會(huì)占用一個(gè)fd值,在linux下如果查看/proc/進(jìn)程id/fd/,是能夠看到這個(gè)fd的,所以在使用完epoll后,必須調(diào)用close()關(guān)閉,否則可能導(dǎo)致fd被耗盡。

②int epoll_ctl(int epfd, int op, int fd, struct epoll_event *event);

epoll的事件注冊(cè)函數(shù),它不同于select()是在監(jiān)聽事件時(shí)告訴內(nèi)核要監(jiān)聽什么類型的事件,而是在這里先注冊(cè)要監(jiān)聽的事件類型。?
第一個(gè)參數(shù)是epoll_create()的返回值。?
第二個(gè)參數(shù)表示動(dòng)作,用三個(gè)宏來表示:?
EPOLL_CTL_ADD:注冊(cè)新的fd到epfd中;?
EPOLL_CTL_MOD:修改已經(jīng)注冊(cè)的fd的監(jiān)聽事件;?
EPOLL_CTL_DEL:從epfd中刪除一個(gè)fd;?
??
第三個(gè)參數(shù)是需要監(jiān)聽的fd。?
第四個(gè)參數(shù)是告訴內(nèi)核需要監(jiān)聽什么事,struct epoll_event結(jié)構(gòu)如下:

1: //保存觸發(fā)事件的某個(gè)文件描述符相關(guān)的數(shù)據(jù)(與具體使用方式有關(guān)) 2:? 3: typedef?union epoll_data { 4:???? void *ptr; 5:???? int fd; 6:???? __uint32_t u32; 7:???? __uint64_t u64; 8: } epoll_data_t; 9:? //感興趣的事件和被觸發(fā)的事件 10: struct epoll_event { 11:???? __uint32_t events; /* Epoll events */ 12:???? epoll_data_t data; /* User data variable */ 13: };

events可以是以下幾個(gè)宏的集合:?
EPOLLIN :表示對(duì)應(yīng)的文件描述符可以讀(包括對(duì)端SOCKET正常關(guān)閉);?
EPOLLOUT:表示對(duì)應(yīng)的文件描述符可以寫;?
EPOLLPRI:表示對(duì)應(yīng)的文件描述符有緊急的數(shù)據(jù)可讀(這里應(yīng)該表示有帶外數(shù)據(jù)到來);?
EPOLLERR:表示對(duì)應(yīng)的文件描述符發(fā)生錯(cuò)誤;?
EPOLLHUP:表示對(duì)應(yīng)的文件描述符被掛斷;?
EPOLLET: 將EPOLL設(shè)為邊緣觸發(fā)(Edge Triggered)模式,這是相對(duì)于水平觸發(fā)(Level Triggered)來說的。?
EPOLLONESHOT:只監(jiān)聽一次事件,當(dāng)監(jiān)聽完這次事件之后,如果還需要繼續(xù)監(jiān)聽這個(gè)socket的話,需要再次把這個(gè)socket加入到EPOLL隊(duì)列里

③ int epoll_wait(int epfd, struct epoll_event * events, int maxevents, int timeout);

收集在epoll監(jiān)控的事件中已經(jīng)發(fā)送的事件。參數(shù)events是分配好的epoll_event結(jié)構(gòu)體數(shù)組,epoll將會(huì)把發(fā)生的事件賦值到events數(shù)組中(events不可以是空指針,內(nèi)核只負(fù)責(zé)把數(shù)據(jù)復(fù)制到這個(gè)events數(shù)組中,不會(huì)去幫助我們?cè)谟脩魬B(tài)中分配內(nèi)存)。maxevents告之內(nèi)核這個(gè)events有多大,這個(gè) maxevents的值不能大于創(chuàng)建epoll_create()時(shí)的size,參數(shù)timeout是超時(shí)時(shí)間(毫秒,0會(huì)立即返回,-1將不確定,也有說法說是永久阻塞)。如果函數(shù)調(diào)用成功,返回對(duì)應(yīng)I/O上已準(zhǔn)備好的文件描述符數(shù)目,如返回0表示已超時(shí)。

3.Epoll? 工作模式

①LT模式:Level Triggered水平觸發(fā)

這個(gè)是缺省的工作模式。同時(shí)支持block socket和non-block socket。內(nèi)核會(huì)告訴程序員一個(gè)文件描述符是否就緒了。如果程序員不作任何操作,內(nèi)核仍會(huì)通知。

?

②ET模式:Edge Triggered 邊緣觸發(fā)

是一種高速模式。僅當(dāng)狀態(tài)發(fā)生變化的時(shí)候才獲得通知。這種模式假定程序員在收到一次通知后能夠完整地處理事件,于是內(nèi)核不再通知這一事件。注意:緩沖區(qū)中還有未處理的數(shù)據(jù)不算狀態(tài)變化,所以ET模式下程序員只讀取了一部分?jǐn)?shù)據(jù)就再也得不到通知了,正確的用法是程序員自己確認(rèn)讀完了所有的字節(jié)(一直調(diào)用read/write直到出錯(cuò)EAGAIN為止)。

?

如下圖:

0:表示文件描述符未準(zhǔn)備就緒

1:表示文件描述符準(zhǔn)備就緒

?

對(duì)于水平觸發(fā)模式(LT):在1處,如果你不做任何操作,內(nèi)核依舊會(huì)不斷的通知進(jìn)程文件描述符準(zhǔn)備就緒。

對(duì)于邊緣出發(fā)模式(ET): 只有在0變化到1處的時(shí)候,內(nèi)核才會(huì)通知進(jìn)程文件描述符準(zhǔn)備就緒。之后如果不在發(fā)生文件描述符狀態(tài)變化,內(nèi)核就不會(huì)再通知進(jìn)程文件描述符已準(zhǔn)備就緒。

?

Nginx 默認(rèn)采用的就是ET。

?

?

4.實(shí)例

?

1: #include <stdio.h> 2: #include <stdlib.h> 3: #include <unistd.h> 4: #include <sys/socket.h> 5: #include <errno.h> 6: #include <sys/epoll.h> 7: #include <netinet/in.h> 8: #include <fcntl.h> 9: #include <string.h> 10:? #include <netdb.h> 11:? 12:? 13:? 14: struct epoll_event? *events = NULL; 15: int epollFd = -1; 16:? 17: const?int MAX_SOCK_NUM = 1024; 18:? 19:? 20: int epoll_init(); 21: int epoll_socket(int domain, int type, int protocol); 22: int epoll_cleanup(); 23: int epoll_new_conn(int sfd); 24:? 25:? 26: int main() 27: { 28:?????? struct sockaddr_in listenAddr; 29:?????? int listenFd = -1; 30:? 31:?????? if(-1 == epoll_init()) 32:?????? { 33:?????????? printf("epoll_init err\n"); 34:?????????? return -1; 35:?????? } 36:? 37:?????? if((listenFd = epoll_socket(AF_INET,SOCK_STREAM,0)) == -1) 38:?????? { 39:?????????? printf("epoll_socket err\n"); 40:?????????? epoll_cleanup(); 41:?????????? return -1; 42:?????? } 43:? 44:?????? listenAddr.sin_family = AF_INET; 45:?????? listenAddr.sin_port = htons(999); 46:?????? listenAddr.sin_addr.s_addr = htonl(INADDR_ANY); 47:? 48:?????? if(-1 == bind(listenFd,(struct sockaddr*)&listenAddr,sizeof(listenAddr))) 49:?????? { 50:?????????? printf("bind err %d\n",errno); 51:?????????? epoll_cleanup(); 52:?????????? return -1; 53:?????? } 54:? 55:?????? if(-1 == listen(listenFd,1024)) 56:?????? { 57:?????????? printf("listen err\n"); 58:?????????? epoll_cleanup(); 59:?????????? return -1; 60:?????? } 61:? 62:?????? //Add ListenFd into epoll 63:?????? if(-1 == epoll_new_conn(listenFd)) 64:?????? { 65:?????????? printf("eph_new_conn err\n"); 66:?????????? close(listenFd); 67:???????? epoll_cleanup(); 68:???????? return -1; 69:?????? } 70:? 71:? 72:?????? //LOOP 73:?????? while(1) 74:?????? { 75:?????????? int n; 76:?????????? n = epoll_wait(listenFd,events,MAX_SOCK_NUM,-1); 77:?????????? for (int i = 0; i < n; i++) 78:?????????? { 79:??????????????? if( (events[i].events & EPOLLERR) || ( events[i].events & EPOLLHUP ) || !(events[i].events & EPOLLIN) ) 80:??????????????? { 81:??????????????????? printf("epoll err\n"); 82:??????????????????? close(events[i].data.fd); 83:??????????????????? continue; 84:??????????????? } 85:??????????????? else?if(events[i].data.fd == listenFd) 86:??????????????? { 87:??????????????????? while(1) 88:??????????????????? { 89:??????????????????????? struct sockaddr inAddr; 90:??????????????????????? char hbuf[1024],sbuf[NI_MAXSERV]; 91:??????????????????????? socklen_t inLen = -1; 92:??????????????????????? int inFd = -1; 93:??????????????????????? int s = 0; 94:??????????????????????? int flag = 0; 95:? 96:??????????????????????? inLen = sizeof(inAddr); 97:??????????????????????? inFd = accept(listenFd,&inAddr,&inLen); 98:? 99:??????????????????????? if(inFd == -1) 100:??????????????????????? { 101:??????????????????????????? if( errno == EAGAIN || errno == EWOULDBLOCK ) 102:??????????????????????????? { 103:??????????????????????????????? break; 104:??????????????????????????? } 105:??????????????????????????? else 106:??????????????????????????? { 107:??????????????????????????????? printf("accept error\n"); 108:??????????????????????????????? break; 109:??????????????????????????? } 110:??????????????????????? } 111:? 112:???????????????????? if (s ==? getnameinfo (&inAddr, inLen, hbuf, sizeof(hbuf), sbuf, sizeof(sbuf), NI_NUMERICHOST | NI_NUMERICSERV)) 113:???????????????????? { 114:???????????????????????? printf("Accepted connection on descriptor %d (host=%s, port=%s)\n", inFd, hbuf, sbuf); 115:???????????????????? } 116:? 117:???????????????????? //Set Socket to non-block 118:???????????????????? if((flag = fcntl(inFd,F_GETFL,0)) < 0 || fcntl(inFd,F_SETFL,flag | O_NONBLOCK) < 0) 119:???????????????????? { 120:???????????????????????? close(inFd); 121:???????????????????????? return -1; 122:???????????????????? } 123:? 124:???????????????????? epoll_new_conn(inFd); 125:??????????????????? } 126:??????????????? } 127:??????????????? else 128:??????????????? { 129:???????????????????????? while (1) 130:???????????????????????? { 131:???????????????????????? ssize_t count; 132:???????????????????????? char buf[512]; 133:? 134:???????????????????????? count = read (events[i].data.fd, buf, sizeof buf); 135:? 136:???????????????????????? if (count == -1) 137:???????????????????????? { 138:???????????????????????????? if (errno != EAGAIN) 139:????????????????????????????? { 140:???????????????????????????????? printf("read err\n"); 141:???????????????????????????????? } 142:? 143:???????????????????????????? break; 144:? 145:???????????????????????? } 146:???????????????????????? else?if (count == 0) 147:???????????????????????? {? 148:???????????????????????????? break; 149:???????????????????????? } 150:? 151:???????????????????????? write (1, buf, count); 152:???????????????????? } 153:???????????????? } 154:?????????? } 155:? 156:?????? } 157:? 158:?????? epoll_cleanup(); 159: } 160:? 161:? 162: int epoll_init() 163: { 164:???? if(!(events = (struct epoll_event* ) malloc ( MAX_SOCK_NUM * sizeof(struct epoll_event)))) 165:???? { 166:???????? return -1; 167:???? } 168:? 169:???? if( (epollFd = epoll_create(MAX_SOCK_NUM)) < 0 ) 170:???? { 171:???????? return -1; 172:???? } 173:? 174:???? return 0; 175: } 176:? 177: int epoll_socket(int domain, int type, int protocol) 178: { 179:???? int sockFd = -1; 180:???? int flag = -1; 181:? 182:???? if ((sockFd = socket(domain,type,protocol)) < 0) 183:???? { 184:???????? return -1; 185:???? } 186:? 187:???? //Set Socket to non-block 188:???? if((flag = fcntl(sockFd,F_GETFL,0)) < 0 || fcntl(sockFd,F_SETFL,flag | O_NONBLOCK) < 0) 189:???? { 190:???????? close(sockFd); 191:???????? return -1; 192:???? } 193:? 194:???? return sockFd; 195: } 196:? 197: int epoll_cleanup() 198: { 199:???? free(events); 200:???? close(epollFd); 201:???? return 0; 202: } 203:? 204: int epoll_new_conn(int sfd) 205: { 206:? 207:?????? struct epoll_event? epollEvent; 208:?????? memset(&epollEvent, 0, sizeof(struct epoll_event)); 209:?????? epollEvent.events = EPOLLIN | EPOLLERR | EPOLLHUP | EPOLLET; 210:?????? epollEvent.data.ptr = NULL; 211:?????? epollEvent.data.fd? = sfd; 212:? 213:?????? if (epoll_ctl(epollFd, EPOLL_CTL_ADD, sfd, &epollEvent) < 0) 214:?????? { 215:???????? return -1; 216:?????? } 217:? 218:???? epollEvent.data.fd? = sfd; 219:? 220:???? return 0; 221: }

5.Epoll為什么高效

Epoll高效主要體現(xiàn)在以下三個(gè)方面:

①?gòu)纳厦娴恼{(diào)用方式就可以看出epoll比select/poll的一個(gè)優(yōu)勢(shì):select/poll每次調(diào)用都要傳遞所要監(jiān)控的所有fd給select/poll系統(tǒng)調(diào)用(這意味著每次調(diào)用都要將fd列表從用戶態(tài)拷貝到內(nèi)核態(tài),當(dāng)fd數(shù)目很多時(shí),這會(huì)造成低效)。而每次調(diào)用epoll_wait時(shí)(作用相當(dāng)于調(diào)用select/poll),不需要再傳遞fd列表給內(nèi)核,因?yàn)橐呀?jīng)在epoll_ctl中將需要監(jiān)控的fd告訴了內(nèi)核(epoll_ctl不需要每次都拷貝所有的fd,只需要進(jìn)行增量式操作)。所以,在調(diào)用epoll_create之后,內(nèi)核已經(jīng)在內(nèi)核態(tài)開始準(zhǔn)備數(shù)據(jù)結(jié)構(gòu)存放要監(jiān)控的fd了。每次epoll_ctl只是對(duì)這個(gè)數(shù)據(jù)結(jié)構(gòu)進(jìn)行簡(jiǎn)單的維護(hù)。

?

② 此外,內(nèi)核使用了slab機(jī)制,為epoll提供了快速的數(shù)據(jù)結(jié)構(gòu):

在內(nèi)核里,一切皆文件。所以,epoll向內(nèi)核注冊(cè)了一個(gè)文件系統(tǒng),用于存儲(chǔ)上述的被監(jiān)控的fd。當(dāng)你調(diào)用epoll_create時(shí),就會(huì)在這個(gè)虛擬的epoll文件系統(tǒng)里創(chuàng)建一個(gè)file結(jié)點(diǎn)。當(dāng)然這個(gè)file不是普通文件,它只服務(wù)于epoll。epoll在被內(nèi)核初始化時(shí)(操作系統(tǒng)啟動(dòng)),同時(shí)會(huì)開辟出epoll自己的內(nèi)核高速cache區(qū),用于安置每一個(gè)我們想監(jiān)控的fd,這些fd會(huì)以紅黑樹的形式保存在內(nèi)核cache里,以支持快速的查找、插入、刪除。這個(gè)內(nèi)核高速cache區(qū),就是建立連續(xù)的物理內(nèi)存頁(yè),然后在之上建立slab層,簡(jiǎn)單的說,就是物理上分配好你想要的size的內(nèi)存對(duì)象,每次使用時(shí)都是使用空閑的已分配好的對(duì)象。

?

③ epoll的第三個(gè)優(yōu)勢(shì)在于:當(dāng)我們調(diào)用epoll_ctl往里塞入百萬個(gè)fd時(shí),epoll_wait仍然可以飛快的返回,并有效的將發(fā)生事件的fd給我們用戶。這是由于我們?cè)谡{(diào)用epoll_create時(shí),內(nèi)核除了幫我們?cè)趀poll文件系統(tǒng)里建了個(gè)file結(jié)點(diǎn),在內(nèi)核cache里建了個(gè)紅黑樹用于存儲(chǔ)以后epoll_ctl傳來的fd外,還會(huì)再建立一個(gè)list鏈表,用于存儲(chǔ)準(zhǔn)備就緒的事件,當(dāng)epoll_wait調(diào)用時(shí),僅僅觀察這個(gè)list鏈表里有沒有數(shù)據(jù)即可。有數(shù)據(jù)就返回,沒有數(shù)據(jù)就sleep,等到timeout時(shí)間到后即使鏈表沒數(shù)據(jù)也返回。所以,epoll_wait非常高效。而且,通常情況下即使我們要監(jiān)控百萬計(jì)的fd,大多一次也只返回很少量的準(zhǔn)備就緒fd而已,所以,epoll_wait僅需要從內(nèi)核態(tài)copy少量的fd到用戶態(tài)而已。那么,這個(gè)準(zhǔn)備就緒list鏈表是怎么維護(hù)的呢?當(dāng)我們執(zhí)行epoll_ctl時(shí),除了把fd放到epoll文件系統(tǒng)里file對(duì)象對(duì)應(yīng)的紅黑樹上之外,還會(huì)給內(nèi)核中斷處理程序注冊(cè)一個(gè)回調(diào)函數(shù),告訴內(nèi)核,如果這個(gè)fd的中斷到了,就把它放到準(zhǔn)備就緒list鏈表里。所以,當(dāng)一個(gè)fd(例如socket)上有數(shù)據(jù)到了,內(nèi)核在把設(shè)備(例如網(wǎng)卡)上的數(shù)據(jù)copy到內(nèi)核中后就來把fd(socket)插入到準(zhǔn)備就緒list鏈表里了。

如此,一顆紅黑樹,一張準(zhǔn)備就緒fd鏈表,少量的內(nèi)核cache,就幫我們解決了大并發(fā)下的fd(socket)處理問題。

1.執(zhí)行epoll_create時(shí),創(chuàng)建了紅黑樹和就緒list鏈表。

2.執(zhí)行epoll_ctl時(shí),如果增加fd(socket),則檢查在紅黑樹中是否存在,存在立即返回,不存在則添加到紅黑樹上,然后向內(nèi)核注冊(cè)回調(diào)函數(shù),用于當(dāng)中斷事件來臨時(shí)向準(zhǔn)備就緒list鏈表中插入數(shù)據(jù)。

3.執(zhí)行epoll_wait時(shí)立刻返回準(zhǔn)備就緒鏈表里的數(shù)據(jù)即可。

6.Epoll源碼分析

?

1: static?int __init eventpoll_init(void) 2: { 3:?? mutex_init(&pmutex); 4:? 5:?? ep_poll_safewake_init(&psw); 6:? 7:?? epi_cache = kmem_cache_create("eventpoll_epi", sizeof(struct epitem), 0, SLAB_HWCACHE_ALIGN|EPI_SLAB_DEBUG|SLAB_PANIC, NULL); 8:? 9:?? pwq_cache = kmem_cache_create("eventpoll_pwq", sizeof(struct eppoll_entry), 0, EPI_SLAB_DEBUG|SLAB_PANIC, NULL); 10:? 11:?? return 0; 12: }

?

epoll用kmem_cache_create(slab分配器)分配內(nèi)存用來存放struct?epitem和struct?eppoll_entry。

?

當(dāng)向系統(tǒng)中添加一個(gè)fd時(shí),就創(chuàng)建一個(gè)epitem結(jié)構(gòu)體,這是內(nèi)核管理epoll的基本數(shù)據(jù)結(jié)構(gòu):

1: struct epitem 2: { 3:???? struct rb_node? rbn;??????? //用于主結(jié)構(gòu)管理的紅黑樹 4:? 5:???? struct list_head? rdllink;? //事件就緒隊(duì)列 6:? 7:???? struct epitem? *next;?????? //用于主結(jié)構(gòu)體中的鏈表 8:? 9:???? struct epoll_filefd? ffd;?? //這個(gè)結(jié)構(gòu)體對(duì)應(yīng)的被監(jiān)聽的文件描述符信息 10:? 11:???? int? nwait;???????????????? //poll操作中事件的個(gè)數(shù) 12:? 13:???? struct list_head? pwqlist;? //雙向鏈表,保存著被監(jiān)視文件的等待隊(duì)列,功能類似于select/poll中的poll_table 14:? 15:???? struct eventpoll? *ep;????? //該項(xiàng)屬于哪個(gè)主結(jié)構(gòu)體(多個(gè)epitm從屬于一個(gè)eventpoll) 16:? 17:???? struct list_head? fllink;?? //雙向鏈表,用來鏈接被監(jiān)視的文件描述符對(duì)應(yīng)的struct file。因?yàn)閒ile里有f_ep_link,用來保存所有監(jiān)視這個(gè)文件的epoll節(jié)點(diǎn) 18:? 19:???? struct epoll_event? event;? //注冊(cè)的感興趣的事件,也就是用戶空間的epoll_event 20:? 21: }

?

而每個(gè)epoll fd(epfd)對(duì)應(yīng)的主要數(shù)據(jù)結(jié)構(gòu)為:

1: struct eventpoll 2: { 3:???? spin_lock_t?????? lock;???????????? //對(duì)本數(shù)據(jù)結(jié)構(gòu)的訪問 4:? 5:???? struct mutex????? mtx;????????????? //防止使用時(shí)被刪除 6:? 7:???? wait_queue_head_t???? wq;?????????? //sys_epoll_wait() 使用的等待隊(duì)列 8:? 9:???? wait_queue_head_t?? poll_wait;????? //file->poll()使用的等待隊(duì)列 10:? 11:???? struct list_head??? rdllist;??????? //事件滿足條件的鏈表 12:? 13:???? struct rb_root????? rbr;??????????? //用于管理所有fd的紅黑樹(樹根) 14:? 15:???? struct epitem????? *ovflist;?????? //將事件到達(dá)的fd進(jìn)行鏈接起來發(fā)送至用戶空間 16:? 17: } 18:?

?

eventpoll在epoll_create時(shí)創(chuàng)建:

1: long sys_epoll_create(int size) 2: { 3:? 4:???? struct eventpoll *ep; 5:? 6:???? ... 7:? 8:???? ep_alloc(&ep); //為ep分配內(nèi)存并進(jìn)行初始化 9:? 10: /* 調(diào)用anon_inode_getfd 新建一個(gè)file instance,也就是epoll可以看成一個(gè)文件(匿名文件)。因此我們可以看到epoll_create會(huì)返回一個(gè)fd。epoll所管理的所有的fd都是放在一個(gè)大的結(jié)構(gòu)eventpoll(紅黑樹)中, 11: 將主結(jié)構(gòu)體struct eventpoll *ep放入file->private項(xiàng)中進(jìn)行保存(sys_epoll_ctl會(huì)取用)*/ 12:? 13:? fd = anon_inode_getfd("[eventpoll]", &eventpoll_fops, ep, O_RDWR | (flags & O_CLOEXEC)); 14:? 15:????? return fd; 16:? 17: }

??

其中,ep_alloc(struct?eventpoll **pep)為pep分配內(nèi)存,并初始化。

其中,上面注冊(cè)的操作eventpoll_fops定義如下:?

1: static?const?struct file_operations eventpoll_fops = { 2:? 3:???? .release=? ep_eventpoll_release, 4:? 5:???? .poll??? =? ep_eventpoll_poll, 6:? 7: };

??

這樣說來,內(nèi)核中維護(hù)了一棵紅黑樹,大致的結(jié)構(gòu)如下:?

??

?

接著是epoll_ctl函數(shù)(省略了出錯(cuò)檢查等代碼):

1: asmlinkage long sys_epoll_ctl(int epfd,int op,int fd,struct epoll_event __user *event) { 2:? 3:??? int error; 4:? 5:??? struct file *file,*tfile; 6:? 7:??? struct eventpoll *ep; 8:? 9:??? struct epoll_event epds; 10:? 11:? 12:? 13:??? error = -FAULT; 14:? 15:??? //判斷參數(shù)的合法性,將 __user *event 復(fù)制給 epds。 16:? 17:??? if(ep_op_has_event(op) && copy_from_user(&epds,event,sizeof(struct epoll_event))) 18:? 19:??????????? goto error_return; //省略跳轉(zhuǎn)到的代碼 20:? 21:? 22:? 23:??? file? = fget (epfd); // epoll fd 對(duì)應(yīng)的文件對(duì)象 24:? 25:??? tfile = fget(fd);??? // fd 對(duì)應(yīng)的文件對(duì)象 26:? 27:? 28:? 29:??? //在create時(shí)存入進(jìn)去的(anon_inode_getfd),現(xiàn)在取用。 30:? 31:??? ep = file->private->data; 32:? 33:? 34:? 35:??? mutex_lock(&ep->mtx); 36:? 37:? 38:? 39:??? //防止重復(fù)添加(在ep的紅黑樹中查找是否已經(jīng)存在這個(gè)fd) 40:? 41:??? epi = epi_find(ep,tfile,fd); 42:? 43:? 44:? 45:??? switch(op) 46:? 47:??? { 48:? 49:?????? ... 50:? 51:??????? case EPOLL_CTL_ADD:? //增加監(jiān)聽一個(gè)fd 52:? 53:??????????? if(!epi) 54:? 55:??????????? { 56:? 57:??????????????? epds.events |= EPOLLERR | POLLHUP;???? //默認(rèn)包含POLLERR和POLLHUP事件 58:? 59:??????????????? error = ep_insert(ep,&epds,tfile,fd);? //在ep的紅黑樹中插入這個(gè)fd對(duì)應(yīng)的epitm結(jié)構(gòu)體。 60:? 61:??????????? } else? //重復(fù)添加(在ep的紅黑樹中查找已經(jīng)存在這個(gè)fd)。 62:? 63:??????????????? error = -EEXIST; 64:? 65:??????????? break; 66:? 67:??????? ... 68:? 69:??? } 70:? 71:??? return error; 72:? 73:? 74:?

?

ep_insert的實(shí)現(xiàn)如下:

1: static?int ep_insert(struct eventpoll *ep, struct epoll_event *event, struct file *tfile, int fd) 2:? 3: { 4:? 5:??? int error ,revents,pwake = 0; 6:? 7:??? unsigned?long flags ; 8:? 9:??? struct epitem *epi; 10:? 11:??? /* 12: 13: ????? struct ep_queue{ 14: 15: ???????? poll_table pt; 16: 17: ???????? struct epitem *epi; 18: 19: ????? }?? */ 20:? 21:? 22:? 23:??? struct ep_pqueue epq; 24:? 25:? 26:? 27:??? //分配一個(gè)epitem結(jié)構(gòu)體來保存每個(gè)加入的fd 28:? 29:??? if(!(epi = kmem_cache_alloc(epi_cache,GFP_KERNEL))) 30:? 31:?????? goto error_return; 32:? 33:??? //初始化該結(jié)構(gòu)體 34:? 35:??? ep_rb_initnode(&epi->rbn); 36:? 37:??? INIT_LIST_HEAD(&epi->rdllink); 38:? 39:??? INIT_LIST_HEAD(&epi->fllink); 40:? 41:??? INIT_LIST_HEAD(&epi->pwqlist); 42:? 43:??? epi->ep = ep; 44:? 45:??? ep_set_ffd(&epi->ffd,tfile,fd); 46:? 47:??? epi->event = *event; 48:? 49:??? epi->nwait = 0; 50:? 51:??? epi->next = EP_UNACTIVE_PTR; 52:? 53:? 54:? 55:??? epq.epi = epi; 56:? 57:??? //安裝poll回調(diào)函數(shù) 58:? 59:??? init_poll_funcptr(&epq.pt, ep_ptable_queue_proc ); 60:? 61:??? /* 調(diào)用poll函數(shù)來獲取當(dāng)前事件位,其實(shí)是利用它來調(diào)用注冊(cè)函數(shù)ep_ptable_queue_proc(poll_wait中調(diào)用)。 62: 63: ?????? 如果fd是套接字,f_op為socket_file_ops,poll函數(shù)是 64: 65: ?????? sock_poll()。如果是TCP套接字的話,進(jìn)而會(huì)調(diào)用 66: 67: ?????? 到tcp_poll()函數(shù)。此處調(diào)用poll函數(shù)查看當(dāng)前 68: 69: ?????? 文件描述符的狀態(tài),存儲(chǔ)在revents中。 70: 71: ?????? 在poll的處理函數(shù)(tcp_poll())中,會(huì)調(diào)用sock_poll_wait(), 72: 73: ?????? 在sock_poll_wait()中會(huì)調(diào)用到epq.pt.qproc指向的函數(shù), 74: 75: ?????? 也就是ep_ptable_queue_proc()。? */? 76:? 77:? 78:? 79:??? revents = tfile->f_op->poll(tfile, &epq.pt); 80:? 81:? 82:? 83:??? spin_lock(&tfile->f_ep_lock); 84:? 85:??? list_add_tail(&epi->fllink,&tfile->f_ep_lilnks); 86:? 87:??? spin_unlock(&tfile->f_ep_lock); 88:? 89:? 90:? 91:??? ep_rbtree_insert(ep,epi); //將該epi插入到ep的紅黑樹中 92:? 93:? 94:? 95:??? spin_lock_irqsave(&ep->lock,flags); 96:? 97:? 98:? 99: //? revents & event->events:剛才fop->poll的返回值中標(biāo)識(shí)的事件有用戶event關(guān)心的事件發(fā)生。 100:? 101: // !ep_is_linked(&epi->rdllink):epi的ready隊(duì)列中有數(shù)據(jù)。ep_is_linked用于判斷隊(duì)列是否為空。 102:? 103: /*? 如果要監(jiān)視的文件狀態(tài)已經(jīng)就緒并且還沒有加入到就緒隊(duì)列中,則將當(dāng)前的 104: 105: ??? epitem加入到就緒隊(duì)列中.如果有進(jìn)程正在等待該文件的狀態(tài)就緒,則 106: 107: ??? 喚醒一個(gè)等待的進(jìn)程。? */? 108:? 109:? 110:? 111: if((revents & event->events) && !ep_is_linked(&epi->rdllink)) { 112:? 113:?????? list_add_tail(&epi->rdllink,&ep->rdllist); //將當(dāng)前epi插入到ep->ready隊(duì)列中。 114:? 115: /* 如果有進(jìn)程正在等待文件的狀態(tài)就緒, 116: 117: 也就是調(diào)用epoll_wait睡眠的進(jìn)程正在等待, 118: 119: 則喚醒一個(gè)等待進(jìn)程。 120: 121: waitqueue_active(q) 等待隊(duì)列q中有等待的進(jìn)程返回1,否則返回0。 122: 123: */ 124:? 125:? 126:? 127:?????? if(waitqueue_active(&ep->wq)) 128:? 129:????????? __wake_up_locked(&ep->wq,TAKS_UNINTERRUPTIBLE | TASK_INTERRUPTIBLE); 130:? 131:? 132:? 133: /*? 如果有進(jìn)程等待eventpoll文件本身(???)的事件就緒, 134: 135: ?????????? 則增加臨時(shí)變量pwake的值,pwake的值不為0時(shí), 136: 137: ?????????? 在釋放lock后,會(huì)喚醒等待進(jìn)程。 */? 138:? 139:? 140:? 141:?????? if(waitqueue_active(&ep->poll_wait)) 142:? 143:????????? pwake++; 144:? 145:??? } 146:? 147:??? spin_unlock_irqrestore(&ep->lock,flags); 148:? 149:?? 150:? 151:? 152:? 153: if(pwake) 154:? 155:?????? ep_poll_safewake(&psw,&ep->poll_wait);//喚醒等待eventpoll文件狀態(tài)就緒的進(jìn)程 156:? 157:??? return 0; 158:? 159: }

?

init_poll_funcptr(&epq.pt,?ep_ptable_queue_proc);?

revents = tfile->f_op->poll(tfile, &epq.pt);?

這兩個(gè)函數(shù)將ep_ptable_queue_proc注冊(cè)到epq.pt中的qproc。?

??

1: typedef?struct poll_table_struct { 2:? 3: poll_queue_proc qproc; 4:? 5: unsigned?long key; 6:? 7: }poll_table;

執(zhí)行f_op->poll(tfile, &epq.pt)時(shí),XXX_poll(tfile, &epq.pt)函數(shù)會(huì)執(zhí)行poll_wait(),poll_wait()會(huì)調(diào)用epq.pt.qproc函數(shù),即ep_ptable_queue_proc。

ep_ptable_queue_proc函數(shù)如下:?

1: /*? 在文件操作中的poll函數(shù)中調(diào)用,將epoll的回調(diào)函數(shù)加入到目標(biāo)文件的喚醒隊(duì)列中。 2: 3: ??? 如果監(jiān)視的文件是套接字,參數(shù)whead則是sock結(jié)構(gòu)的sk_sleep成員的地址。? */ 4:? 5: static?void ep_ptable_queue_proc(struct file *file, wait_queue_head_t *whead, poll_table *pt) { 6:? 7: /* struct ep_queue{ 8: 9: ???????? poll_table pt; 10: 11: ???????? struct epitem *epi; 12: 13: ????? } */ 14:? 15:???? struct epitem *epi = ep_item_from_epqueue(pt); //pt獲取struct ep_queue的epi字段。 16:? 17:???? struct eppoll_entry *pwq; 18:? 19:? 20:? 21:???? if (epi->nwait >= 0 && (pwq = kmem_cache_alloc(pwq_cache, GFP_KERNEL))) { 22:? 23:???????? init_waitqueue_func_entry(&pwq->wait, ep_poll_callback); 24:? 25:???????? pwq->whead = whead; 26:? 27:???????? pwq->base = epi; 28:? 29:???????? add_wait_queue(whead, &pwq->wait); 30:? 31:???????? list_add_tail(&pwq->llink, &epi->pwqlist); 32:? 33:???????? epi->nwait++; 34:? 35:???? } else { 36:? 37:???????? /* We have to signal that an error occurred */ 38:? 39:???????? /* 40: 41: ???????? * 如果分配內(nèi)存失敗,則將nwait置為-1,表示 42: 43: ???????? * 發(fā)生錯(cuò)誤,即內(nèi)存分配失敗,或者已發(fā)生錯(cuò)誤 44: 45: ???????? */ 46:? 47:???????? epi->nwait = -1; 48:? 49:???? } 50:? 51: }

?

其中struct?eppoll_entry定義如下:

1: struct eppoll_entry { 2:? 3: struct list_head llink; 4:? 5: struct epitem *base; 6:? 7: wait_queue_t wait; 8:? 9: wait_queue_head_t *whead; 10:? 11: };

?

ep_ptable_queue_proc 函數(shù)完成 epitem 加入到特定文件的wait隊(duì)列任務(wù)。?

ep_ptable_queue_proc有三個(gè)參數(shù):?

struct?file *file; 該fd對(duì)應(yīng)的文件對(duì)象?

wait_queue_head_t *whead; 該fd對(duì)應(yīng)的設(shè)備等待隊(duì)列(同select中的mydev->wait_address)?

poll_table *pt; f_op->poll(tfile, &epq.pt)中的epq.pt?

在ep_ptable_queue_proc函數(shù)中,引入了另外一個(gè)非常重要的數(shù)據(jù)結(jié)構(gòu)eppoll_entry。eppoll_entry主要完成epitem和epitem事件發(fā)生時(shí)的callback(ep_poll_callback)函數(shù)之間的關(guān)聯(lián)。首先將eppoll_entry的whead指向fd的設(shè)備等待隊(duì)列(同select中的wait_address),然后初始化eppoll_entry的base變量指向epitem,最后通過add_wait_queue將epoll_entry掛載到fd的設(shè)備等待隊(duì)列上。完成這個(gè)動(dòng)作后,epoll_entry已經(jīng)被掛載到fd的設(shè)備等待隊(duì)列。

??

由于ep_ptable_queue_proc函數(shù)設(shè)置了等待隊(duì)列的ep_poll_callback回調(diào)函數(shù)。所以在設(shè)備硬件數(shù)據(jù)到來時(shí),硬件中斷處理函數(shù)中會(huì)喚醒該等待隊(duì)列上等待的進(jìn)程時(shí),會(huì)調(diào)用喚醒函數(shù)ep_poll_callback

??

1: static?int ep_poll_callback(wait_queue_t *wait, unsigned mode, int sync, void *key) { 2:? 3:??? int pwake = 0; 4:? 5:??? unsigned?long flags; 6:? 7:??? struct epitem *epi = ep_item_from_wait(wait); 8:? 9:??? struct eventpoll *ep = epi->ep; 10:? 11:? 12:? 13:??? spin_lock_irqsave(&ep->lock, flags); 14:? 15:??? //判斷注冊(cè)的感興趣事件 16:? 17: //#define EP_PRIVATE_BITS? (EPOLLONESHOT | EPOLLET) 18:? 19: //有非EPOLLONESHONT或EPOLLET事件 20:? 21:??? if (!(epi->event.events & ~EP_PRIVATE_BITS)) 22:? 23:?????? goto out_unlock; 24:? 25:? 26:? 27:??? if (unlikely(ep->ovflist != EP_UNACTIVE_PTR)) { 28:? 29:?????? if (epi->next == EP_UNACTIVE_PTR) { 30:? 31:????????? epi->next = ep->ovflist; 32:? 33:????????? ep->ovflist = epi; 34:? 35:?????? } 36:? 37:?????? goto out_unlock; 38:? 39:??? } 40:? 41:? 42:? 43:??? if (ep_is_linked(&epi->rdllink)) 44:? 45:?????? goto is_linked; 46:? 47:???? //***關(guān)鍵***,將該fd加入到epoll監(jiān)聽的就緒鏈表中 48:? 49:??? list_add_tail(&epi->rdllink, &ep->rdllist); 50:? 51:??? //喚醒調(diào)用epoll_wait()函數(shù)時(shí)睡眠的進(jìn)程。用戶層epoll_wait(...) 超時(shí)前返回。 52:? 53: if (waitqueue_active(&ep->wq)) 54:? 55:?????? __wake_up_locked(&ep->wq, TASK_UNINTERRUPTIBLE | TASK_INTERRUPTIBLE); 56:? 57:??? if (waitqueue_active(&ep->poll_wait)) 58:? 59:?????? pwake++; 60:? 61:??? out_unlock: spin_unlock_irqrestore(&ep->lock, flags); 62:? 63:??? if (pwake) 64:? 65:?????? ep_poll_safewake(&psw, &ep->poll_wait); 66:? 67:??? return 1; 68:? 69: }

?

所以ep_poll_callback函數(shù)主要的功能是將被監(jiān)視文件的等待事件就緒時(shí),將文件對(duì)應(yīng)的epitem實(shí)例添加到就緒隊(duì)列中,當(dāng)用戶調(diào)用epoll_wait()時(shí),內(nèi)核會(huì)將就緒隊(duì)列中的事件報(bào)告給用戶。

epoll_wait實(shí)現(xiàn)如下:?

1: SYSCALL_DEFINE4(epoll_wait, int, epfd, struct epoll_event __user *, events, int, maxevents, int, timeout)? { 2:? 3:??? int error; 4:? 5:??? struct file *file; 6:? 7:??? struct eventpoll *ep; 8:? 9:???? /* 檢查maxevents參數(shù)。 */ 10:? 11:??? if (maxevents <= 0 || maxevents > EP_MAX_EVENTS) 12:? 13:?????? return -EINVAL; 14:? 15:???? /* 檢查用戶空間傳入的events指向的內(nèi)存是否可寫。參見__range_not_ok()。 */ 16:? 17:??? if (!access_ok(VERIFY_WRITE, events, maxevents * sizeof(struct epoll_event))) { 18:? 19:?????? error = -EFAULT; 20:? 21:?????? goto error_return; 22:? 23:??? } 24:? 25:???? /* 獲取epfd對(duì)應(yīng)的eventpoll文件的file實(shí)例,file結(jié)構(gòu)是在epoll_create中創(chuàng)建。 */ 26:? 27:??? error = -EBADF; 28:? 29:??? file = fget(epfd); 30:? 31:??? if (!file) 32:? 33:?????? goto error_return; 34:? 35:???? /* 通過檢查epfd對(duì)應(yīng)的文件操作是不是eventpoll_fops 來判斷epfd是否是一個(gè)eventpoll文件。如果不是則返回EINVAL錯(cuò)誤。 */ 36:? 37:??? error = -EINVAL; 38:? 39:??? if (!is_file_epoll(file)) 40:? 41:?????? goto error_fput; 42:? 43:???? /* At this point it is safe to assume that the "private_data" contains? */ 44:? 45:??? ep = file->private_data; 46:? 47:???? /* Time to fish for events ... */ 48:? 49:??? error = ep_poll(ep, events, maxevents, timeout); 50:? 51:???? error_fput: 52:? 53:??? fput(file); 54:? 55: error_return: 56:? 57:??? return error; 58:? 59: } 60:? 61:? 62:? 63: epoll_wait調(diào)用ep_poll,ep_poll實(shí)現(xiàn)如下: 64:? 65:? static?int ep_poll(struct eventpoll *ep, struct epoll_event __user *events, int maxevents, long timeout) { 66:? 67:???? int res, eavail; 68:? 69:??? unsigned?long flags; 70:? 71:??? long jtimeout; 72:? 73:??? wait_queue_t wait; 74:? 75:???? /* timeout是以毫秒為單位,這里是要轉(zhuǎn)換為jiffies時(shí)間。這里加上999(即1000-1),是為了向上取整。 */ 76:? 77:??? jtimeout = (timeout < 0 || timeout >= EP_MAX_MSTIMEO) ?MAX_SCHEDULE_TIMEOUT : (timeout * HZ + 999) / 1000; 78:? 79:? retry: 80:? 81:??? spin_lock_irqsave(&ep->lock, flags); 82:? 83:???? res = 0; 84:? 85:??? if (list_empty(&ep->rdllist)) { 86:? 87:?????? /* 沒有事件,所以需要睡眠。當(dāng)有事件到來時(shí),睡眠會(huì)被ep_poll_callback函數(shù)喚醒。*/ 88:? 89:?????? init_waitqueue_entry(&wait, current); //將current進(jìn)程放在wait這個(gè)等待隊(duì)列中。 90:? 91:?????? wait.flags |= WQ_FLAG_EXCLUSIVE; 92:? 93:?????? /* 將當(dāng)前進(jìn)程加入到eventpoll的等待隊(duì)列中,等待文件狀態(tài)就緒或直到超時(shí),或被信號(hào)中斷。 */ 94:? 95:?????? __add_wait_queue(&ep->wq, &wait); 96:? 97:??????? for (;;) { 98:? 99:????????? /* 執(zhí)行ep_poll_callback()喚醒時(shí)應(yīng)當(dāng)需要將當(dāng)前進(jìn)程喚醒,所以當(dāng)前進(jìn)程狀態(tài)應(yīng)該為“可喚醒”TASK_INTERRUPTIBLE? */ 100:? 101:????????? set_current_state(TASK_INTERRUPTIBLE); 102:? 103:????????? /* 如果就緒隊(duì)列不為空,也就是說已經(jīng)有文件的狀態(tài)就緒或者超時(shí),則退出循環(huán)。*/ 104:? 105:????????? if (!list_empty(&ep->rdllist) || !jtimeout) 106:? 107:???????????? break; 108:? 109:????????? /* 如果當(dāng)前進(jìn)程接收到信號(hào),則退出循環(huán),返回EINTR錯(cuò)誤 */ 110:? 111:????????? if (signal_pending(current)) { 112:? 113:???????????? res = -EINTR; 114:? 115:???????????? break; 116:? 117:????????? } 118:? 119:?????????? spin_unlock_irqrestore(&ep->lock, flags); 120:? 121:????????? /* 主動(dòng)讓出處理器,等待ep_poll_callback()將當(dāng)前進(jìn)程喚醒或者超時(shí),返回值是剩余的時(shí)間。 122: 123: 從這里開始當(dāng)前進(jìn)程會(huì)進(jìn)入睡眠狀態(tài),直到某些文件的狀態(tài)就緒或者超時(shí)。 124: 125: 當(dāng)文件狀態(tài)就緒時(shí),eventpoll的回調(diào)函數(shù)ep_poll_callback()會(huì)喚醒在ep->wq指向的等待隊(duì)列中的進(jìn)程。*/ 126:? 127:????????? jtimeout = schedule_timeout(jtimeout); 128:? 129:????????? spin_lock_irqsave(&ep->lock, flags); 130:? 131:?????? } 132:? 133:?????? __remove_wait_queue(&ep->wq, &wait); 134:? 135:??????? set_current_state(TASK_RUNNING); 136:? 137:??? } 138:? 139:???? /* ep->ovflist鏈表存儲(chǔ)的向用戶傳遞事件時(shí)暫存就緒的文件。 140: 141: ??? * 所以不管是就緒隊(duì)列ep->rdllist不為空,或者ep->ovflist不等于 142: 143: ??? * EP_UNACTIVE_PTR,都有可能現(xiàn)在已經(jīng)有文件的狀態(tài)就緒。 144: 145: ??? * ep->ovflist不等于EP_UNACTIVE_PTR有兩種情況,一種是NULL,此時(shí) 146: 147: ??? * 可能正在向用戶傳遞事件,不一定就有文件狀態(tài)就緒, 148: 149: ??? * 一種情況時(shí)不為NULL,此時(shí)可以肯定有文件狀態(tài)就緒, 150: 151: ??? * 參見ep_send_events()。 152: 153: ??? */ 154:? 155:??? eavail = !list_empty(&ep->rdllist) || ep->ovflist != EP_UNACTIVE_PTR; 156:? 157:???? spin_unlock_irqrestore(&ep->lock, flags); 158:? 159:???? /* Try to transfer events to user space. In case we get 0 events and there's still timeout left over, we go trying again in search of more luck. */ 160:? 161:??? /* 如果沒有被信號(hào)中斷,并且有事件就緒,但是沒有獲取到事件(有可能被其他進(jìn)程獲取到了),并且沒有超時(shí),則跳轉(zhuǎn)到retry標(biāo)簽處,重新等待文件狀態(tài)就緒。 */ 162:? 163:??? if (!res && eavail && !(res = ep_send_events(ep, events, maxevents)) && jtimeout) 164:? 165:?????? goto retry; 166:? 167:???? /* 返回獲取到的事件的個(gè)數(shù)或者錯(cuò)誤碼 */ 168:? 169:??? return res; 170:? 171: }

?

?

ep_send_events函數(shù)向用戶空間發(fā)送就緒事件。?

ep_send_events()函數(shù)將用戶傳入的內(nèi)存簡(jiǎn)單封裝到ep_send_events_data結(jié)構(gòu)中,然后調(diào)用ep_scan_ready_list() 將就緒隊(duì)列中的事件傳入用戶空間的內(nèi)存。

用戶空間訪問這個(gè)結(jié)果,進(jìn)行處理。?

?

?

7.參考

?

1.http://www.cnblogs.com/apprentice89/p/3234677.html

2.http://www.cnblogs.com/apprentice89/archive/2013/05/06/3063039.html


總結(jié)

以上是生活随笔為你收集整理的Epoll详解及源码分析的全部?jī)?nèi)容,希望文章能夠幫你解決所遇到的問題。

如果覺得生活随笔網(wǎng)站內(nèi)容還不錯(cuò),歡迎將生活随笔推薦給好友。

伊人五月天婷婷 | 国产97av| 国产青草视频在线观看 | 美女又爽又黄 | 91麻豆看国产在线紧急地址 | 狠狠干综合 | 91视视频在线直接观看在线看网页在线看 | 国产97视频| www.夜夜干.com| 国产成人精品免高潮在线观看 | 午夜av电影 | 国产男女无遮挡猛进猛出在线观看 | 丝袜护士aⅴ在线白丝护士 天天综合精品 | 午夜成人免费影院 | a在线免费 | 欧美日韩高清 | 欧美日韩免费网站 | 日一日干一干 | 99精品国产一区二区三区不卡 | 色综合久久中文字幕综合网 | 欧美精品小视频 | av黄色在线观看 | 婷婷日日 | 国产精品久久久久久久av大片 | 日韩电影一区二区在线观看 | 日韩电影一区二区在线观看 | 国产精品高清免费在线观看 | 精品视频免费在线 | 国产在线观看免费观看 | 又爽又黄在线观看 | 国内精品久久久久久久久久清纯 | 国产精品一区二区在线播放 | 一级性av | 国产中文字幕在线观看 | 亚洲一区二区视频在线 | 亚洲精品综合在线 | 日韩av电影免费在线观看 | 2019久久精品 | 国产主播大尺度精品福利免费 | 色婷婷综合成人av | 成人黄色毛片视频 | 在线观看aaa | 日韩电影一区二区三区在线观看 | 亚洲美女在线一区 | 久久免费av | 激情欧美一区二区三区 | 99视频在线免费播放 | 欧美在线视频第一页 | 天天操天天摸天天干 | 九九九九九九精品 | 国产在线专区 | 亚洲成人在线免费 | 在线看片视频 | 国模视频一区二区 | 韩国一区二区av | 久久久免费精品国产一区二区 | 亚洲一级理论片 | 日韩色区 | 91精品国产一区 | 欧美精品一区二区在线播放 | 精品久久久久久一区二区里番 | 日韩视频 一区 | 91热精品| 日韩精品一区二区在线视频 | 国内精品久久久久久中文字幕 | 久久国产精品99久久久久久进口 | 色婷婷综合久久久久中文字幕1 | 在线看日韩 | 国产免费黄视频在线观看 | 我要色综合天天 | 久久成人18免费网站 | 免费av网址大全 | 国产精品国产亚洲精品看不卡15 | 亚洲国产成人在线观看 | 婷婷在线播放 | 91麻豆精品国产91久久久无需广告 | 亚洲精品午夜aaa久久久 | 日韩av视屏在线观看 | 精品视频免费在线 | 在线天堂v | 国产福利一区二区三区在线观看 | 五月网婷婷 | 久久激情视频 久久 | 日韩中文在线播放 | 久久理论影院 | 国产a视频免费观看 | 一区二区丝袜 | 国产你懂的在线 | 中文在线字幕免费观看 | 国产免费一区二区三区网站免费 | 国产麻豆果冻传媒在线观看 | 久久视频这里只有精品 | 久久成年人网站 | 久久最新视频 | 久久精品www人人爽人人 | 一区免费观看 | 国产精品久久久亚洲 | 国产99久久九九精品免费 | 91人人在线 | 国产精品毛片一区二区 | 亚洲mv大片欧洲mv大片免费 | 天天爽天天爽天天爽 | 精品视频久久久久久 | 久久99久久久久 | av线上看 | 三级性生活视频 | 精品一二三四五区 | 欧美精品国产综合久久 | 久久免费a | 国产在线2020 | 国产喷水在线 | 探花视频免费在线观看 | 国产精品青青 | 成人一级片在线观看 | 精品国产伦一区二区三区观看方式 | 四虎最新域名 | 久久99日韩 | 亚洲黄色激情小说 | 国产午夜麻豆影院在线观看 | 天天干,夜夜爽 | 国产精品资源 | 免费在线成人 | 91av在线免费视频 | 午夜色影院 | 国产精品原创 | 日本爱爱免费视频 | 国产a级精品 | 日韩理论电影在线观看 | 久久久国产精品网站 | 久久精品艹 | 99这里只有久久精品视频 | 久久午夜羞羞影院 | 人人舔人人舔 | 欧美99热 | 亚州精品天堂中文字幕 | 国产精品久久久久久久久久99 | 色是在线视频 | 欧美一级片免费在线观看 | 521色香蕉网站在线观看 | 日韩视频一区二区三区在线播放免费观看 | 成人免费视频视频在线观看 免费 | 在线亚洲欧美日韩 | 婷婷久久久久 | 国外av在线| 久久福利剧场 | 一级黄色在线免费观看 | 在线视频福利 | 国产成人精品日本亚洲999 | 99久久婷婷国产一区二区三区 | 2023天天干| 色偷偷网站视频 | 天天av综合网| 最新的av网站| 日本黄色免费看 | 国产特级毛片aaaaaa | 亚洲九九九在线观看 | 波多野结衣视频一区二区 | 久久精品国产v日韩v亚洲 | 粉嫩aⅴ一区二区三区 | 久久久久久久久久电影 | 丁香婷婷色综合亚洲电影 | 国产色婷婷精品综合在线手机播放 | 久久99视频精品 | 亚州av网站大全 | 久久免费福利 | 在线欧美小视频 | 91香蕉久久 | 黄色一级大片免费看 | 在线观看播放av | 在线免费观看黄色 | 91中文字幕在线观看 | 国产又粗又猛又爽又黄的视频先 | 中文字幕免费观看 | 国产91精品欧美 | 色激情在线 | 91麻豆精品国产自产在线 | www.夜色321.com | 免费欧美高清视频 | 日日夜夜干 | 亚洲精品视频在线观看网站 | 免费在线观看av的网站 | 天天草夜夜 | 91精品国产电影 | 久久精品韩国 | 黄色在线网站噜噜噜 | 国产精品久久久久久99 | 在线免费性生活片 | 9在线观看免费高清完整 | 99精品国产免费久久久久久下载 | 99免费在线播放99久久免费 | 青青啪 | 黄色三级网站在线观看 | 国产亚洲综合精品 | 亚洲视频久久久久 | 国产精品永久免费 | 国产精品黑丝在线观看 | 97超碰精品| av一级网站 | 久久久精品一区二区三区 | 久久久久免费精品 | 亚州人成在线播放 | 99精品黄色 | 国产小视频在线免费观看视频 | 91视频在线免费观看 | av大全在线免费观看 | 日韩欧美高清一区二区三区 | 国产在线看一区 | 91欧美在线 | 亚洲成a人片在线观看网站口工 | 97在线观看免费视频 | 欧美91精品国产自产 | 成人免费在线播放 | 久久草在线视频国产 | 九九热精| 97国产超碰 | 国产精品对白一区二区三区 | 成人精品视频 | 欧美成人aa| 91激情小视频 | 日韩精品一区二区三区中文字幕 | 婷婷日日 | 国产精品对白一区二区三区 | 国产精品成人国产乱一区 | 欧美激情精品久久 | 久久久久综合视频 | 精品国产一区二区三区在线观看 | 国内丰满少妇猛烈精品播放 | 一区二区三区免费在线播放 | 午夜一级免费电影 | 精品国偷自产在线 | 欧美成人精品在线 | 国产精品久久久久9999吃药 | 天天色天天爱天天射综合 | 菠萝菠萝在线精品视频 | 视频在线一区 | 日韩精品一区二区三区中文字幕 | h久久| 国产剧情一区二区在线观看 | 欧美日韩亚洲一 | 久久999久久 | 中文一区在线 | 久久国产精品免费视频 | 91精品播放 | 六月婷色 | 久久精品美女视频网站 | 免费看黄在线观看 | 亚洲一级片免费观看 | 色妞久久福利网 | 亚洲视屏 | av大全在线免费观看 | 欧美一级片在线观看视频 | 亚洲人成精品久久久久 | 超级碰碰免费视频 | 国产精品福利久久久 | 一区免费观看 | 国产精品久久久久婷婷二区次 | 国产精品久久久网站 | 久久国产精品影视 | 激情久久伊人 | 欧美成年人在线观看 | 18网站在线观看 | 国产专区视频在线观看 | 一区二区电影在线观看 | 精品国产伦一区二区三区观看说明 | 911精品美国片911久久久 | 日韩精品综合在线 | 久久久久久高潮国产精品视 | 久久九九国产精品 | 美女网站一区 | 麻豆成人在线观看 | 天天爽夜夜爽人人爽曰av | 久久久99国产精品免费 | 国产精品理论在线观看 | 亚洲成人黄 | 丁香av| 国产免费影院 | 国产精品免费在线观看视频 | 欧美精品一区在线发布 | 99精品欧美一区二区蜜桃免费 | 欧美久久久影院 | 国产成人一区二区三区电影 | 日韩羞羞 | 最近2019年日本中文免费字幕 | 黄毛片在线观看 | 天天操天天干天天爱 | 蜜臀av一区二区 | 日日干夜夜操视频 | 国产精品18久久久久久vr | 天天干天天搞天天射 | 99在线热播精品免费 | 国产亚洲精品久久久久动 | 亚洲激情视频在线 | 日本精品久久久一区二区三区 | 波多野结衣视频一区二区三区 | 视频91 | 69人人 | 日日夜夜精品免费 | 日韩a在线 | 色综合婷婷 | 69中文字幕 | 91人人爽人人爽人人精88v | 久久久片 | 国产96在线观看 | 亚洲欧美日韩一级 | 久久天天躁狠狠躁夜夜不卡公司 | 成人精品99 | 婷婷在线色 | 91九色在线视频 | 成人毛片久久 | 一区二区不卡视频在线观看 | 久久艹国产视频 | 91少妇精拍在线播放 | 高清av中文在线字幕观看1 | 欧美精品免费视频 | 国产午夜精品久久 | 国产精品久久久777 成人手机在线视频 | 狠狠色伊人亚洲综合网站野外 | 三级免费黄色 | 久久精品视频免费播放 | 欧美一级特黄aaaaaa大片在线观看 | 国产精品一区二区在线 | 中文字幕亚洲欧美 | 亚洲一区二区三区四区在线视频 | 日韩特级片 | 黄色国产区 | 久久久综合九色合综国产精品 | 国产在线观看99 | 91喷水| 黄a在线 | 99视频在线精品 | 亚洲理论在线观看 | 成人在线播放av | 国产精品白丝jk白祙 | 久久精品成人热国产成 | 四虎国产精品成人免费4hu | 亚洲视频在线看 | 亚洲色图激情文学 | 免费国产ww| 国产成人av电影 | 久久情侣偷拍 | 一区二区三区日韩精品 | 在线免费黄色av | 亚洲最大成人免费网站 | 在线视频1卡二卡三卡 | 午夜免费电影院 | 久久久久久久久久久久国产精品 | 欧美少妇xx | 麻豆成人在线观看 | 91麻豆精品国产91久久久无需广告 | 97天天干| 激情开心站 | 免费韩国av | 全久久久久久久久久久电影 | 亚洲精品视频在线观看视频 | 日韩欧美有码在线 | 狠狠干网| 国产片网站 | 天天操天天透 | 久草在线视频首页 | 亚洲综合精品视频 | 国产在线观看你懂得 | 碰超在线97人人 | 成人久久久电影 | 精品91久久久久 | 日韩欧美在线观看一区 | 国产高清99| 婷婷五月色综合 | 亚洲精品乱码久久久久久蜜桃不爽 | 99精品国产亚洲 | 精品96久久久久久中文字幕无 | 国产一区二区在线观看免费 | 久久成人18免费网站 | 开心色激情网 | 成人黄色毛片视频 | 天天操月月操 | 黄色一及电影 | 国产精品99久久久精品 | 色噜噜狠狠狠狠色综合 | 97精品国产97久久久久久免费 | 久久视频免费观看 | 欧美黄色特级片 | 国产精品一区二区久久国产 | 日韩av不卡播放 | 午夜久操| 成人黄色中文字幕 | 亚洲精品乱码久久久久久 | 四虎影视成人永久免费观看视频 | 久草精品免费 | 日韩欧美一区二区三区在线观看 | 国内精品在线一区 | 国产精品欧美久久 | 亚洲三级在线免费观看 | 成年人免费电影在线观看 | 国产精品久久久 | 色丁香综合 | 久久久精品午夜 | 就色干综合 | 在线观看涩涩 | 欧美日韩观看 | 一级黄色av| 色吧av色av| a视频免费| www.91成人 | 亚洲乱码在线观看 | 视频一区二区在线 | 日韩在线观看视频在线 | 久久1电影院 | 国产精品xxxx18a99 | 日本在线精品视频 | 日韩欧美91 | 亚洲精品一区二区三区高潮 | 日韩高清精品免费观看 | 欧美影院久久 | 91在线视频免费观看 | 五月婷婷电影网 | 最近更新好看的中文字幕 | 精品久久久久亚洲 | 91亚瑟视频 | 日韩精品字幕 | 日本久久成人 | 久久精品视频4 | 国产中文视 | 国产在线国偷精品产拍免费yy | 日批在线观看 | 狠狠色丁香婷婷综合欧美 | 欧美激情综合五月色丁香小说 | 香蕉视频在线观看免费 | 在线观看 国产 | 国产精品毛片 | 中文字幕第一 | 国产一级片一区二区三区 | 亚洲激情五月 | 爱色婷婷 | 国产一区黄色 | 99热精品视 | 欧美精品在线观看一区 | 中文日韩在线 | 亚洲无线视频 | 久久精品79国产精品 | 日韩高清在线观看 | wwwwwww黄 | 超碰在线9 | 欧美日韩观看 | 成人久久毛片 | 91在线看黄 | 美女露久久| 亚洲精品午夜国产va久久成人 | 欧美久草网| 亚洲在线视频网站 | 少妇自拍av | 91在线视频网址 | 天天操天天射天天添 | 黄色一级免费 | 国产99久久99热这里精品5 | 国产精品久久久av久久久 | 免费黄色在线播放 | 久久久国产电影 | 国产精品综合av一区二区国产馆 | 啪啪动态视频 | 国产精国产精品 | 综合国产在线观看 | 欧美人体xx | 一级做a视频 | 美女精品在线观看 | 人人插超碰 | 视频三区在线 | 国产精品va在线播放 | 狠狠色丁香久久婷婷综合丁香 | 精品一区二区三区久久久 | 蜜臀久久99精品久久久久久网站 | 成人91在线 | av在线播放亚洲 | 日日夜日日干 | 性色在线视频 | 久草9视频| 玖玖玖影院 | 四虎影视av| 久久综合亚洲鲁鲁五月久久 | 中文字幕日韩精品有码视频 | 在线观看免费视频你懂的 | 在线欧美中文字幕 | 免费在线观看国产精品 | 久久久久国产精品厨房 | 久久99精品久久久久蜜臀 | 欧美片网站yy | 99热在线免费观看 | 精品久久久久久亚洲 | 日韩在线视频在线观看 | 精品国自产在线观看 | 婷婷丁香花五月天 | 国产人成精品一区二区三 | 国产视频 亚洲视频 | 日韩中文字幕在线观看 | 欧日韩在线视频 | 国产精品一区免费在线观看 | 天天爱av导航 | 99久久婷婷国产综合亚洲 | 国产97色在线 | 在线观看91精品国产网站 | 911国产| 亚洲视频高清 | 午夜影视一区 | 五月婷婷六月综合 | 国产日韩精品久久 | 欧美激情xxxx| 日韩一区二区三区免费视频 | 欧美日韩免费观看一区二区三区 | www国产亚洲精品 | 久久66热这里只有精品 | 夜夜操网 | 欧美日韩亚洲第一 | 久久免费看视频 | 天天干天天做天天爱 | 欧美一级欧美一级 | 97天天综合网 | 黄色软件在线观看 | 久久影院中文字幕 | 日韩视频在线观看免费 | 日韩最新中文字幕 | 久草香蕉在线视频 | 波多野结衣日韩 | 欧美一进一出抽搐大尺度视频 | 国产成人一区二区三区在线观看 | 欧美日韩网站 | 亚洲性xxxx | 丁香婷婷在线 | 一本色道久久精品 | 天天操天天摸天天射 | 日韩大片在线播放 | 国产高清不卡av | 国产精品国产三级国产aⅴ无密码 | 国产一区二区影院 | 国产成人综 | 亚洲日韩欧美一区二区在线 | 中文字幕色在线视频 | 日韩高清www | 国产成人久久久77777 | 亚洲涩涩涩 | 亚洲精品美女免费 | aaa毛片视频 | 中文日韩在线 | 人人爱人人添 | 成人av在线影视 | 99精品欧美一区二区蜜桃免费 | 91视频91自拍 | 亚洲国产三级在线观看 | 日日干日日 | 99久久精品久久久久久动态片 | 狠狠的日日 | 成人动漫一区二区三区 | 欧美日本国产在线观看 | 视频精品一区二区三区 | 91精彩视频| 麻豆av电影| 欧美激情va永久在线播放 | 成人免费在线视频观看 | 91精品天码美女少妇 | 91尤物国产尤物福利在线播放 | 一级免费看 | 国产视频欧美视频 | 97碰在线视频 | av手机版| 午夜精品电影一区二区在线 | 激情久久久久久久久久久久久久久久 | 亚洲婷婷综合色高清在线 | 伊人久久精品久久亚洲一区 | 成人免费视频网站在线观看 | 在线免费视频一区 | 国产精品久久久999 国产91九色视频 | 91资源在线 | 国产精品二区在线观看 | 免费观看视频黄 | 国产日韩精品一区二区在线观看播放 | 亚洲国产中文字幕 | 色国产精品 | 欧美伦理一区二区 | 久久国产视屏 | 亚洲粉嫩av | 欧美污污网站 | 天天想夜夜操 | 天天插天天操天天干 | 亚洲综合导航 | 日韩色在线 | 中文字幕亚洲欧美日韩2019 | 欧美黄污视频 | 99se视频在线观看 | 国产成人一区二 | 最新99热 | 久久天天躁夜夜躁狠狠85麻豆 | 99这里都是精品 | 成年人黄色av | 亚洲九九影院 | 高清色免费 | 在线之家免费在线观看电影 | 五月天婷婷在线观看视频 | 五月天网站在线 | 日韩精品一区二区三区在线播放 | 操久| 69久久久久久久 | 在线亚洲精品 | 精品视频一区在线观看 | 国内三级在线观看 | 狠狠的干 | 97色噜噜 | 日韩理论在线播放 | 国产精品久久久久久久久久久久久 | 午夜精品久久久久久久99热影院 | 欧美精品乱码99久久影院 | 久久视频在线观看 | 成年人免费看片 | 精品久久久国产 | 日韩在线免费 | 久久久久99精品国产片 | 国产在线视频导航 | 国产在线观看地址 | 精品99在线视频 | 国产高清小视频 | 国产r级在线观看 | 亚洲视频在线观看 | 国产精久久久久久妇女av | 日本精品久久久久 | 国产亚洲欧美在线视频 | 日韩av网址在线 | 一区二区高清在线 | av资源在线看 | av电影在线观看完整版一区二区 | 超碰在线日韩 | 日本在线视频一区二区三区 | 婷婷狠狠操 | www视频免费在线观看 | 91麻豆精品国产91久久久无限制版 | 久久国产精品精品国产色婷婷 | 国内视频 | 一本一本久久a久久精品牛牛影视 | 综合色亚洲 | 天天爱天天草 | 中文字幕在线观看一区 | 2023年中文无字幕文字 | 99免在线观看免费视频高清 | 久久成人精品电影 | 日本公妇色中文字幕 | 91视频 - v11av | 国产三级国产精品国产专区50 | 国产69精品久久99的直播节目 | 亚洲高清视频一区二区三区 | 久章草在线观看 | 欧美美女视频在线观看 | 日韩一区二区三区在线观看 | 国产一区二区三区 在线 | 欧美成人69av | 2019中文字幕第一页 | 日韩大片免费观看 | 婷婷网在线 | 日韩av一区二区在线影视 | 麻豆视频免费播放 | 日韩在线观看视频在线 | 可以免费观看的av片 | 免费高清无人区完整版 | 丁香五月缴情综合网 | 国产91九色视频 | 中文字幕第 | 欧美一级爽 | 精品三级av | 亚洲一区久久 | 国产欧美三级 | 成人毛片一区二区三区 | 香蕉影院在线观看 | 日韩中文字幕免费在线播放 | 91看片网址 | 肉色欧美久久久久久久免费看 | 狠狠色伊人亚洲综合成人 | 欧美日韩成人一区 | 高清av免费看 | 天天躁日日躁狠狠躁av中文 | av福利超碰网站 | 色999视频 | 免费观看视频的网站 | 天天色播 | 精品影院一区二区久久久 | 狠狠色丁香久久综合网 | 一本一本久久aa综合精品 | 久久视频在线 | 91免费看黄色 | 国产精品自在线拍国产 | 婷婷丁香狠狠爱 | 中文视频在线 | 91理论电影| 中文字幕网站视频在线 | 99久国产 | 91豆麻精品91久久久久久 | 日韩最新av在线 | 成人午夜精品福利免费 | 激情伊人五月天 | 不卡av电影在线观看 | 日本精品在线看 | 国产精品久久久久久久久毛片 | 免费精品在线观看 | 亚洲免费婷婷 | 夜夜爽www | 少妇视频在线播放 | 国产精品毛片一区视频播不卡 | 成年人电影免费在线观看 | 91cn国产在线| 久久综合亚洲鲁鲁五月久久 | 亚洲欧美国产精品va在线观看 | 国产999精品久久久久久绿帽 | 激情视频国产 | 久久激情片 | 成人小视频在线免费观看 | 免费观看一级成人毛片 | 国产成人精品日本亚洲999 | 国产精品午夜av | 久久国产精品色婷婷 | 久久综合久久88 | 91视频成人免费 | 日韩在线观看高清 | 久久99国产一区二区三区 | 最近免费中文字幕mv在线视频3 | 国产精品美女999 | 美女网站在线播放 | 国产黄色理论片 | 91麻豆精品久久久久久 | 欧美久久久久久久久久 | 欧美精品二 | 在线观看亚洲精品 | 国产一区二区久久 | 日韩精品一区二区在线 | 婷婷综合久久 | 亚洲视频网站在线观看 | 丰满少妇对白在线偷拍 | 91精品国产高清自在线观看 | 亚洲黄电影 | 在线看片成人 | 国产69精品久久99不卡的观看体验 | 国产成人精品国内自产拍免费看 | 久久久在线 | www.亚洲精品 | 在线播放你懂 | 成人av在线直播 | 亚洲最大在线视频 | 国内免费的中文字幕 | 久久久精品国产一区二区电影四季 | 九九久久免费 | 丁香电影小说免费视频观看 | 亚洲精品乱码久久久久久写真 | 97精品久久| 成人免费观看在线视频 | 久久艹在线观看 | 极品美女被弄高潮视频网站 | 婷婷丁香在线视频 | 黄色a在线| 成人av免费播放 | 国产精品毛片久久久久久久 | 精品久久久久久久久久久久久 | 国产精品久久久久久久午夜 | 日韩精品免费一区二区在线观看 | 亚洲国产精品va在线看 | 久久成人麻豆午夜电影 | 欧美性生活一级片 | 欧美精品九九 | 日韩高清激情 | 91在线视频在线 | 欧美一级欧美一级 | 国产精品福利午夜在线观看 | 欧美成人精品欧美一级乱黄 | 首页av在线 | 久久久久久高潮国产精品视 | 九九色综合 | 九九视频在线播放 | 激情综合五月天 | 91成熟丰满女人少妇 | 97人人模人人爽人人喊中文字 | 天天爽网站 | 国产资源免费 | 久久久影院官网 | 玖玖视频在线 | 色中文字幕在线观看 | 国产精品中文字幕在线播放 | 日韩在线观看精品 | 亚洲精品在线免费 | 黄色在线视频网址 | 亚洲一级片在线看 | 操操色 | 成人影片免费 | 伊人天堂网 | 成人午夜剧场在线观看 | 久久久久久久久久久久久久电影 | 婷婷亚洲激情 | 色播五月激情五月 | 丁香婷婷在线观看 | 欧美精彩视频在线观看 | 91视频在线观看下载 | 在线免费观看黄网站 | 日本黄色免费网站 | 欧美一区二区三区免费观看 | 欧美做受69| 国产成人精品网站 | 国产精品久久久久久婷婷天堂 | 91污污| 国产精品一区二区av | 亚洲国产成人久久 | 天天爽天天射 | 国产99久久精品一区二区300 | www.久久色.com| 亚洲成aⅴ人片久久青草影院 | 国产在线不卡精品 | 久久成人亚洲欧美电影 | 在线观看黄色免费视频 | 超碰电影在线观看 | 成人午夜久久 | 国产 一区二区三区 在线 | 久久久精品成人 | 久久久污| 久久69精品久久久久久久电影好 | 波多野结衣视频一区二区三区 | 日韩成人在线免费观看 | 日韩成人在线一区二区 | 中中文字幕av在线 | 激情网站五月天 | 欧美激情h | 国内外成人在线视频 | 亚洲精品午夜久久久久久久 | 亚洲国产一区在线观看 | 日韩在线视频网站 | 久久综合九色综合久99 | 国产破处精品 | 色哟哟国产精品 | 久久婷婷一区二区三区 | 欧美国产在线看 | 久久亚洲人| 最新高清无码专区 | 欧美精品第一 | 久久久久二区 | 国产精品99免视看9 国产精品毛片一区视频 | 黄色av一区 | 99精品久久99久久久久 | 亚洲va欧美va | 精品日韩在线一区 | 91麻豆精品久久久久久 | 久久99国产精品二区护士 | 99色视频在线 | 中文字幕影片免费在线观看 | 日韩中文字幕免费电影 | 日韩精品视频一二三 | 亚洲欧美国产精品18p | 国产精品一区二区三区视频免费 | 激情久久网 | 亚洲国产精品va在线看 | 久久久久一区二区三区四区 | 国产视频欧美视频 | 97在线视频免费看 | 欧美一二三在线 | 国产色女| 午夜 在线| 3d黄动漫免费看 | 国产成人黄色在线 | 成av人电影| 一区二区久久 | 97精品国产 | av免费电影网站 | 91福利影院在线观看 | 黄色av一级片 | 久久一区二区三区超碰国产精品 | 中日韩在线视频 | 最新日韩视频在线观看 | 韩国精品在线 | 亚洲精品久久久久久久蜜桃 | 成人中文字幕在线观看 | 欧美日韩69| 欧美日韩69| 欧美日一级片 | 久久人人艹 | 精品欧美日韩 | 国产在线v| 婷婷久久亚洲 | 中文国产字幕在线观看 | 8090yy亚洲精品久久 | 国产青草视频在线观看 | 日韩精品免费在线观看视频 | 手机在线免费av | 午夜精品久久一牛影视 | 99精品视频在线观看免费 | 99精品国产免费久久久久久下载 | 国产在线国偷精品产拍免费yy | 久久精品视频网 | 欧美日韩午夜在线 | 91精品国产高清自在线观看 | 国产亲近乱来精品 | 亚洲午夜久久久久久久久 | 丁香花在线视频观看免费 | 91日韩在线播放 | 一级成人网 | 黄色99视频| 一区二区三区四区五区六区 | 国产精品一区二区三区久久久 | 狠狠综合久久av | 99中文字幕视频 | 久久人人爽爽人人爽人人片av | 日韩欧美一级二级 | 成年美女黄网站色大片免费看 | 国产精品毛片一区二区在线 | 美女视频黄色免费 | 欧美另类xxxx | 一区二区三区四区五区在线 | 在线免费高清一区二区三区 | 热久久视久久精品18亚洲精品 | 久草www | 综合色站| 亚洲成人精品久久久 | av午夜电影| 久久精品视频99 | 国产成人精品久久二区二区 | 久久精久久精 | 国产亚洲情侣一区二区无 | 中文字幕在线国产精品 | 国产人在线成免费视频 | 免费看污黄网站 | 久久人人爽人人爽人人片av软件 | 亚洲精品国产精品国自产在线 | 在线探花| 国产在线v| 日韩精品免费一线在线观看 | 国内精品久久久久久久久久久 | 亚洲精品伦理在线 | 欧美精品资源 | 国产精品一区二区精品视频免费看 | 欧美一区二区免费在线观看 | 综合黄色网 | 久久一区二区三区日韩 | 日韩在线视频看看 | 婷婷六月天丁香 | 亚洲人成精品久久久久 | 99r精品视频在线观看 | 永久中文字幕 | 一区二区三区高清不卡 | 欧洲精品视频一区二区 | 最近中文字幕国语免费av | 成人免费观看在线视频 | 午夜视频免费在线观看 | 日韩欧美视频在线观看免费 | 天天综合久久 | 亚洲天堂香蕉 | 免费观看www小视频的软件 | 久久艹精品 | 国内视频一区二区 | 99这里只有精品视频 | 国产免费美女 | 高清免费av在线 | 日韩精品一区二区三区视频播放 | 2023天天干 | 麻豆一二 | 中文国产在线观看 | 国产91精品一区二区麻豆亚洲 | 在线免费观看黄网站 | 欧美激情片在线观看 | 国产精品1区2区在线观看 | 欧美日韩国产精品久久 | 免费a v观看 | 91在线看视频免费 | 亚洲国产久 | 精品久久久久久久久久久久 | 天天综合日日夜夜 | 日本性生活免费看 | aaa日本高清在线播放免费观看 | 日韩网站在线免费观看 | 最新av免费 | 波多野结衣在线播放一区 | 成人高清在线观看 | 91精品国产91p65 | 91中文字幕一区 | 丁香激情综合 | av黄网站 | 在线亚洲午夜片av大片 | 国产一级二级在线观看 | 亚洲人人射 | 日本韩国精品一区二区在线观看 | 69人人 | 最新中文字幕 | 一区二区三区免费网站 | 亚洲午夜久久久久久久久电影网 | 久草精品资源 | 精品国产一区二 | 欧美日韩精品在线观看视频 | 久久久91精品国产一区二区三区 | 狠狠操综合网 | 成人国产精品一区 | 91麻豆看国产在线紧急地址 | 国产精品手机看片 | 精品国产乱码久久久久久天美 | 国产视频1区2区 | 最新三级在线 | 天天躁日日躁狠狠躁av麻豆 | 在线草 | 欧美久草网 | 在线成人观看 | 精品视频区 | 91精品国产麻豆 | 在线导航福利 |