如何使用POSIX实现O_DIRECT?

6

直接I/O是拷贝较大文件的最高效方式,因此我希望将该功能添加到一个程序中。

Windows在Win32的CreateFileA()中提供了FILE_FLAG_WRITE_THROUGHFILE_FLAG_NO_BUFFERING。自Linux 2.4.10起,open()O_DIRECT标志

是否有一种可移植的POSIX方法可以实现相同的结果? 就像Win32 API在Windows XP到Windows 11上的工作方式一样,在所有UNIX类系统中进行直接I/O,并以一种可靠的可移植方式。


3
如果是这样,为什么我们需要一个特定于Linux的标志呢? - n. m.
@n.m. 我在想是否有可能(即使是通过某种迂回的方式,而不是简单的基于开放API)。 - An Ant
可能是重复的。https://dev59.com/tXDYa4cB1Zd3GeqPE9yg - Nadeem Taj
2个回答

4

没有POSIX标准支持直接IO。

截至2023年1月,至少存在两种不同的API和行为。Linux、FreeBSD和显然IBM的AIX使用O_DIRECT标志来执行open(),而Oracle的Solaris则在已打开的文件描述符上使用directio()函数。

Linux对POSIX open()函数O_DIRECT标志的使用在Linux open()手册页中有记录:

O_DIRECT(自Linux 2.4.10起)

尝试最小化对此文件进行I/O的缓存效应。通常情况下,这会降低性能,但在特殊情况下非常有用,例如应用程序自己进行缓存https://en.wikipedia.org/wiki/QFSwhen时。文件I/O直接完成到/从用户空间缓冲区。单独使用O_DIRECT标志会努力实现数据同步传输,但不提供O_SYNC标志的保证,即数据和必要元数据将被传输。为保证同步I/O,必须除了使用O_DIRECT外还需使用O_SYNC。有关详细讨论,请参见下面的NOTES。

Linux没有明确指定直接IO如何与同一文件上打开的其他描述符交互,或者使用mmap()映射文件时会发生什么;也没有对直接IO读取或写入操作的对齐或大小限制。根据我的经验,这些都是特定于文件系统的,并且随着时间的推移而改进/变得不那么受限制,但大多数Linux文件系统需要页面对齐的IO缓冲区,而许多(多数?所有?)(曾经?现在仍然?)需要页面大小的读取或写入。

FreeBSD遵循Linux模型:通过将 O_DIRECT 标志传递给 open()

O_DIRECT 可用于最小化或消除读写的缓存效应。 系统将尝试避免缓存您读取或写入的数据。 如果它无法避免缓存数据,则会将数据对缓存的影响最小化。 不慎使用此标志会严重降低性能。

OpenBSD不支持直接IO。在OpenBSD的open()OpenBSD的'fcntl()`手册中都没有提到直接IO。

IBM的AIX 似乎支持Linux类型的O_DIRECT标志来执行open(), 但实际发布的IBM AIX手册似乎并不普遍可用。

SGI的Irix也支持Linux风格的O_DIRECT标志来执行open()

O_DIRECT

若设定,则所有在此文件描述符上进行的读写操作都将直接执行到/从用户程序缓冲区,前提是满足适当的大小和对齐要求。有关如何确定对齐约束条件的信息,请参见fcntl(2)手册中的F_SETFLF_DIOINFO命令。 O_DIRECT是 Silicon Graphics 扩展,仅受支持于本地 EFS 和 XFS 文件系统以及远程 BDS 文件系统。

值得注意的是,在 Linux 上的 XFS 文件系统起源于 SGI 的 Irix。

Solaris 使用了完全不同的接口。Solaris 使用 特定的directio() 函数,以在每个文件上设置直接 IO

