FantasticMao 个人博客

<Unix 环境高级编程> 谈及的几种 IO 模型

发表于 2020/11/18,预计阅读时间 1 分钟

前言

本篇文章记录自己在阅读 《Unix 环境高级编程》 时候,关于书中谈及的几种 IO 模型的一些笔记。

阻塞 IO

Unix 系统中的文件 IO 函数 openreadwrite,以及标准 IO 库中的函数 getc / putcfgets / fputsfread / fwrite 都属于阻塞 IO,这些函数可能会使进程永远阻塞,例如在以下场景时:

通常会基于「多线程」的方式来避免在单线程中发生阻塞,不过这种方式将会需要处理线程之间的同步工作。

非阻塞 IO

非阻塞 IO 可以使 openreadwrite 这类 IO 操作不会永远阻塞,在这类操作无法完成的时候,它会立即给调用者返回一个错误,表示该操作如果继续执行的话将会导致阻塞。

对于一个给定的描述符,有两种方式可以将其指定为非阻塞 IO:

  1. 对于尚未打开的描述符,可以在调用 open 函数时指定 O_NOBLOCK 标志;
  2. 对于已经打开的描述符,可以通过调用 fcntl 函数来添加 O_NOBLOCK 标志。

通常会基于「轮询」的方式来使用非阻塞 IO 进行读写,不过这种方式将会消耗额外的 CPU 资源。

IO 多路复用

以 telnet 命令为例来讲述多路复用机制的适用场景。首先介绍 telnet 命令的工作机制:

  1. 本地机器上的 telnet 命令从终端(标准输入)中读取用户的输入,将所读的数据写入到网络连接,然后发送给远程的 telnetd 守护进程;
  2. 远程机器上的 telnetd 守护进程会将用户的输入发送给 shell,并且会将 shell 的输出写回到网络连接,然后发送给本地的 telnet 命令;
  3. 本地机器上的 telnet 命令从网络连接中读取 shell 的输出,将所读的数据写回到终端(标准输出)。

image

从 telnet 命令的工作机制可以得知,telnet 命令在工作时会有两个输入和两个输出。在实现 telnet 命令时,对于 IO 模型的选择应该考虑以下几点:

对于 telnet 命令的工作机制,一种比较好的实现方式是使用 IO 多路复用。在使用 IO 多路复用的时候,开发者需要先构建一个感兴趣的描述符(通常不止一个)列表,然后调用一个函数,该函数会在列表中某个描述符已经准备好进行 IO 操作时才会返回。用于执行 IO 多路复用的函数有 pollpselectselect,当从这些函数返回时,进程会被告知哪些描述符已经准备好可以进行 IO 操作。

异步 IO

信号机制提供了一种以异步形式通知某种事件已经发生的方法。由 BSD 和 System V 派生的所有系统都提供了某种形式的异步 IO,支持使用一个信号(在 System V 中是 SIGPOLL,在 BSD 中是 SIGIO)通知进程,对某个描述符所关心的某个事件已经发生。

但是,这些形式的异步 IO 是受限制的:它们不能用在所有的文件类型上,而且只能使用一个信号。因此如果要对一个以上的描述符进行异步 IO 时,那么进程在接收到该信号时不会知道该信号对应了哪一个描述符。

参考资料