youyichannel

📌志于道,据于德,依于仁,游于艺!

0%

说说你了解的文件 I/O

文件的读写方式有很多,对于文件 I/O 的分类也有很多,常见的分类:

  • 缓冲和非缓冲 I/O
  • 直接和非直接 I/O
  • 阻塞和非阻塞 I/O
  • 同步和异步 I/O

缓冲和非缓冲 I/O

文件操作的标准库是可以实现数据的缓冲的,根据「是否利用标准库缓冲」,可以把文件 I/O 分为缓冲 I/O 和非缓冲 I/O

  • 缓冲 I/O:利用标准库的缓冲实现文件的加速访问,标准库再通过 syscall 访问文件;
  • 非缓冲 I/O:直接通过 syscall 访问文件,不经过标准库缓冲。

「缓冲」指的是标准库内部实现的缓冲,比如 C 语言 printf() 函数的缓冲区。

直接和非直接 I/O

Linux 内核为了减少磁盘 I/O 次数,会在 syscall 调用后,将用户数据拷贝到 PageCache 中缓存起来,交由 OS 来决定刷盘的时机。

根据「是否利用操作系统的缓存」,可以把文件 I/O 分为直接 I/O 和非直接 I/O

  • 直接 I/O:不发生内核缓存和用户程序之间的数据复制,直接经过文件系统访问磁盘;
  • 非直接 I/O:读操作时,数据从内核缓存拷贝给用户程序;写操作时,数据从用户程序拷贝到内核缓存,再由内核决定缓存数据刷盘的时机。

非直接 I/O 的缓存数据刷盘时机

1)在调用 write 时,如果发现内核缓存的数据太多时,内核会把数据写到磁盘上;

2)用户主动调用 sync,内核缓存会刷盘;

3)当内存十分紧张,无法再分配页面时,内核缓存刷盘;

4)内核缓存数据的缓存时间超过某个时间时,也会把数据刷盘。

阻塞和非阻塞 I/O VS 同步和异步 I/O

同步 / 异步、阻塞 / 非阻塞之间有什么区别呢?先给出解释:

  • 同步 / 异步更关注消息通信机制,即调用方以什么方式获取到结果,是直接返回还是通过回调通知;
  • 阻塞 / 非阻塞更关注线程在等待调用结果时的状态,阻塞会一直占用线程资源,非阻塞发起调用后可以立即返回,线程可以被释放出来做其他的工作。

阻塞 / 非阻塞

阻塞 I/O:当用户程序执行 read 系统调用时,线程会被阻塞,一直等到内核数据准备好,并把数据从内核缓冲区拷贝到应用程序的缓冲区中,当拷贝过程完成,read 才会返回。

也就是说,阻塞等待的是「内核数据准备完成」和「数据从内核态拷贝到用户态」两个过程

非阻塞 I/O:非阻塞的 read 请求在数据未准备好的情况下立即返回,可以继续执行,此时应用程序不断轮询内核,直到数据准备好,内核将数据拷贝到应用程序缓冲区,read 调用才可以获取到结果。

注意:上图中的最后一次 read 调用获取数据的是一个同步的过程,需要等待。

但是这种不断轮询内核效率也不高,于是乎就出现了I/O 多路复用技术,如 `select, poll, epoll,它是通过 I/O 事件分发,当内核数据准备好时,再以事件通知应用程序进行操作。这个做法大大改善了 CPU 的利用率,因为当调用了 I/O 多路复用接口,如果没有事件发生,那么当前线程就会发生阻塞,这时 CPU 会切换其他线程执行任务,等内核发现有事件到来的时候,会唤醒阻塞在 I/O 多路复用接口的线程,然后用户可以进行后续的事件处理。

I/O 多路复用的最大优势在于用户可以在一个线程内同时处理多个 socket 的 I/O 请求。在同步阻塞模型中,必须通过多线程的方式才能达到这个目的。

同步 / 异步

实际上,阻塞 I/O、非阻塞 I/O、基于非阻塞 I/O 的多路复用都是同步调用,因为它们在 read 调用时,内核将数据从内核空间拷贝到应用程序空间这个过程是需要等待的,也就是说这个过程是同步的。

异步 I/O是「内核数据准备好」和「数据从内核拷贝到应用程序」两个过程都不需要等待。

Java I/O 模型

BIO

同 「阻塞 I/O」

NIO

「Java 中的 NIO 模型」

Java NIO Buffer 详解:

AIO

同 「异步 I/O」