因为如今大多数资源都是通过网络访问的:数据库、对象存储和其他微服务。大多数服务器应用程序开发人员在考虑 I/O 时,都会考虑网络 I/O,然而,数据库开发人员还必须考虑文件 I/O。
一般来说,在Linux服务器上访问文件有四种选择:传统读/写、mmap、直接I/O(DIO)读/写和异步直接I/O(AIO/DIO)。
传统读写
是使用read(2)和write(2)系统调用。在现代实现中, read 系统调用(或其众多变体之一 - pread、readv、preadv等)要求内核读取文件的一部分并将数据复制到调用进程的地址空间中。如果所有请求的数据都在页面缓存中,内核将复制它并立即返回;否则,它将安排磁盘将请求的数据读入页面缓存,阻止调用线程,并且当数据可用时,它将恢复线程并复制数据。
使用mmap(2)系统调用将文件内存映射到应用程序地址空间。这会导致地址空间的一部分直接引用包含文件数据的页面缓存页面。完成此准备步骤后,应用程序可以使用处理器的内存读取和内存写入指令访问文件数据。如果请求的数据恰好在缓存中,则完全绕过内核并以内存速度执行读取(或写入)。
传统的读写和 mmap 都涉及内核页面缓存,并将 I/O 调度推迟到内核。当应用程序希望自己调度 I/O 时(原因我们将在后面解释),它可以使用直接 I/O。这涉及使用O_DIRECT标志打开文件;进一步的活动将使用正常的读写系列系统调用,但它们的行为现在已改变:不是访问缓存,而是直接访问磁盘,这意味着调用线程将无条件进入休眠状态。此外,磁盘控制器将绕过内核,将数据直接复制到用户空间。
异步直接 I/O
是直接 I/O
的改进版本,其行为类似,但可防止调用线程阻塞。相反,应用程序线程使用io_submit(2)系统调用来调度直接 I/O 操作,但线程不会被阻塞;I/O 操作与正常线程执行并行运行。单独的系统调用io_getevents(2)用于等待和收集已完成的 I/O 操作的结果。与 DIO 一样,内核的页面缓存被绕过,磁盘控制器负责将数据直接复制到用户空间。