描述
directio()函数提供了对系统关于应用程序在访问与打开文件描述符fildes相关联的数据时预期行为的建议。系统使用这些信息来优化对文件数据的访问。尽管这可能会影响其他操作的性能,但directio()函数对数据的语义没有影响。
建议参数是针对每个文件保留的;调用directio()的最后一个调用者为使用与fildes相关联的文件的所有应用程序设置建议。
建议的值在中定义。
DIRECTIO_OFF
当应用程序访问文件数据时,应用程序获得默认的系统行为。
当应用程序从文件中读取数据时,数据首先被缓存在系统内存中,然后复制到应用程序的缓冲区(参见read(2))。如果系统检测到应用程序正在顺序读取文件,则系统将异步地从文件中“预读”到系统内存中,以便数据立即可用于下一个read(2)操作。
当应用程序将数据写入文件时,数据首先被缓存在系统内存中,并在稍后的时间写入设备(参见write(2))。当可能时,系统通过将数据缓存在内存页中来增加write(2)操作的性能。数据被复制到系统内存,并且write(2)操作立即返回给应用程序。数据稍后会异步写入设备。当可能时,缓存的数据被“聚集”到大块中,并以单个写操作写入设备。
DIRECTIO_OFF的系统行为可能会随时更改。
DIRECTIO_ON
系统表现得好像应用程序在不久的将来不会重用文件数据。换句话说,文件数据不会缓存在系统的内存页中。
当使用read(2)和write(2)操作访问数据时,尽可能地在应用程序的内存和设备之间直接读取或写入数据。当无法进行这样的传输时,系统会切换回默认行为,但只对该操作有效。一般情况下,当应用程序的缓冲区对齐在两字节(short)边界上,文件的偏移量在设备扇区边界上,并且操作的大小是设备扇区的倍数时,传输是可能的。
在与fildes相关联的文件映射(参见mmap(2))时,此建议将被忽略。
DIRECTIO_ON的系统行为可能会随时更改。
请注意,在Solaris上的行为也是不同的:如果任何进程通过直接IO启用了文件,那么访问该文件的所有进程都将通过直接IO进行访问(Solaris 10+对直接IO没有对齐或大小限制,因此在直接IO和“正常”IO之间切换不会破坏任何东西*)。如果一个文件通过mmap()映射,那么该文件的直接IO将被完全禁用。

* - 这并不是完全正确的 - 如果您正在使用SAMFS或QFS文件系统以共享模式,并从文件系统的活动元数据控制器(其中文件系统必须通过Solaris forcedirectio挂载选项进行挂载,以便在群集中的一个系统上通过直接IO完成所有访问),如果您使用directio(fd, DIRECTIO_OFF)禁用文件的直接IO,则会损坏文件系统。如果您在QFS元数据控制器上进行数据库恢复,Oracle自己的顶级RAC数据库就会这样做,您将最终得到一个损坏的文件系统。


3
简短的回答是不行。
IEEE 1003.1-2017(目前 POSIX 标准,据我所知)没有提及任何直接 I/O 的指令,如 O_DIRECT。尽管如此,初步浏览告诉我,GNU/Linux 和 FreeBSD 支持 O_DIRECT 标志,而 OpenBSD 则不支持。
除此之外,似乎并非所有文件系统都支持 O_DIRECT,因此即使在 GNU/Linux 系统上,你知道你的 open() 实现将识别该指令,仍然不能保证你可以使用它。
归根结底,我只能看到可移植的直接 I/O 是通过运行时检查程序所在平台是否支持它;虽然你可以进行编译时检查,但我不建议这样做,因为文件系统可能会改变,或者你的目的地可能不在操作系统驱动器上。你可能会很幸运地发现已经有项目开始这样做了,但我有点怀疑它是否存在。
我建议您首先编写程序以检查您的平台是否支持直接 I/O,并相应地采取措施,在您知道您的程序将在其中运行的内核和文件系统中添加检查和支持。
希望我能帮到您,
--K

1
请注意,各种Linux文件系统对于直接IO的支持水平可能会有所不同。大多数情况下需要页面对齐缓冲区,但是在我的经验中,有些文件系统只能通过直接IO进行完整的页面大小读取/写入,这使得读取或写入任何不是系统页面大小倍数的文件更加复杂。 - Andrew Henle

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接