文章目录
一、epoll初识
二、epoll的相关系统调用
1.epoll_create
2.epoll_ctl
3.epoll_wait
三、epoll工作原理
四、epoll的工作方式
本文主要介绍了epoll内部工作机制,如何达到高性能的多路转接。技术有限,如有错误请指正。参考文献:深入揭秘 epoll 是如何实现 IO 多路复用的
一、epoll初识
按照man手册:epoll是为处理大批量句柄而改进的poll,被公认为linux2.6下性能最好的io就绪通知方法
二、epoll的相关系统调用
epoll有三个相关调用
1.epoll_create
int epoll_create(int size);
创建一个epoll句柄,从Linux2.6.8之后,size参数是被忽略的,设置为>0即可。
用完之后,必须调用close()关闭
2.epoll_ctl
int epoll_ctl(int epfd,int op,int fd,struct epoll_event *event);
epoll的事件注册函数
- 它不同于select()是在监听事件时告诉内核要监听什么类型的事件,而是在这里先注册要监听的事件类型
- epfd是创建的返回值
- 第二个参数表示动作,用三个宏来表示:EPOLL_CTL_ADD注册新的fd到epfd中 ;EPOLL_CTL_MOD修改已经注册的fd监听事件; EPOLL_CTL_DEL:从epfd中删除一个fd
- 第三个参数是需要监听的fd
- 第四个参数是告诉内核需要监听什么事件
struct 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_data_t data;} __EPOLL_PACKED;
- events可以是几个宏的集合:
EPOLLIN:表示对应的fd可以读(包括对端SOCKET正常关闭) EPOLLOUT:表示对应的fd可以写 EPOLLPRI:表示对应的fd有紧急的数据可读(这里对应表示有带外数据到来) EPOLLERR:表示对应的fd描述符发生错误 EPOLLHUP:表示对应的fd被挂断 EPOLLET:将epoll设置为边缘触发(et),这是相对于水平触发(lt)来说的 EPOLLONESHOT:只监听一次事件,当监听完这次事件后,如果还需要继续监听socket的话,需要再次把这个socket加入到epoll队列中
3.epoll_wait
int epoll_wait(int epfd,struct epoll_event *events,int maxevents,int timeout);
收集在epoll监控的事件中已经发送的事件
- 参数events是分配好的epll_event结构体数组
- epoll将会把发生的事件赋值到events数组中(events不可以是空指针,内核只负责把数据复制到这个events数组中,不会去帮助我们在用户态中分配内存)
- maxevents告知内核这个events有多大,这个Maxevnets的值不能大于创建epoll_create()时的size
- 参数timeout是超时事件(ms,0会立即返回,-1永久阻塞)
- 函数调用成功,返回对应io上已经准备好的fd数目,如返回0表示已经超时,返回小于0表示函数失败
三、epoll工作原理
struct eventpoll
{/* 红黑树的根节点,这棵树中存储着所有添加到epoll当中需要监控的事件*/struct rb_tree;struct list_head rdlist;
};
每一个epoll对象都有一个独立的eventpoll结构体,用于存放通过epoll_ctl方法向epoll对象中添加进来的事件
这些事件都会挂载在红黑树中,如此重复添加的事件就可以通过红黑树而高效的识别出来(红黑树的插入效率是logn,其中n为树的高度)
而所有添加到epoll事件中都会与设备(网卡)驱动程序建立回调关系,也就是说,当响应的事件发生时,都会调用这个回调方法
这个回调方法在内核中叫ep_poll_callback,它会将发生的事件添加到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; //期待发生的事件类型
}
当调用epoll_wait检查是否有事件发生时,只需要检查eventpoll对象中的rdlist双链表中是否有epitem元素即可
如果rdlist不为空,则把发生的事件复制到用户态,同时将事件数量返回给用户,这个操作的事件复杂度为O(1)
epoll的优点
- 接口使用方便:虽然拆分成了三个函数,但是使用起来更高效,不需要每次循环都设置关注的fd,也做到了输入输出参数分离
- 数据拷贝轻量:只在合适的时候调用EPOLL_CTL_ADD将fd拷贝到内核中,这个操作并不频繁(而select,poll每次都需要进行拷贝)
- 事件回调机制:避免使用遍历,而是使用回调函数的方式,将就绪的fd结构加入到就绪队列中,epoll_wait返回直接访问就绪队列就知道哪些fd就绪,这个操作事件复杂度O(1),即使fd很多,效率也不会收到影响
- 没有数量限制,文件描述符数目无上限
有的地方说,epoll使用了内存映射机制,内存映射机制是:内核直接将就绪队列通过mmap的方式映射到用户态,避免了拷贝内存这样的额外性能开销。这种说法是不准确的,我们定义的struct epoll_evnet是我们在用户空间中分配好的内存,势必还是需要将内核中的数据结构拷贝到这个用户空间的内存中的。