I/O 模型 06 - 直接 I/O

I/O 模型 06 - 直接 I/O

在 Linux 2.6 中,内存映射和直接访问文件没有本质上的差异, 因为数据从进程用户态空间到磁盘都要经过两次复制,即在磁盘与内核缓冲区之间以及在内核缓冲区与用户态内存空间。

引入内核缓冲区的目的在于提高磁盘文件的访问性能, 因为当进程需要读取磁盘文件时,如果文件内容已经在内核缓冲区中,那么就不需要再次访问磁盘; 而当进程需要向文件中写入数据时,实际上只是写到内核缓冲区便告诉进程已经写成功,而真正写入磁盘是通过一定的策略进行延迟的。

然而,对于一些较复杂的应用,比如数据库服务器,它们为了充分提高性能,希望绕过内核缓冲区, 由自己在用户态空间实现并管理 I/O 缓冲区,包括缓存机制和写延迟机制等,以支持独特的查询机制, 比如数据库可以根据更加合理的策略来提高查询缓存命中率。 另一方面,绕过内核缓冲区也可以减少系统内存的开销,因为内核缓冲区本身就在使用系统内存。

Liunx 提供了对这种需求的支持,即在 open() 系统调用中增加参数选项 O_DIRECT, 用它打开的文件便可以绕过内核缓冲区的直接访问,这样便有效避免了 CPU 和内存的多余时间开销。

在 MySQL 中,对于 Innodb 存储引擎,其自身可以进行数据和索引的缓存管理,所以它对于内核缓冲区的依赖不是那么重要。 MySQL 提供了一种实现直接 I/O 的方法,在 my.cnf 配置中,可以在分配 Innodb 数据空间文件的时候, 通过使用 raw 分区跳过内核缓冲区,实现直接 I/O,这在 MySQL 的官方手册上略有介绍,但是不多, 主要涉及 raw 分区的使用,这是一种特别的分区,它不能像其他分区格式(比如 ext2)一样通过 mount 来挂载使用, 而是需要使用 raw 设备管理程序来加载。为 Innodb 使用 raw 分区的配置如下所示:

innodb_data_file_path = /dev/sda5:100Gnewraw

假设 /dev/sda5 是 raw 分区,在分区大小后面增加 newraw 关键字,便可以将 raw 分区作为数据空间, 并由 Innodb 存储引擎直接访问。具体的操作还涉及其他一些步骤,这里就不具体罗列了。

另外,MySQL 还提供了 innodb_flush_method 配置选项,你可以将它设置为如下形式: innodb_flush_method = O_DIRECT

这样便可以通过另一种方式来实现直接 I/O。

顺便提一下,与 O_DIRECT 类似的一个选项是 O_SYNC,后者只对写数据有效, 它将写入内核缓冲区的数据立即写入磁盘,将机器故障时数据的丢失减少到最小,但是它仍然要经过内核缓冲区。

Ref

摘自《构建高性能 Web 站点》第 3 章 服务器并发处理能力 3.6 I/O 模型