- 高性能IO底层原理
- IO读写的基础原理
- 内核缓冲区与进程缓冲区
- 典型的系统调用流程
- 五种主要的IO模型
- IO多路复用模型的特点
用户程序进行IO的读写,依赖于底层的IO读写。都会用到read&write两大系统调用,不通 *** 作系统中,IO读写系统调用的名称可能不完全相同,功能基本一致。在用户程序中,无论是Socket的IO还是文件IO *** 作都属于上层应用开发。
read系统调用:并不是直接从物理设备把数据读取到内存中,是把数据从内核缓冲区复制到进程缓冲区
write系统调用:也不是直接把数据写入到物理设备,是把数据从进程缓冲区复制到内核缓冲区
即上层应用无论是调用 *** 作系统的read还是调用 *** 作系统的write,都会涉及到缓冲区。
上层应用的IO *** 作,实际上不是物理设备级别的读写,而是缓存的复制。
read&write两大系统调用,都不负责数据在内核缓冲区和磁盘之间的交换。底层的读写交换是由 *** 作系统内核来完成的。
缓冲区的目的是为了减少频繁与设备之间的物理交换。设备的直接读写,涉及 *** 作系统的中断。
当发生系统中断时,需要保存之前的进程数据和状态等信息,而结束中断之后,还需要恢复之前的进程数据和状态等信息。为了减少这种底层系统的时间损耗,性能损耗,就出行了内存缓冲区。
有了内存缓冲区后,上层应用使用read系统调用时,仅仅是把数据从内核缓冲区复制到上层应用的缓冲区(进程缓冲区);上层应用使用write系统调用时,只需要把数据从进程缓冲区复制到内核缓冲区。底层 *** 作会对内核缓冲区进行监控,等待缓冲区达到一定数量时,再进行IO设备的中断处理,集中执行物理设备的实际IO *** 作,这种机制提示了系统的性能。以至于什么时候中断(读中断,写中断),由 *** 作系统的内核决定,用户程序不需要关心。
从数量上来说,Linux系统中, *** 作系统内核只有一个内核缓冲区。
每个用户程序(进程),有自己的缓冲区,叫做进程缓冲区。
所以用户程序的IO读写程序,在大多数情况下,没有进行实际的IO *** 作,而是在进程缓冲区和内核缓冲区直接之间进行的数据交换。
图示 系统调用read&write流程
例如read系统调用的输入流程
-
等待数据就绪
-
从内核向进程复制数据。
如果read是一个socket。那么具体流程为:
-
等待数据从网络中到达网卡,当等待的分组到达时,数据会被复制到内核中的某个缓冲区。
(这个 *** 作有 *** 作系统完成,用户程序无感知)
-
把数据从内核缓冲区复制到应用进程缓冲区
-
从客户端和服务器端的角度来理解,即一次socket的请求和响应如下
- 客户端请求: linux通过网卡读取到客户端的请求数据,将数据读取到内核缓冲区
- 服务器端获取请求数据:通过read系统调用,从linux内核缓冲区读取数据,再送入Java进程缓冲区
- 服务器端业务处理:Java进程在自己的用户空间处理客户端的请求
- 服务器端返回数据:Java进程处理完成,构建好响应数据。通过write系统调用。将这些数据从用户缓存区写入内核缓冲区
- 发送至客户端:linux内核通过网络IO,将内核缓冲区中的数据写入网卡,网卡通过底层的通信协议,会将数据发送给目标客户端。
-
同步阻塞IO (Blocking IO)
阻塞IO,指需要内核IO *** 作彻底完成后,才返回到用户空间执行用户的 *** 作。
阻塞指的是用户空间程序的执行状态,传统的IO模型都是同步阻塞IO。
Java中默认创建的socket都是阻塞的。
-
同步非阻塞IO (Non-blocking IO)
非阻塞IO,指的是用户程序不需要等待内核IO *** 作彻底完成,可以立即返回执行用户 *** 作,即处于非阻塞的状态。
非阻塞IO要求socket被设置为NONBLOCK
用户程序需要不断的进行IO系统调用,轮询数据是否已经准备好,制度完成IO系统调用为止。
- 优点:每次发起的IO系统调用,在内核等待数据过程中可以立即返回,用户线程不会阻塞,实时性比较好。
- 缺点:不断地轮询内核,占用大量的CPU时间,效率低下。
阻塞和非阻塞
阻塞是指用户空间(调用线程)一直等待,非阻塞指用户空间(调用线程)拿到内核返回的状态值就返回自己的空间。
-
IO多路复用(IO Multiplexing)
避免同步非阻塞IO模型中轮询等待的问题,可以采用IO多路复用模型。
在IO多路复用中,引入了一种新的系统调用,查询IO的就绪状态。在Linux系统中,对应的 *** 作系统调用为select/epoll系统调用。通过该系统调用,一个进程可以监视多个文件描述符,一旦某个描述符就绪(一般是内核缓冲区可读/可写),内存能过将就绪的状态返回给应用程序。随后,应用程序根据就绪的状态,进行对应IO系统调用。
在IO多路复用模型中通过select/epoll系统调用,单个应用程序的进程,可以不断的轮询成百上千个的socket连接,当某个或者某些socket网络连接有IO就绪的状态,就返回对应的可执行的读写 *** 作。
例如发起一个多路复用IO的read读 *** 作系统调用
-
选择器注册
先将read *** 作的目标socket网络连接,提前注册到select/epoll选择器中(Java中对应Selector类)
-
就绪状态的轮询
通过选择器的查询方法,查询所有注册过的socket连接的就绪状态。当其中任何一个socket连接数据准备好了,内核缓冲区有数据(就绪)了,内核就会将该socket连接维护为就绪状态。
通过查询的系统调用,内核会返回一个就绪的socket列表。当用户程序执行select方法,整个线程就会阻塞掉
-
用户线程发起read调用
用户线程获取到就绪状态的socket列表后,依靠socket连接发起read系统调用,用户线程阻塞。内核开始复制数据,将数据从内存缓冲区复制到用户缓冲区。
-
复制完成
复制完成后,内核返回结果,用户线程接触阻塞状态。用户线程获取到了数据,继续向下执行。
-
*** 作系统的内核必须能够提供多路分离的系统调用select/epoll。
-
多路复用也需要轮询,负责select/epoll查询调用的线程,需要不断的进行select/epoll轮询,进而
获取到就绪的socket连接。
-
优点: 与一个线程维护一个连接的阻塞IO模式相比,优势在于一个选择器查询的线程可以同时处理成千上万个连接(Connection)。系统不必创建,维护大量线程。减小系统开销。
-
缺点: 本质上,select/epoll 系统调用也是阻塞式的,属于同步IO。需要等待读写事件就绪后,由系统调用本身进行读写。
-
-
异步IO (Asynchronous IO)
彻底解决线程的阻塞,需要引入异步IO模型。
异步IO,指的是用户空间与内核空间的调用方式反过来,用户空间的线程编程被动接受者,而内核空间成为主动调用者。
类似于Java中比较典型的回调模式,用户空间的线程向内核空间注册了各种IO时间的回调函数,由内核去主动调用。
欢迎分享,转载请注明来源:内存溢出
评论列表(0条)