何为IO
操作系统是一种非常特殊的软件。这种软件对上服务着我们用户的程序(Application),对下管理着我们的硬件。I/O 描述了操作系统与外部设备之间通信的过程。
用户态和内核态
现代操作系统,有一个“保护”的概念,也就是会把一些特别危险的操作、特别核心的操作只能我自己来运行。用户想来访问硬盘、网络,想访问一些其他敏感的硬件,不好意思只能通过我的内核来访问,你不可以直接去访问
Inter CPU 指令级别分别是 Ring0~Ring3,Ring0 级别最高,Ring3 级别最低。 在 Linux 系统中,Ring0 作为内核态,Ring3 作为用户态。
假如 Linux 进程的有 4GB 地址空间,3G-4G 部分大家是共享的,是内核态的地址空间,这里存放在整个内核的代码和所有的内核模块,以及内核所维护的数据。用户运行一个程序,该程序所创建的进程开始是运行在用户态的,如果要执行文件操作,网络数据发送等操作,必须通过 write,send 等系统调用,这些系统调用会调用内核中的代码来完成操作,这时必须切换到 Ring0,然后进入 3GB-4GB 中的内核地址空间去执行这些代码完成操作,完成后,切换回 Ring3,回到用户态。这样,用户态的程序就不能随意操作内核地址空间,具有一定的安全保护作用。
用户态是没有权限访问内核态管理的相关资源(网络、磁盘、cpu 等),因此内核态提供一系列的接口,提供给用户态来访问这些资源,这些接口被称为系统调用
常见网络IO模型
UNIX 系统下, IO 模型一共有 5 种: 同步阻塞 I/O、同步非阻塞 I/O、I/O 多路复用、信号驱动 I/O 和异步 I/O。
BIO
BIO属于同步阻塞IO。同步阻塞 IO 模型中,应用程序发起 read 系统调用后,会一直阻塞,直到在内核把数据拷贝到用户空间。在客户端连接数量不高的情况下,是没问题的。但是,当面对十万甚至百万级连接的时候,传统的 BIO 模型是无能为力的。
NIO
同步非阻塞 IO 模型中,应用程序会一直发起 read 调用,等待数据从内核空间拷贝到用户空间的这段时间里,线程依然是阻塞的,直到在内核把数据拷贝到用户空间。
相比于同步阻塞 IO 模型,同步非阻塞 IO 模型确实有了很大改进。通过轮询操作,避免了一直阻塞。
但是,这种 IO 模型同样存在问题:应用程序不断进行 I/O 系统调用轮询数据是否已经准备好的过程是十分消耗 CPU 资源的。
IO多路复用
IO 多路复用模型中,线程首先发起 select 调用,询问内核数据是否准备就绪,等内核把数据准备好了,用户线程再发起 read 调用。read 调用的过程(数据从内核空间->用户空间)还是阻塞的。
目前支持 IO 多路复用的系统调用,有 select,epoll 等等。select 系统调用,是目前几乎在所有的操作系统上都有支持
- select 调用 :内核提供的系统调用,它支持一次查询多个系统调用的可用状态。几乎所有的操作系统都支持。
- epoll 调用 :linux 2.6 内核,属于 select 调用的增强版本,优化了 IO 的执行效率。
IO 多路复用模型,通过减少无效的系统调用,减少了对 CPU 资源的消耗。
AIO
异步 IO (AIO)是基于事件和回调机制实现的,也就是应用操作之后会直接返回,不会堵塞在那里,当后台处理完成,操作系统会通知相应的线程进行后续的操作
目前来说 AIO 的应用还不是很广泛。Netty 之前也尝试使用过 AIO,不过又放弃了。这是因为,Netty 使用了 AIO 之后,在 Linux 系统上的性能并没有多少提升。
JAVA中的网络IO
java中的BIO和AIO不在多说,和上面讲的是一致的。
Java 中的 NIO ,有一个非常重要的选择器 ( Selector ) 的概念,也可以被称为 多路复用器。通过它,只需要一个线程便可以管理多个客户端连接。当客户端数据到了之后,才会为其服务。