Unix IO模型

Unix IO模型简介

一个输入操作通常包含俩个阶段

  • 等待数据准备好
  • 从内核向进程复制数据

对于一个套接字上的输入操作,第一步通常设计等待数据从网络中到达.当锁等待分组到达时,它被复制到内核的某个缓冲区,.第二部就是把数据从内核缓冲区复制到应用缓冲区

unix下面有五中I/O模型

  • 阻塞式I/O
  • 非阻塞式I/O
  • I/O复用(select和poll)
  • 信号驱动式I/O(SIGIO)
  • 异步IO(AIO)

阻塞式I/O

应用进程阻塞,知道数据复制到应用进程缓冲区中才返回

应该注意到,在阻塞的过程中,其它程序还可以执行,因此阻塞不意味着整个操作系统都被阻塞。因为其他程序还可以执行,因此不消耗 CPU 时间,这种模型的执行效率会比较高。

下图中,recvfrom 用于接收 Socket 传来的数据,并复制到应用进程的缓冲区 buf 中。这里把 recvfrom() 当成系统调用。

ssize_t recvfrom(int sockfd, void *buf, size_t len, int flags, struct sockaddr *src_addr, socklen_t *addrlen);

非阻塞式I/O

应用进程执行系统调用后,内核返回一个错误码,可以继续执行,但是需要不断的执行系统调用来获取I/O是否完成,这种方式称为轮询(polling)

由于CPU要处理更多的系统调用 ,因此这种模型是比较低效的

IO复用(事件驱动IO)

使用select或者poll等待数据,并且可以等待多个套接字中的任何一个变为可读,这一过程会被阻塞, 当某一个套接字可读时返回.之后再使用recvfrom把数据从内核复制到进程中.

它可以让单个进程具有处理多个I/O实践的能力,又被称为Event Driven I/O,即事件驱动I/O

如果一个Web服务没有I/O复用,那么每一个Socket连接都需要创建一个线程去处理,如果同时又几万个个连接,那么就需要常见相同数量的线程.并且相比于多进程和多线程技术,I/O复用不需要线程的切换的开销,系统开销更小

信号驱动I/O

应用进程使用sigaction系统调用,内核立即返回,应用进程可以继续执行,也就是说等待数据阶段应用进程是非阻塞的.内核在数据到达时向应用进程发送SIGIO信号,应用进程收到之后再信号处理程序中调用recvfrom将数据从内核复制到应用程序中

相比于非阻塞I/O的轮询方式,信号驱动I/Ocpu利用率更高

异步IO

进行aio_read系统调用会立刻返回,应用进程继续执行,不会被阻塞,内核会在所有操作完成后向应用进程发送信号.

异步I/O和信号驱动I/O的区别在于, 异步I/O的信号是通知应用进程I/O完成,而信号驱动I/O是通知应用进程可以开始I/O

IO模型比较

  • 同步 I/O: 应用进程在调用 recvfrom 操作时会阻塞。
  • 异步 I/O: 不会阻塞。

阻塞式 I/O、非阻塞式 I/O、I/O 复用和信号驱动 I/O 都是同步 I/O,虽然非阻塞式 I/O 和信号驱动 I/O 在等待数据阶段不会阻塞,但是在之后的将数据从内核复制到应用进程这个操作会阻塞。

五大I/O模型比较:前四种 I/O 模型的主要区别在于第一个阶段,而第二个阶段是一样的: 将数据从内核复制到应用进程过程中,应用进程会被阻塞。

IO多路复用

epoll的描述符事件有俩种出发模式:LT(level trigger)和ET(edge trigger)

LT模式

当epoll_wait()检测到描述符事件达到时,将此事件通知进程,进程可以不立即处理该事件,下次调用epoll_wait()会再次通知进程,是默认的一种方式,并且同时支持Blocking和No-Blocking

ET模式

和LT模式不同的是,通知之后必须立刻处理事件,下次调用epoll_wait()时不会再得到事件通知

很大程度上减少了 epoll 事件被重复触发的次数,因此效率要比 LT 模式高。只支持 No-Blocking,以避免由于一个文件句柄的阻塞读/阻塞写操作把处理多个文件描述符的任务饿死。

应用场景

很容易产生一种错觉认为只要用 epoll 就可以了,select 和 poll 都已经过时了,其实它们都有各自的使用场景。

select应用场景

select 的 timeout 参数精度为 1ns,而 poll 和 epoll 为 1ms,因此 select 更加适用于实时要求更高的场景,比如核反应堆的控制。

select 可移植性更好,几乎被所有主流平台所支持。

poll 应用场景

poll 没有最大描述符数量的限制,如果平台支持并且对实时性要求不高,应该使用 poll 而不是 select。

需要同时监控小于 1000 个描述符,就没有必要使用 epoll,因为这个应用场景下并不能体现 epoll 的优势。

需要监控的描述符状态变化多,而且都是非常短暂的,也没有必要使用 epoll。因为 epoll 中的所有描述符都存储在内核中,造成每次需要对描述符的状态改变都需要通过 epoll_ctl() 进行系统调用,频繁系统调用降低效率。并且epoll 的描述符存储在内核,不容易调试

epoll 应用场景

只需要运行在 Linux 平台上,并且有非常大量的描述符需要同时轮询,而且这些连接最好是长连接。

Last modification:December 27, 2022
如果觉得我的文章对你有用,请随意赞赏