[转载]浅谈Buffer I/O 和 Direct I/O

通常来说,文件I/O可以分为两种:

  1. Buffer I/O
  2. Direct I/O
    在这里插入图片描述
    在这里插入图片描述

Buffer I/O

缓存 I/O 又被称作标准 I/O,大多数文件系统的默认 I/O 操作都是缓存 I/O。 在 Linux 的缓存 I/O 机制中,这种访问文件的方式是通过两个系统调用实现的:read() 和 write()。调用read()时,如果 操作系统内核地址空间的页缓存( page cache )有数据就读取出该数据并直接返回给应用程序,如果没有就从磁盘读取数据到页缓存。然后再从页缓存拷贝到应用程序的地址空间。调用write()时,数据会先从应用程序的地址空间拷贝到 操作系统内核地址空间的页缓存,然后再写入磁盘。根据Linux的延迟写机制,当数据写到操作系统内核地址空间的页缓存就意味write()完成了,操作系统会定期地将页缓存的数据刷到磁盘上。所以缓存 I/O 有以下这些优点:

  1. 缓存 I/O 使用了操作系统内核的页缓存,保护了磁盘
  2. 缓存 I/O 减少读盘的次数,提高了读取速度

总的来说,Buffer I/O为了提高读写效率和保护磁盘,使用了页缓存机制,不过由于页缓存处于内核空间,不能被应用程序(用户进程)直接寻址,所以还需要将页缓存数据再拷贝到内存对应的用户空间中。这样,需要两次数据拷贝才能完成用户进程对数据的读取操作。写操作也是一样,将页缓存的数据写入磁盘的时候,必须先拷贝到内核空间对应的主存,然后在写入磁盘中。

因此,Buffer I/O 中引入一类特别的操作叫做内存映射文件(mmap),它的不同点在于,中间会减少一层数据从用户地址空间到操作系统地址空间的复制开销 。使用mmap函数的时候,会在当前进程的用户地址空间中开辟一块内存,这块内存与系统的文件进行映射。对其的读取和写入,会转化为对相应文件的操作。 并且,在进程退出的时候,会将变化的内容(脏页)自动回写到对应的文件里面。

Direct I/O

凡是通过直接 I/O 方式进行数据传输,数据均直接在用户地址空间的缓冲区和磁盘之间直接进行传输,中间少了页缓存的支持。操作系统层提供的缓存往往会使应用程序在读写数据的时候获得更好的性能,但是对于某些特殊的应用程序,比如说数据库管理系统这类应用,他们更倾向于选择他们自己的缓存机制,因为数据库管理系统往往比操作系统更了解数据库中存放的数据,数据库管理系统可以提供一种更加有效的缓存机制来提高数据库中数据的存取性能。

Java 中目前是没有直接支持 Direct I/O的,只支持Buffer I/O。我们可以通过JNA 来实现其支持,linux通过将O_DIRECT标志传递给 open()系统调用来实现对Direct I/O 的支持,不过直接操作磁盘,所有写入内存块数量必须是文件系统块大小的倍数,而且要与内存页大小对齐。这些对齐规则参数操作我们可以使用JNA来完成。 JNA 是 Java 中一种用来与本地共享库进行互操作的便捷方式,使用它可以直接调用操作系统本地库。

开源的Jaydio是一个实现了对Direct I/O支持的Java I/O库。https://github.com/smacke/jaydio