I/O 多路复用之 Event Poll

来源:互联网 发布:三菱plc编程教学视频 编辑:程序博客网 时间:2024/05/22 20:57

概述

由于 poll() 和 select() 的局限,Linux 2.6内核引入了 event poll(epoll) 机制。虽然 epoll 的实现比 poll() 和 select() 要复杂得多,epoll 解决了前两个存在的基本性能问题,并增加了一些新的特性。

对于poll() 和 select(),每次调用时都需要所有被监听的文件描述符列表。内核必须遍历所有被监视的文件描述符。当这个文件描述符列表变得很大时——包含几百个甚至几千个文件描述符时——每次调用都要遍历列表就变成了规模上的瓶颈。

epoll 把监听注册从实际监听中分离出来,从而解决了这个问题。一个系统调用会初始化 epoll 上下文,另一个从上下文中加入或删除监视的文件描述符,第三个执行真正的事件等待(event wait)。

Event Poll


创建新的 epoll 实例

通过 epoll_create1() 创建 epoll 上下文:

        #include <sys/epoll.h>

        int epoll_create1 ( int  flags);
        /* deprecated. use epoll_create1() in new code */
        int epoll_create (int size);

调用成功时,epoll_create1() 会创建新的 epoll 实例,并返回和该实例关联的文件描述符。这个文件描述符和真正的文件没有关系,仅仅是为了后续调用 epoll 而创建的。参数 flags 支持修改 epoll 行为,当前,只有 EPOLL_CLOEXEC 是个合法的 flag,它表示进程被替换时关闭文件描述符。

出错时,返回 -1,并设置 errno 为下列值之一:
  
EINVAL        参数 flags 非法
EMFILE        用户打开的文件数达到上限
ENFILE        系统打开的文件数达到上限
ENOMEN     内存不足,无法完成本次操作

epoll_create() 是老版本的epoll_create1()的实现,现在已经废弃。它不接受任何标志位。相反地,它接收 size 参数,该参数没有用。size 之前是用于表示要监视的文件描述符个数;现在,内核可以动态获取数据结构的大小,只需要 size 参数大于 0 即可。如果 size 值小于0,会返回 EINVAL。如果应用所运行的系统其Linux版本低于Linux内核2.6.27 以及 glibc 2.9,应该使用老的 epoll_create() 调用。

epoll 的标准调用方式如下:

        int epfd;
        epfd = epoll_create1 (0);
        if (epfd < 0) {
               perror ("epoll_create1");
        }

当完成监视后,epoll_create1() 返回的文件描述符需要通过 close() 调用来关闭。


控制 epoll

epoll_ctl() 函数可以指向的 epoll 上下文中加入或删除文件描述符:

        #include <sys/epoll.h>
        int epoll_ctl (int epfd,
                             int op,
                             int fd,
                             struct epoll_event *event);

头文件<sys/epoll.h>中定义了 epoll event 结构体:

        struct epoll_event {
               __u32 events;           /* events */
               union {
                     void *ptr;
                     int fd;
                     __u32 u32;
                     __u64 u64;
               } data;
         };

epoll_ctl() 调用如果执行成功,会控制和文件描述符 epfd 关联的 epoll 实例。参数 op 指定对 fd 指向的文件所执行的操作。参数 event 进一步描述 epoll 更具体的行为。

以下是参数 op 的有效值:

EPOLL_CTL_ADD        
把文件描述符 fd 所指向的文件添加到 epfd 指定的 epoll 监听实例集中,监听 event 中定义的事件。

EPOLL_CTL_DEL
把文件描述符 fd 所指向的文件从 epfd 指定的 epoll 监听集中删除。

EPOLL_CTL_MOD
使用 event 指定的更新事件修改在已有 fd 上的监听行为。

epoll_events 结构体中的 events 变量列出了在指定文件描述符上要监听的事件。多个监听事件可以通过位或运算同时指定。以下为有效的 events 值:

EPOLLERR
文件出错。即使没有设置,这个事件也是被监听的。

EPOLLET
在监听文件上开启边缘触发(edge-triggered) ,默认是条件触发(level-trigered) 。

EPOLLHUP
文件被挂起。即使没有设置,这个事件也是被监听的。

