I/O多路复用
I/O 多路复用的本质,是通过一种机制(系统内核缓冲I/O数据),让单个进程可以监视多个文件描述符,一旦某个描述符就绪(一般是读就绪或写就绪),能够通知程序进行相应的读写操作。
所谓I/O多路复用指的是这样一个过程:
我们拿到了一堆文件描述符(不管是网络相关的、还是磁盘文件相关等等,任何文件描述符都可以)
通过调用某个函数告诉内核:“这个函数你先不要返回,你替我监视着这些描述符,当这堆文件描述符中有可以进行I/O读写操作的时候你再返回”
当调用的这个函数返回后我们就能知道哪些文件描述符可以进行I/O操作了。
也就是说通过I/O多路复用我们可以同时处理多路I/O。
epoll机制可以用来进行I/O多路复用
epoll 的核心数据结构是:1个红黑树和1个链表。还有3个核心API
熟悉一下epoll
epoll
epoll_create(int size)
创建一个epoll的句柄,size用来告诉内核这个监听的数目一共有多大
epoll_ctl(int epfd, int op, int fd, struct epoll_event *event)
第一个参数是epoll_create()的返回值 第二个参数表示动作 EPOLL_CTL_ADD:注册新的fd到epfd中 EPOLL_CTL_MOD:修改已经注册的fd的监听事件 EPOLL_CTL_DEL:从epfd中删除一个fd 第三个参数是需要监听的fd 第四个参数是告诉内核需要监听什么事,在被监测的文件描述符上实际发生的事件。
epoll_wait(int epfd, struct epoll_event * events, int maxevents, int timeout)
第1个参数 epfd是 epoll的描述符 第2个参数 events则是分配好的 epoll_event结构体数组,epoll将会把发生的事件复制到 events数组中 第3个参数 maxevents表示本次可以返回的最大事件数目,通常 maxevents参数与预分配的events数组的大小是相等的 第4个参数 timeout表示在没有检测到事件发生时最多等待的时间(单位为毫秒),如果 timeout为0,则表示 epoll_wait在 rdllist链表中为空,立刻返回,不会等待;timeout = -1表示调用将一直阻塞,直到有文件描述符进入ready状态或者捕获到信号才返回
epoll event
EPOLLIN:描述符处于可读状态 EPOLLOUT:描述符处于可写状态 EPOLLET:将epoll event通知模式设置成edge triggered EPOLLONESHOT:第一次进行通知,之后不再监测 EPOLLHUP:本端描述符产生一个挂断事件,默认监测事件 EPOLLRDHUP:对端描述符产生一个挂断事件 EPOLLPRI:由带外数据触发 EPOLLERR:描述符产生错误时触发,默认检测事件
epoll流程
Epoll流程:
当某个进程调用epoll_create方法时,内核会创建一个eventpoll对象(也就是程序中epfd所代表的对象)。eventpoll对象也是文件系统中的一员,和socket一样,它也会有等待队列。
创建epoll对象后,可以用epoll_ctl添加或删除所要监听的socket。以添加socket为例,如下图,如果通过epoll_ctl添加sock1、sock2和sock3的监视,内核会将eventpoll添加到这三个socket的等待队列中
当socket收到数据后,中断程序会操作eventpoll对象,而不是直接操作进程。
当socket收到数据后,中断程序会给eventpoll的“就绪列表”添加socket引用。如下图展示的是sock2和sock3收到数据后,中断程序让rdlist引用这两个socket。
eventpoll对象相当于是socket和进程之间的中介,socket的数据接收并不直接影响进程,而是通过改变eventpoll的就绪列表来改变进程状态。
当程序执行到epoll_wait时,如果rdlist已经引用了socket,那么epoll_wait直接返回,如果rdlist为空,阻塞进程。
假设计算机中正在运行进程A和进程B,在某时刻进程A运行到了epoll_wait语句。如下图所示,内核会将进程A放入eventpoll的等待队列中,阻塞进程。
当socket接收到数据,中断程序一方面修改rdlist,另一方面唤醒eventpoll等待队列中的进程,进程A再次进入运行状态(如下图)。也因为rdlist的存在,进程A可以知道哪些socket发生了变化。