五种传统I/O模型

作者:tsing

本文地址:https://www.cnblogs.com/TssiNG-Z/p/17089759.html

简介

提起I/O模型, 就会说到同步/异步/阻塞/非阻塞乱七八糟一大堆, 这里简单整理一下, 做个备忘.

正文

传统I/O模型一共有5种 : 阻塞I/O, 非阻塞I/O, 多路I/O复用, 信号驱动I/O, 异步I/O.

为了更好的理解同步和异步的区别, 这里我们引入两个概念 : 用户进程 和 内核, 通过这两个概念, 我们尝试对I/O进行一个肤浅的解释和理解: 当我们在进程中通过系统API进行系统调用,将内核中的数据读入准备好的buffer时, 由于系统调用, 进程会进入阻塞, 当所数据被成功读入了buffer后,系统调用结束, 上述这个读取的过程, 我们可以理解为一次Input(Output则反过来).

而同步和异步的区别的关键点就在于, 有没有系统调用导致进程进入了阻塞.

下面我们回到传统I/O模型, 解释一下每种模型的行为(为了便于理解, 下面用read代表从内核读取数据的系统调用):

  1. 阻塞I/O : 在需要数据时, 直接调用read, 要求从内核中读取一定量的数据, 此时进程一直阻塞在这里(等待内核数据准备就绪), 直到操作完成后, read返回读入的字节数(或者被信号打断).

  2. 非阻塞I/O : 在需要数据时, 调用read, 若此时内核中的数据没有准备好, read会直接返回一个类似false的值, 表明数据没准备好, 传统的代码结构中, 我们会不断调用read, 直至read返回类似true的值, 表明数据读取成功. 在这种调用场景下, CPU通常会处于高占用状态.

  3. 多路I/O复用 : 即通过select/poll/epoll(epoll是在linux内核2.6版本之后加入的, win32和bsd中并没有epoll这个接口, win32有性能更好IOCP, bsd中相似的接口则是kqueue)来检查一个或多个设备文件描述符是否处于可读(ready)状态, 这三种调用中select和poll采用轮询机制, epoll使用内核回调, 所以epoll(O(1))的性能会比select/poll(O(n))要好, 当上述接口成功返回后, 我们就可以直接对返回的设备文件描述符进行read, 在这种场景下, 程序则阻塞在上述三个系统调用中的某一个中, 而不是read这个系统调用.

  4. 信号驱动I/O : 顾名思义, 通过向内核注册一个特定信号的回调函数, 当I/O准备就绪时, 内核会向进程发送特定信号, 该信号会被注册好的回调函数处理, 回调函数中则包含了read系统调用等操作来将数据读入buffer.

  5. 异步I/O : 可以简单理解为对信号驱动I/O的一种升级, 在进行I/O操作之前, 先准备好buffer和回调接口, 将buffer直接通过aio_read(asynchronous I/O)接口提交给系统内核, 内核会帮忙完成数据的拷贝工作, 当数据准备完成时, 回调接口会被调用, 在该回调中可以直接对buffer进行操作来处理数据(内核帮忙read过了), 而在此期间, 程序没有因为系统调用进入过阻塞状态. win32的IOCP就是一种异步模型, boost的asio库也是如此.

总结

我们可以将I/O模型分为两类 : 同步I/O和异步I/O, 其中同步I/O的关键在于系统调用导致进程进入阻塞, 故而上述的 阻塞I/O, 非阻塞I/O, 多路复用I/O 和 信号驱动I/O都属于同步I/O, 异步I/O则可以参考win32 IOCP和boost asio.

参考文献:

  • <<UNIX网络编程 卷一>>

以上, 如有错误疏漏或疑问, 欢迎指正讨论, 转载请注明.

02-11 13:17