3

五种传统IO模型 - public_tsing

 1 year ago
source link: https://www.cnblogs.com/TssiNG-Z/p/17089759.html
Go to the source link to view the article. You can view the picture content, updated content and better typesetting reading experience. If the link is broken, please click the button below to view the snapshot at that time.
neoserver,ios ssh client

五种传统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网络编程 卷一>>

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


About Joyk


Aggregate valuable and interesting links.
Joyk means Joy of geeK