epoll与fork
? ? ? ? ?使用epoll時,如果在調用epoll_create之后,調用了fork創建子進程,那么父子進程雖然有各自epoll實例的副本,但是在內核中,它們引用的是同一個實例。子進程向自己的epoll實例添加、修改和刪除文件描述符時,是可以影響到父進程的epoll_wait的。所以會發生意想不到的問題,分情況看一下:
?
? ? ? ? ?1:向子進程中的epoll實例添加描述符,描述符事件觸發后,也會影響到父進程的epoll實例,代碼如下:
#define MAXEVENTS 20int listenfd; struct epoll_event events[MAXEVENTS];int epfd = epoll_create(MAXEVENTS);if((pid = fork()) < 0) return;if(pid == 0) {listenfd = socketfd();struct epoll_event lisevent;lisevent.events = EPOLLIN;lisevent.data.fd = listenfd;res = epoll_ctl(epfd, EPOLL_CTL_ADD, listenfd, &lisevent); }while(1) {res = epoll_wait(epfd, events, MAXEVENTS, -1);for(i = 0; i < res; i++){connectfd = accept(events[i].data.fd, (struct sockaddr *)&clientaddr, (socklen_t *)&addrlen);if(connectfd < 0){perror("accept error");continue;}printf("connect from %s\n", inet_ntop(AF_INET, &(clientaddr.sin_addr), addrbuf, 20));close(connectfd);} }
???????? 上述代碼中,在fork之前創建epoll實例,然后在子進程中,創建監聽socket,并且加入到epoll實例中。父子進程同時在epoll實例上調用epoll_wait等待連接的到來。如果此時客戶端建鏈,則打印如下:
accept error: Bad file descriptor accept error: Bad file descriptor …… accept error: Bad file descriptor connect from 127.0.0.1? ? ? ? ?也就是說,連接到來時,盡管是在子進程中創建的監聽套接字,加入到子進程中的epoll實例中。但是父子進程中epoll實例都會收到觸發的事件,二者的epoll_wait都會停止阻塞,開始調用accept。
???????? 父進程調用accept失敗,打印出Bad file descriptor錯誤,是因為在父進程中,根本沒有監聽套接字。所以,只要子進程沒有調用accept成功,則該連接事件就會一直觸發,從而父進程一直打印accept錯誤信息,直到子進程調用accept成功,打印出connect from 127.0.0.1。
?
???????? 2:在fork之前,創建epoll實例、監聽套接字listenfd,并將listenfd加入到epoll實例中。然后父子進程一起等待事件的觸發,代碼如下:
#define MAXEVENTS 20int listenfd; struct epoll_event events[MAXEVENTS];int epfd = epoll_create(MAXEVENTS);listenfd = socketfd(); struct epoll_event lisevent; lisevent.events = EPOLLIN; lisevent.data.fd = listenfd;res = epoll_ctl(epfd, EPOLL_CTL_ADD, listenfd, &lisevent);if((pid = fork()) < 0) return;while(1) {res = epoll_wait(epfd, events, MAXEVENTS, -1);for(i = 0; i < res; i++){printf("[%s]before accept\n", pid?"father":"child");connectfd = accept(events[i].data.fd, (struct sockaddr *)&clientaddr, (socklen_t *)&addrlen);printf("[%s]after accept\n", pid?"father":"child");if(connectfd < 0){perror("accept error");continue;}printf("connect from %s\n", inet_ntop(AF_INET, &(clientaddr.sin_addr), addrbuf, 20));close(connectfd);} }
???????? 上述代碼在fork之前創建好epoll實例和監聽套接字,然后調用fork,父子進程在各自的epoll實例上等待事件的發生,如果此時到來了一個客戶端連接,則打印如下:
[father]before accept [father]after accept connect from 127.0.0.1 [child]before accept???????? 可見,到來的連接觸發的事件,會同時通告給被父子進程的epoll實例。父進程調用accept得到該連接,而子進程調用accept時,連接已經被取走了,所以子進程中的accept阻塞。
?
???????? 總結:在fork之前創建的epoll實例,盡管分別處于父子進程各自的空間中,但是它們在底層引用的同一個內核結構。所以,當事件發生時,會同時通告給父子進程中的epoll實例。這其實算是epoll設計上的一個缺陷,應該避免在fork之前創建epoll實例,或者在fork之后,關閉原epoll實例,重新創建本進程的epoll實例。這一點在libev的文檔中有所提及:
? ? ? ? The biggest issue is fork races, however - if a program forks then?both parent and child process have to recreate the epoll set, which can take considerable time (one syscall per file descriptor) and is of course hard to detect.
?
?
參考:http://pod.tst.eu/http://cvs.schmorp.de/libev/ev.pod
轉載于:https://www.cnblogs.com/gqtcgq/p/7247110.html
總結
以上是生活随笔為你收集整理的epoll与fork的全部內容,希望文章能夠幫你解決所遇到的問題。
- 上一篇: 对某人失望心寒的说说 无法诉说的委屈的句
- 下一篇: 图形学 网址暂存