新闻资讯
Group news
青岛广盛源肥业有限公司    您的位置: 首页  >  新闻资讯  >  正文

关于Epoll,你应该知道的那些细节

2019年10月12日 文章来源:网络整理 热度:88℃ 作者:刘英

Epoll,位于头文件sys/epoll.h,是Linux系统上的I/O事件通知基础设施。epoll API为Linux系统专有,于内核2.5.44中首次引入,glibc于2.3.2版本加入支持。其它提供类似的功能的系统,包括FreeBSD kqueue,Solaris /dev/poll等。

Epoll API

Epoll API实现了与poll类似的功能:监测多个文件描述符上是否可以执行I/O操作。支持边缘触发ET和水平触发LT,相比poll支持监测数量更多的文件描述符。

以下API用于创建和管理epoll实例:

epoll_create:创建Epoll实例,并返回Epoll实例关联的文件描述符。(最新的epoll_create1扩展了epoll_create的功能)

create_ctl:注册关注的文件描述符。注册于同一epoll实例的一组文件描述符被称为epoll set,可以通过进程对应的/proc/[pid]/fdinfo目录查看。

epoll_wait:等待I/O事件,如果当前没有任何注册事件处于可用状态,调用线程会被阻塞。

水平触发LT与边缘触发ET

Epoll事件分发接口可以使用ET和LT两种模式。两种模式的差别描述如下。

典型场景:

1 管道(pipe)读端的文件描述符(rfd)注册于Epoll实例。

2 写者(Writer)向管道(pipe)写端写2KB的数据。

3 epoll_wait调用结束,返回rfd作为就绪的文件描述符。

4 管道读者(pipe reader) 从rfd读1KB的数据。

5 下一次epoll_wait调用。

如果rfd文件描述符使用EPOLLET(边缘触发)标记加入Epoll接口,第5步对epoll_wait的调用可能会挂住,尽管文件输入缓冲区中仍然有可用数据;与此同时,远端实体由于已经发送数据,可能正在等待回应。其原因是边缘触发模式仅在所监控的文件描述符状态发生变化时才投递事件。所以,第5步的调用方可能最终一直在等待数据到来,但数据其实已经在输入缓存区。经过第2步的写操作和第3步的事件处理,rfd上只会产生一次事件。由于第4步的读操作没有读完全部的缓冲区数据,第5步对epoll_wait的调用可能会永远阻塞。

使用EPOLLET标记时,应该设置文件描述符为非阻塞,以避免阻塞读写,使处理多个文件描述符的任务饿死。因此,使用Epoll 边缘触发(EPOLLET)模式的接口,以下有两点建议:

1 使用非阻塞的文件描述符

2 只有在read或write返回EAGAIN之后,才继续等待事件(调用epoll_wait)

相比之下,当Epoll作为水平触发接口(LT,默认模式)使用时,epoll相当于一个更快的poll,可以用于poll适用的任何场景,因为二者语义相同。

在边缘触发模式下,当收到多个数据块时也可能会产生多个事件,调用方可以通过设置EPOLLONESHOT标记,告诉epoll当通过epoll_wait收到事件时,取消关联的文件描述符。当给epoll设置EPOLLONESHOT标记时,调用方需要通过epoll_ctl对文件描述符设置EPOLL_CTL_MOD标记。

使用范例

当Epoll作为水平触发接口使用时与poll语义相同,而作为边缘触发接口使用时需要注意应用层事件循环的细节,以避免错误。以下举例。设想一个非阻塞的socket为监听者,可以在该socket上调用listen。函数do_use_fd()处理新就绪的文件描述符,直到遇到读(read)或写(write)返回EAGAIN。事件驱动的状态机应用,应该在收到EAGAIN之后记录当前状态,以便在下次调用do_use_fd时,能够继续从之前停止读写数据的地方继续读写(read / write)。

1

2

3

4

5

6

7

8

9

10

11

12

13

14

15

16

17

18

19

20

21

22

23

24

25

26

27

28

29

30

31

32

33

34

35

36

37

38

39

40

41

42

43

44

45

46

47

48

#define MAX_EVENTS 10

structepoll_event ev, events[MAX_EVENTS];

intlisten_sock, conn_sock, nfds, epollfd;

/*Code to set up listening socket, 'listen_sock',

(socket(), bind(), listen()) omitted */

epollfd= epoll_create1(0);

if(epollfd == -1) {

perror("epoll_create1");

exit(EXIT_FAILURE);

}

ev.events= EPOLLIN;

ev.data.fd= listen_sock;

if(epoll_ctl(epollfd, EPOLL_CTL_ADD, listen_sock, &ev) == -1) {

perror("epoll_ctl: listen_sock");

exit(EXIT_FAILURE);

}

for(;;) {

nfds = epoll_wait(epollfd, events, MAX_EVENTS, -1);

if (nfds == -1) {

perror("epoll_wait");

exit(EXIT_FAILURE);

}

for (n = 0; n < nfds; ++n) {

if (events[n].data.fd == listen_sock) {

conn_sock = accept(listen_sock,

(struct sockaddr *) &addr, &addrlen);

if (conn_sock == -1) {

perror("accept");

exit(EXIT_FAILURE);

}

setnonblocking(conn_sock);

ev.events = EPOLLIN | EPOLLET;

ev.data.fd = conn_sock;

if (epoll_ctl(epollfd, EPOLL_CTL_ADD,conn_sock,

&ev) == -1) {

perror("epoll_ctl:conn_sock");

exit(EXIT_FAILURE);

}

} else {

do_use_fd(events[n].data.fd);

}

}

}

上一篇:需要了解的Linux调试器之源码级断点


下一篇:你知道ubuntu14.04版本的重置密码?

友情链接
Links