了解epoll首先必须知道epoll_event这个数据结构:
typedef union epoll_data {
void ptr;
int fd;
__uint32_t u32;
__uint64_t u64;
} epoll_data_t;
struct epoll_event {
__uint32_t events; / Epoll events /
epoll_data_t data; / User data variable /
};
其中events表示data.fd这个文件描述符发生的事件类型。events值包括: EPOLLIN,EPOLLOUT,EPOLLERR,EPOLLHUP,EPOLLPRI,
EPOLLET分别表示:可读事件发生,可写事件发生,错误发生,中断发生,紧急事件发生,边沿触发事件发生。
epoll_data_t中的fd表示关心用户关心的文件描述符,ptr指针的作用是什么呢?
epoll相关的函数重要的API有三个:
int epoll_create(int size);
/*** size可以不给,系统会默认给值,返回一个文件句柄,在/proc/进程id/fd里面 ***/
int epoll_ctl(int epfd, int op, int fd, struct epoll_event *event);
/*** epfd创建好的epoll句柄,
op表示动作:增,删,改,
fd是需要注册的文件句柄,
event是告诉内核需要监听什么事件
***/
int epoll_wait(int epfd, struct epoll_event * events, int maxevents, int timeout);
/***
epfd创建好的epoll句柄,
events内核监听的文件句柄集合,epoll_wait返回的时候将准好的文件事件复制到events里面
maxevents:告诉内核events最大有多大
timeout:epoll_wait的等待时间
***/
这三个API函数是使用最多是函数,需要明白每一个参数的意思和函数的返回值以及函数本身涉及到的内核动作。
epoll的底层有三个东西支撑着它: mmap技术,红黑树和双向链表。
mmap技术将用户和内核共享一片物理地址,是得物理地址对内核和用户而言都是可见的,这样避免了select方式下的fd\_set频繁在用户态和内核态之间的频繁复制的开销。内核存放event事件的struct的时候采用的是红黑树的方式,这样保证了epoll\_ctl注册事件能在O(logn)时间内完成。epoll\_wait返回的事件数量,那么事件本身和事件状态存放在哪里呢?epoll_ctl添加进来的事件都放到了红黑树里面了,同时该事件就和网卡驱动建立了回调关系。ep_poll_callback函数就是那个回调函数,当相应的事件发生的时候该回调函数就会将事件添加到rdlist双向循环链表里面,epoll_wait只需要检测rdlist是否有值,如果有就将其复制到用户态内存中即可。
这里还有一个问题:每一个epoll_event中的events到底设置成什么呢?我们知道epoll有EPOLLET和EPOLLLT(水平触发和边沿触发两种方式)两种触发方式通知你EPOLLIN或者EPOLLOUT事件来了。这两种方式有什么不同呢? 看下别人写的能说明问题的代码:
#include
#include
#includeint main(void)
{
int epfd,nfds;
struct epoll_event ev,events[5];
epfd = epoll_create(1);
ev.data.fd = STDIN_FILENO;
ev.events = EPOLLIN|EPOLLET; //监听读状态同时设置ET模式
epoll_ctl(epfd, EPOLL_CTL_ADD, STDIN_FILENO, &ev);
for(;;) {
nfds = epoll_wait(epfd, events, 5, -1);
for(int i = 0; i < nfds; i++) {
f(events[i].data.fd==STDIN_FILENO)
printf("Welcome Epoll World!\n");
}
}
}
这个的输出是输入一个,就printf打印一次。那如果将ev.events改成默认水平触发模式,输出结果就不是那么回事了。只要标准输入有数据了,那么程序会一直不停地打印 “Welcome Epoll World!”。原因就是因为水平模式会不停将fd的EPOLLIN事件放到rdlsit中,epoll_wait会取出。
ev.events = EPOLLIN;