阻塞非阻塞,同步异步,select、poll和epoll总结

参考文章

IO模型

  1. 阻塞IO
  2. 非阻塞IO
  3. IO多路复用
  4. 信号驱动IO
  5. 异步IO

其中前4种是同步,第5种是异步

在进行网络IO的时候会涉及用户态和内核态,数据会在用户态和内核态发生交换。
整个过程可以分为:

  1. 用户态等待内核态数据准备好(这个决定是否阻塞和非阻塞)
  2. 将数据从内核态拷贝到用户态(这个决定同步和异步)

阻塞IO:
1. 用户态等待内核态数据可读(阻塞)
2. 当数据可读时,等待内核态将数据拷贝至用户态(同步)

同步非阻塞IO:
1. 无论数据有没有准备好,都会立即返回,然后间隔一段时间进行轮训,数据有没有准备好,直到准备好进行第二阶段(非阻塞)
2. 当数据可读时,等待内核态将数据拷贝至用户态(同步)

IO多路复用:
和阻塞IO类似,区别是第一阶段,监听数据是否可读:

  • 阻塞IO:通过一个线程来同时监听多个描述符,只要任何一个满足就绪条件,那么内核态就返回,一个线程只能处理一个IO请求
  • IO多路复用:一个线程可以同时处理多个IO请求,然后交到后面的线程池里处理,其实现依赖于操作系统的select、poll和epoll

异步IO:
1. 无论数据有没有准备好,都会立即返回,直到数据准备完成,系统执行callback通知程序(非阻塞)。
2. 无论数据有没有拷贝完成,都会立即返回,直到数据拷贝完成,系统执行callback通知程序(异步)。

select、poll、epoll

select:

  1. 被监控的fds(描述符)集合限制为1024。
  2. 需要将描述符集合从用户空间拷贝到内核空间。
  3. 当有描述符可操作的时候都需要遍历一下整个描述符集合才能知道哪个是可操作的,效率很低。

poll 对于select的改进:
监控的fds(描述符)集合限制被取消。

epoll 对于 poll 的改进:
参考连接
- 1024数量限制的问题,通过epoll_create方法来创建一个epoll句柄,这个句柄监听的描述符的数量不再有限制。
- 文件描述符频繁从用户空间拷贝到内核空间的问题,通过观察select的操作会发现描述符从用户空间到内核空间拷贝发生在调用select方法的时候,只要没有注册新的事件或者取消注册事件,每次拷贝的描述符都是一样的。因此epoll引入了epoll_ctl调用,该方法用于注册新事件和取消注册事件。而在epoll_wait的时候并不会拷贝描述符,描述符始终存在于内核空间,当需要修改的时候只要调用epoll_ctl修改一下内核的描述符即可。如此一来便省去了描述符来回拷贝的开销。
- 文件描述符可操作的时候遍历整个描述符集合的问题,在调用epoll_ctl注册感兴趣的事件的时候,实际上会为设置的事件添加一个回调函数,当对应的感兴趣的事件发生的时候,回调函数就会触发,然后将自己加到一个链表中。epoll_wait函数的作用就是去查看这个链表中有没有已经准备就绪的事件,如果有的话就通知应用程序处理,如此操作epoll_wait只需要遍历就绪的事件描述符即可。

epoll 边缘触发和水平触发

  • LT水平触发是缺省的工作方式,并且同时支持block和no-blocksocket。在这种做法中,内核告诉你一个文件描述符是否就绪了,然后你可以对这个就绪的fd进行IO操作。如果你不作任何操作,内核还是会继续通知你的。
  • ET边缘触发是高速工作方式,只支持no-blocksocket。在这种模式下,当描述符从未就绪变为就绪时,内核会通知你一次,并且除非你做了某些操作导致那个文件描述符不再为就绪状态了,否则不会再次发送通知。

可以看到,本来内核在被DMA中断,捕获到IO设备来数据后,只需要查找这个数据属于哪个文件描述符,进而通知线程里等待的函数即可,但是,LT要求内核在通知阶段还要继续再扫描一次刚才所建立的内核fd和io对应的那个数组,因为应用程序可能没有真正去读上次通知有数据后的那些fd,这种沟通方式效率是很低下的,只是方便编程而已。

猜你喜欢

转载自blog.csdn.net/qq_17612199/article/details/80302992