叙述一下 Unix 下为解决不同 I/O 问题所设计的 I/O 模型
前言
说到IO模型,都会牵扯到同步、异步、阻塞、非阻塞这几个词。从词的表面上看,很多人都觉得很容易理解。但是细细一想,却总会发现有点摸不着头脑。因此想梳理一下IO模型,来加深对这些知识的理解
需要说明以下几点:
- IO有内存IO、网络IO和磁盘IO三种,通常我们说的IO指的是后两者
- 阻塞和非阻塞,是函数/方法的实现方式,即在数据就绪之前是立刻返回还是等待,即发起IO请求是否会被阻塞
- 以文件IO为例,一个IO读过程是文件数据从磁盘→内核缓冲区→用户内存的过程
- 同步与异步的区别主要在于数据从内核缓冲区→用户内存这个过程需不需要用户进程等待,即实际的IO读写是否阻塞请求进程
- 网络IO把磁盘换做网卡即可
开篇
在 Unix 系统下,不论是标准输入还是借助套接字接受网络输入,其实都会有两个步骤,很多文章都提到
- 等待数据准备好(Waiting for the data to be ready)
- 从内核向进程复制数据(Copying the data from the kernel to the process)
用户空间和内核空间
OS 的核心是内核,可以访问底层硬件设备,为了保证用户进程不能直接操作内核从而保证内核的安全,OS 将虚拟空间划分为两部分,一部分为内核空间,一部分为用户空间。
内核空间中存放的是内核代码和数据,例如 Linux 的 OS 和驱动便运行在内核空间,可以操作底层硬件,如果从磁盘读取数据,那么数据会被先载入内核空间的缓冲区中;而进程的用户空间中存放的是用户程序的代码和数据,通常来讲就是应用程序常驻的区域。
因此整个 Linux 内部结构可以分为三部分,从最底层到最上层依次是:硬件、内核空间、用户空间。如下图:
二者间无法直接通信,必须通过系统调用,一般来说系统调用的成本很高,涉及CPU上下文切换的成本
内核态和用户态
- 当一个进程经过系统调用而陷入内核代码中执行时,称进程处于内核运行态,简称内核态
- 当进程在执行用户自己的代码时,则称其处于用户运行态,简称用户态
高性能的Server有什么特点
说完上面的之后,可能疑惑这和 RPC 的通信设计有什么关系呢?其实正是由于这种内存空间的划分,所以 I/O 一般会在两个地方阻塞,一个是等待数据报到达时,一个是从内核空间拷贝到用户空间时,而阻塞多数情况下我们是无法接受的,因为其损耗性能。
而高性能的 server 到底在关注什么?一句话总结: 用尽可能少的系统开销处理尽可能多的连接请求。因此诞生了不同的 I/O 模型,它们的不同点总结起来就是对这两个阻塞阶段的处理方式不同
模型
Unix 下存在五种 I/O 模型:
- 阻塞 I/O
- 非阻塞 I/O
- I/O 复用(select和poll)
- 信号驱动 I/O(SIGIO)
- 异步 I/O
阻塞 I/O
水平有限,可以阅读下方链接即可