select,poll,epoll都是IO多路复用的机制。I/O多路复用就是通过一种机制,一个进程可以监视多个描述符,一旦某个描述符就绪,能够通知程序进行相应的读写操作。其中epoll是在2.6内核中提出的,是之前的select和poll的增强版本。相对于select和poll来说,epoll更加灵活
一.epoll的相关操作概览:
1.int epoll_create(int size);
int epoll_create(int size);
epoll_create 创建一个epoll对象,一般epollfd = epoll_create()
2.int epoll_ctl(int epfd, int op, int fd, struct epoll_event *event);
int epoll_ctl(int epfd, int op, int fd, struct epoll_event *event);
第一个参数是创建的epoll对象
第二个参数是具体的操作类型,如添加、删除
epoll_ctl(epollfd, EPOLL_CTL_ADD, socket, EPOLLIN);//添加
epoll_ctl(epollfd, EPOLL_CTL_DEL, socket, EPOLLOUT);//删除
第三个是需要监听的fd,第四个是监听事件,该事件结构如下
struct epoll_event {
__uint32_t events; /* Epoll events */
epoll_data_t data; /* User data variable */
};
__uint32_t events:
其中events是以下宏的集合
EPOLLIN :表示对应的文件描述符可以读(包括对端SOCKET正常关闭);
EPOLLOUT:表示对应的文件描述符可以写;
EPOLLPRI:表示对应的文件描述符有紧急的数据可读(这里应该表示有带外数据到来);
EPOLLERR:表示对应的文件描述符发生错误;
EPOLLHUP:表示对应的文件描述符被挂断;
EPOLLET: 将EPOLL设为边缘触发(Edge Triggered)模式,这是相对于水平触发(Level Triggered)来说的。
EPOLLONESHOT:只监听一次事件,当监听完这次事件之后,如果还需要继续监听这个socket的话,需要再次把这个socket加入到EPOLL队列里
epoll_data_t data:
data可以存储对应文件描述符(fd)和任意自定义函数或类(ptr)
typedef union epoll_data
{
void *ptr;
int fd;
uint32_t u32;
uint64_t u64;
} epoll_data_t;
(3)int epoll_wait(int epfd, struct epoll_event * events, int maxevents, int timeout);
int epoll_wait(int epfd, struct epoll_event * events, int maxevents, int timeout);
这里是epoll主要的特点,select函数只是当有可读描述符后返回但并不知道具体是那个描述符,返回后还需要轮询,文件描述符数量越多,性能越差。而epoll的epoll_wait函数可以返回具体可操作的文件描述符,存储在events里。Maxevents告诉内核这个events有多大。
二.实现机制
来看具体的实现机制
1.某一进程调用epoll_create方法时,Linux内核会创建一个eventpoll结构体
struct eventpoll{
....
struct rb_root rbr; //红黑树的根节点,存储着所有添加到epoll中的需要监控的事件
struct list_head rdlist; //双链表,存放着将要通过epoll_wait返回给用户的满足条件的事件
....
};
rbr是该epoll对象监听的所有文件对象,rdlist是放进去的有事件发生的文件描述符。
在epoll中,对于每一个事件,都会建立一个epitem结构体
struct epitem{
struct rb_node rbn;//红黑树节点
struct list_head rdllink;//双向链表节点
struct epoll_filefd ffd; //事件句柄信息
struct eventpoll *ep; //指向其所属的eventpoll对象
struct epoll_event event; //期待发生的事件类型
}
双链表与存储着所有监听的红黑树里都是存放的epitem结构数据
2.epoll_ctl
将epoll_event结构拷贝到内核空间中并且判断加入的fd是否支持poll结构,并且从epfd->file->privatedata获取event_poll对象,根据op区分是添加删除还是修改,
对于修改删除是先在红黑树查找是否有相对应的fd,没找到就返回错误
对于插入,先创建一个与fd对应的epitem结构,并且初始化相关成员,然后指定了调用poll_wait时的回调函数用于数据就绪时唤醒进程,(其内部,初始化设备的等待队列,将该进程注册到等待队列)完成这一步, 我们的epitem就跟这个socket关联起来了, 当它有状态变化时,会通过ep_poll_callback()来通知.最后调用加入的fd的file operation->poll函数(最后会调用poll_wait操作)用于完成注册操作.
最后将epitem结构添加到红黑树中
3.epoll_wait
当调用epoll_wait检查是否有事件发生时,只需要检查eventpoll对象中的rdlist双链表中是否有epitem元素即可。如果rdlist不为空,则把发生的事件复制到用户态,同时将事件数量返回给用户
三.ET和LT的区别
首先从epoll的监听流程看起,当有文件描述符的状态改变时,会导致fd上的回调函数被调用,回调函数会将fd对应的epitem添加到rdlist,此时epoll_wait会检测到rdlist不为空,进程被唤醒,结束等待后会将rdlist中的epitem复制到txlist中,然后清空rdlist,epoll_wait继续阻塞等待,接着扫描txlist中的每个epitem,调用相关fd,然后有调用poll取得新的epoll_event,之后将取得的epoll_event发送到用户空间,用户空间会对这个事件进行处理,如果fd是LT模式监听会将其重新加入到rdlist,如果是ET就不在加入到rdlist
所以ET和LT的区别就在ET中的事件只添加到rdlist一次,处理后就删除了,所有当有数据读或写时,要一次读完或一次写完。LT是可以把事件多次添加到rdlist中,只有你主动的删除它。