EPOLLIN
文件未阻塞,可读

EPOLLONESHOT
在事件生成并处理后,文件不会再被监听。必须通过EPOLL_CTL_MOD 指定新的事件掩码,以便重新监听文件。

EPOLLOUT
文件未阻塞,可写。

EPOLLPRI
存在高优先级的带外(out-of-band)数据可读。

event_poll 中的 data 变量是由用户私有使用。当接收到请求的事件后,data 会被返回给用户。通常的用法是把 event.data.fd 设置为 fd,这样可以很容易查看哪个文件描述符触发了事件。

当成功时,epoll_ctl() 返回 0。失败时,返回 -1,并相应设置 errno 为下列值:

EBADF         epfd 不是有效的 epoll 实例,或者 fd 不是有效的文件描述符。
EEXIST       op 值设置为 EPOLL_CTL_ADD, 但是 fd 已经与 epfd 关联。
EINVAL       epfd 不是 epoll 实例, epfd 和 fd 相同, 或 op 无效。
ENOENT      op 值设置为 EPOLL_CTL_MOD 或 EPOLL_CTL_DEL,但是 fd 没有和 epfd 关联。
ENOMEN     没有足够的内存来处理请求。
EPERM         fd 不支持 epoll。

在下面的例子中,在 epoll 实例 epfd 中加入 fd 所指向文件的监听事件,代码如下:

        struct epoll_event event;
        int ret;

        event.data.fd = fd;          /* return the fd to us later (from epoll_wait) */
        event.events = EPOLLIN | EPOLLOUT;

        ret = epoll_ctl(epfd, EPOLL_CTL_ADD, fd, &event);
        if (ret) {
               perror("epoll_ctl");
        }

相反,从 epoll 实例epfd中删除在fd上的一个监听事件,代码如下:

        struct epoll_event event;
        int ret;

        ret = epoll_ctl(epfd, EPOLL_CTL_DEL, fd, &event);
        if (ret) {
               perror("epoll_ctl");
        }

需要注意的是,当 op 设置为 EPOLL_CTL_DEL 时,由于没有提供事件掩码,event参数可能会是NULL。但是,在2.6.9以前的内核版本中,会检查该参数是否非空。为了和老的内核版本保持兼容,必须传递一个有效的非空指针,该指针不能只是声明。内核2.6.9版本修复了这个bug。


等待 epoll 事件

系统调用 epoll_wait() 会等待和指定 epoll 实例关联的文件描述符上的事件:

        #include <sys/epoll.h>
        int epoll_wait(int epfd,
                              struct epoll_event *events,
                              int maxevents,
                              int timeout);

当调用 epoll_wait() 时,等待 epoll 实例 epfd 中的文件 fd 上的事件,时限为 timeout 毫秒。成功时,events 指向描述每个事件的epoll_event结构体的内存,且最多可以有maxevents个事件,返回值是事件数;出错时,返回 -1,并将errno设置为以下值:

EBANF       epfd 是一个无效的文件描述符。
EFAULT     进程对events所指向的内存没有写权限。
EINTR       系统调用在完成前发生信号中断或超时。
EINVAL     epfd 不是有效的epoll实例,或者maxevents值小于或等于0。

如果timeout为0,即使没有事件发生,调用也会立即返回0。如果timeout为 -1,调用将一直等待到有事件发生才返回。
当调用返回时,epoll_event结构体中的events变量描述了发生的事件。data 变量保留了用户在调用epoll_ctl()前的所有内容。

epoll_wait()例子如下:
       
       #define MAX_EVENTS     64
       
       struct epoll_event *events;
       int nr_events, i, epfd;
     
       events = malloc(sizeof(struct epoll_event) * MAX_EVENTS);
       if (!events) {
             perror("malloc");
             return 1;
       }
       
       nr_events = epoll_wait(epfd, events, MAX_EVENTS, -1);
       if (nr_events < 0) {
              perror("epoll wait");
              free(events);
              return 1;
       }
    
       for (i = 0; i < nr_events; ++i) {
             printf("event = %d on fd = %d\n", events[i].events, events[i].data.fd);
             /* 
             * We now can, per events[i].events, operate on 
             * events[i].data.fd without blocking.
             */
        }

        free (events);
 




0 0