如何完成一次 IO
哪个男孩不想来一场异步非阻塞的甜蜜恋爱?
21 点,你打开微信,开心地对女孩说:“晚上好”。女孩说:“我在洗澡”。
你抱着手机等待晚点聊,此刻,你是阻塞的,也是同步的。为什么?
写在前面
谈起 IO, Javaer 会说起 BIO、NIO、AIO,也会提到同步异步、阻塞非阻塞。但到底什么是 IO, IO又是怎么完成的?
1. 什么是 I/O
学术的说 I/O 是信息处理系统(计算机)与外界(人或信息处理系统)间的通信。如计算机,即 CPU 访问任何寄存器和 Cache 等封装以外的数据资源都可当成 I/O ,包括且不限于内存,磁盘,显卡。
软件开发中的 I/O 则常指磁盘、网络 IO。
Unix 系统下,不论是标准输入还是借助套接字接受网络输入,都有两个步骤:
- 等待数据准备好**(Waiting for the data to be ready)**
- 从内核向进程复制数据**(Copying the data from the kernel to the process)**
等待数据准备好还比较好理解,从内核向进程复制数据是什么东东?
2. 计算机内存
计科、软工的同学都知道,修电脑是我们的对口工种,加内存条这种事更是入职基本要求。这的内存条又叫物理内存。那一般来说,有实就有虚,所以就有虚拟内存。
2.1 虚拟内存
操作系统中进程间是共享 CPU 和内存资源的,就需要一套完善的内存管理机制防止进程间内存泄漏。
现代操作系统提供了对主存的抽象概念:虚拟内存(Virtual Memory)。虚拟内存为每个进程提供一个一致私有的地址空间,每个进程拥有一片连续完整的内存空间,让进程有种在独享主存的美好错觉。
实际上,虚拟内存通常被分隔成多个物理内存碎片,还有部分暂存在外部磁盘存储器,在需要时进行数据交换,加载到物理内存中来。大致如下图:
当用户进程发出内存申请请求,系统会为进程分配虚拟地址,并创建内存映射放入页表中,如果对应的数据不在物理内存上就会发生缺页异常,需要把进程需要的数据从磁盘上拷贝到物理内存中。
2.2 内核空间与用户空间
上图有看到,虚拟内存分为内核和用户地址空间两部分,因为需要避免用户进程直接操作内核。
操作系统的核心是内核,独立于普通应用程序,可访问受保护的内存空间,也可访问底层硬件设备。 在 Linux 系统中,内核模块运行在内核空间,当进程经过系统调用而陷入内核代码中执行时,称进程处于内核运行态,即内核态;反之,运行在用户空间执行用户自己的代码时,处于用户态。
上图可以看到,应用程序和内核间无法直接通信,必须通过系统调用,而系统调用的成本很高。
当用户进程想要执行 IO 操作时,由于没有执行这些操作的权限,只能发起系统调用请求操作系统帮忙完成。而系统调用会产生中断陷入到内核,也就是进行了一次上下文切换操作。
2.3 进程切换
到了内核,为了控制进程执行,内核必须有能力挂起正在 CPU 上运行的进程,并恢复以前挂起的某个进程的执行。这种行为被称为进程切换。
需要注意:这里的进程切换和上文 2.2 的用户态转内核态的上下文切换并不一样,后者只是同一个进程的 CPU 权限等级的修改。
进程是资源分配的基本单位, 因此进程切换时,需保存、装载各种状态数据等资源, 代价就比较高。