1. 概述
I/O输入/输出(Input/Output),在POSIX兼容的系统上,例如Linux系统,I/O操作可以有多种方式,比如DIO(Direct I/O),AIO(Asynchronous,I/O 异步I/O),Memory-Mapped I/O(内存映设I/O)等,不同的I/O方式有不同的实现方式和性能,在不同的应用中可以按情况选择不同的I/O方式。
本文我们就来详细介绍一下 UNIX 系统中有哪些 IO 模型以及他们的通信方式
2. 应用
UNIX系统将所有的外部设备都看作一个文件来看待,所有打开的文件都通过文件描述符来引用。文件描述符是一个非负整数,它指向内核中的一个结构体。当打开一个现有文件或创建一个新文件时,内核向进程返回一个文件描述符。而对于一个socket的读写也会有相应的文件描述符,称为socketfd(socket描述符)。
在UNIX系统中,I/O输入操作(例如标准输入或者套接字的输入)通常包含以下两个不同的阶段:
- 等待数据准备好
- 从内核向进程复制数据
3. UNIX 下的 IO 模型
3.1. 下面介绍五种 IO 模型:
- 阻塞式IO
- 非阻塞式IO
- IO复用(select 和 poll)
- 信号驱动式IO(SIGIO)
- 异步IO(POSIX的aio_系列函数)
3.2. 阻塞式IO模型
阻塞式IO模型是最常用的IO模型。 默认情况下,所有套接字都是阻塞的,如下图所示:
这样的IO模型中,系统调用会从应用进程空间切换到内核空间中运行一段时间后再切换回来。 只有当数据报到达并且被复制到应用进程缓冲区中或者发生错误才返回,最常见的错误返回是被信号中断。
3.3. 非阻塞式IO模型
非阻塞式IO并不让进程睡眠,而是在数据报没有准备好的时候由内核立刻返回一个错误。 当一个进程循环调用非阻塞式IO等待数据报时,我们称之为“轮询”。 这样做会耗费大量的CPU时间,通常只在专门提供某一功能的系统中才会用到。
3.4. IO复用模型
IO复用的系统调用有select和poll。 系统阻塞在这两个系统调用上,而不是阻塞在真正的IO系统调用上。 select 调用等待数据报套接字变为可读,然后调用真正的IO系统调用去进行IO操作,将所读的数据写入应用程序缓冲区中。 使用 IO 复用模型的好处在于可以同时等待多个描述符就绪,甚至可以实现复杂的等待条件。
等待多个描述符的另一种实现是创建多个线程,每个线程使用一个阻塞式IO系统调用去等待一个描述符。
3.5. 信号驱动式IO模型
我们也可以设定 SIGIO 信号的信号处理方式,然后让内核在描述符就绪时发送 SIGIO 信号通知我们。 这种方式就是信号驱动式IO。 信号驱动式IO模型的优势在于等待数据报到达期间进程不必被阻塞,一旦数据报准备好,可以立即进行处理。
3.6. 异步IO模型
aio_开头的一些列系统调用实现了异步IO模型。 这些函数的工作机制是:告知内核启动某个操作,并让内核在整个操作完全进行完成之后通知进程。 信号驱动式IO是在IO操作进行之前通知进程进行相应的操作,而与此不同,异步IO模型让内核完成全部的缓冲区复制工作,直到全部工作完成才去通知进程(比如产生某个信号)