深入理解Java AIO(三)

深入理解Java AIO(三)—— Linux中的AIO实现

 

我们调用的Java AIO底层也是要调用OS的AIO实现,而OS主要也就Windows和Linux这两大类,当然还有Solaris和mac这些小众的。

  • Windows 操作系统中,提供了一个叫做 I/O Completion Ports 的方案,通常简称为 IOCP,操作系统负责管理线程池,其性能非常优异,所以在 Windows 中 JDK 直接采用了 IOCP 的支持。
  • 而在 Linux 中其实也是有AIO 的实现的,但是限制比较多,性能也一般,所以 JDK 采用了自建线程池的方式,也就是说JDK并没有用Linux提供的AIO。但是本文主要想聊的,就是Linux中的AIO实现。

 

其实Linux的AIO简单来说是依赖epoll实现的伪AIO,一张图加一小段说明就能讲完。但是我们还是先讲一些前置知识来铺垫一下,最后再聊Linux的AIO。(当然本文不会深入到源码级别,只是讲大概的流程,要说为什么,当然是因为看C语言写的东西实在是太累了,特别是看源码,何况还可能混了汇编)

 

前置知识

 

缓存 I/O

缓存 I/O 又被称作标准 I/O,大多数文件系统的默认 I/O 操作都是缓存 I/O。在 Linux 的缓存 I/O 机制中,操作系统会将 I/O 的数据缓存在文件系统的页缓存( page cache )中,也就是说,数据会先被拷贝到操作系统内核的缓冲区中,然后才会从操作系统内核的缓冲区拷贝到应用程序的地址空间。

缓存的两个阶段

  1. 等待数据准备(等待数据进入内核缓存)
  2. 将数据从内核缓存拷贝到进程内存中

进程的阻塞

正在执行的进程,由于期待的某些事件未发生,如请求系统资源失败、等待某种操作的完成、新数据尚未到达或无新工作做等,则由系统自动执行阻塞原语(Block),使自己由运行状态变为阻塞状态。可见,进程的阻塞是进程自身的一种主动行为,也因此只有处于运行态的进程(获得CPU),才可能将其转为阻塞状态。当进程进入阻塞状态,是不占用CPU资源的。

 

Linux的五种IO模型

 

阻塞 I/O

深入理解Java AIO(三)

 

比较简单就不介绍了,有看过我之前的深入理解NIO系列就会懂

 

非阻塞 I/O

 深入理解Java AIO(三)

 

 简单说就是用户进程需要不断的主动询问kernel数据好了没有。

 

I/O 多路复用

深入理解Java AIO(三)

 

 我们的select、poll和epoll都属于IO多路复用的模型

这里提一下,它们都属于同步非阻塞IO,为什么同步我们后面会解释。

 

信号驱动式IO

深入理解Java AIO(三)

首先我们允许Socket进行信号驱动IO,并安装一个信号处理函数,进程继续运行并不阻塞。当数据准备好时,进程会收到一个SIGIO信号,可以在信号处理函数中调用I/O操作函数处理数据。

 

异步 I/O

深入理解Java AIO(三)

 

用户进程发起read操作之后,立刻就可以开始去做其它的事。而另一方面,从kernel的角度,当它受到一个asynchronous read之后,首先它会立刻返回,所以不会对用户进程产生任何block。然后,kernel会等待数据准备完成,然后将数据拷贝到用户内存,当这一切都完成之后,kernel会给用户进程发送一个signal,告诉它read操作完成了。

这就是我们本节的重点,我们心心念念的AIO了。虽然在第三节才讲有点晚,但是铺垫了这么多,我想要说的,就是AIO它到底异步在哪里?或者说什么是异步?

 

异步和同步的区别

在说明synchronous IO和asynchronous IO的区别之前,需要先给出两者的定义。POSIX的定义是这样子的:

  • A synchronous I/O operation causes the requesting process to be blocked until that I/O operation completes;
  • An asynchronous I/O operation does not cause the requesting process to be blocked;

两者的区别就在于synchronous IO做”IO operation”的时候会将process阻塞。按照这个定义,之前所述的blocking IO,non-blocking IO,IO multiplexing都属于同步IO。

有人会说,non-blocking IO并没有被block啊。这里有个非常“狡猾”的地方,定义中所指的”IO operation”是指真实的IO操作,就是例子中的recvfrom这个system call。non-blocking IO在执行recvfrom这个system call的时候,如果kernel的数据没有准备好,这时候不会block进程。但是,当kernel中数据准备好的时候,recvfrom会将数据从kernel拷贝到用户内存中,这个时候进程是被block了

而asynchronous IO则不一样,当进程发起IO 操作之后,就直接返回再也不理睬了,直到kernel发送一个信号,告诉进程说IO完成。在这整个过程中,进程完全没有被block。

 

深入理解Java AIO(三)

通过上面的图片,可以发现non-blocking IO和asynchronous IO的区别还是很明显的。在non-blocking IO中,虽然进程大部分时间都不会被block,但是它仍然要求进程去主动的check,并且当数据准备完成以后,也需要进程主动的再次调用recvfrom来将数据拷贝到用户内存。而asynchronous IO则完全不同。它就像是用户进程将整个IO操作交给了他人(kernel)完成,然后他人做完后发信号通知。在此期间,用户进程不需要去检查IO操作的状态,也不需要主动的去拷贝数据。    

Linux的AIO实现

最后我们再来聊一下Linux的伪AIO实现:

深入理解Java AIO(三)

 

  1. 主线程往epoll内核事件表上注册socket上的读就绪
  2. 主线程调用epoll_wait等待socket上有数据可读
  3. 当socket上有数据可读时,epoll_wait通知主线程,主线程从socket上循环读取数据,直到没有更多数据可读,然后将读取到的数据封装成一个请求对象并插入到请求队列
  4. 睡眠在请求队列上的某个工作线程被唤醒,他获得请求对象并处理客户请求,然后往epoll内核事件表中注册socket上的写就绪
  5. 主线程调用epoll_wait等待socket可写
  6. 当socket可写时,epoll_wait通知主线程,主线程往socket上写入服务器处理客户请求结果

 

深入理解Java AIO(三)

 (解开个几把,太难了,有些东西还是含糊不清没挖透)

 

 


 

参考资料: https://segmentfault.com/a/1190000003063859 https://www.cnblogs.com/tianzeng/p/10779425.html
上一篇:BIO、NIO、AIO有什么区别?


下一篇:受”误解“的Java AIO