我浏览了K&R C,发现要读取目录中的条目,他们使用了:

while (read(dp->fd, (char *) &dirbuf, sizeof(dirbuf)) == sizeof(dirbuf))
    /* code */

其中dirbuf是系统特定的目录结构,而dp->fd是有效的文件描述符。在我的系统上,dirbuf应该是struct linux_dirent。请注意,struct linux_dirent具有用于条目名称的灵活数组成员,但是为了简单起见,让我们假设它没有。 (在这种情况下处理灵活数组成员将只需要一点额外的样板代码)。

但是,Linux不支持此构造。当使用read()尝试如上所述读取目录条目时,read()返回-1,并且errno设置为EISDIR

相反,Linux专门指定了一个用于读取目录的系统调用,即getdents()系统调用。但是,我注意到它的工作原理几乎与上述相同。
while (syscall(SYS_getdents, fd, &dirbuf, sizeof(dirbuf)) != -1)
    /* code */

这背后的原因是什么?与在K&R中使用read()相比,似乎没有什么好处。

最佳答案

getdents将返回struct linux_dirent。它将对文件系统的任何基础类型执行此操作。 “在磁盘上”格式可能完全不同,只有给定的文件系统驱动程序才知道,因此简单的用户空间读取调用将无法工作。也就是说,getdents可以从 native 格式转换以填充linux_dirent



不连续的文件数据由VFS [“虚拟文件系统”]层处理。不管FS如何选择组织文件的阻止列表(例如ext4使用“inodes”:“index”或“information”节点。这些节点都使用“ISAM”(“索引顺序访问方法”)组织)。 MS/DOS FS可以具有完全不同的组织)。

每个FS驱动程序在启动时都会注册一个VFS函数回调表。对于给定的操作(例如open/close/read/write/seek),表中有相应的条目。

VFS层(即从用户空间syscall中)将“调低”到FS驱动程序中,并且FS驱动程序将执行该操作,执行它认为满足请求所必需的任何操作。



是。例如,如果读取请求是要从文件中读取前三个块(例如0,1,2),则FS将查找文件的索引信息,并获取要读取的物理块的列表(例如1000000, 200,37)。所有这些都在FS驱动程序中透明地处理。

用户空间程序将仅看到其缓冲区中填充了正确的数据,而无需考虑FS索引和块获取的复杂程度。

在存在文件 inode 的情况下,将其称为“轻松地”传输 inode 数据更为合适(即, inode 具有索引信息以“分散/聚集”文件的FS块)。但是,FS驱动程序也在内部使用它来从目录中读取。也就是说,每个目录都有一个 inode 来跟踪该目录的索引信息。

因此,对于FS驱动程序而言,目录非常类似于具有特殊格式信息的平面文件。这些是目录“条目”。这是getdents返回的内容。这“位于” inode 索引层之上。

目录条目可以是可变长度的(基于文件名的长度)。因此,磁盘上的格式应为(称为“Type A”):

static part|variable length name
static part|variable length name
...

但是...某些FS的组织方式有所不同(称为“Type B”):
<static1>,<static2>...
<variable1>,<variable2>,...

因此,用户空间read(2)调用可能会自动读取A型组织,而B型将有困难。因此,getdents VFS调用可以处理此问题。



那就是getdents的目的。


getdents并不总是存在。当Dirents固定大小且只有一种FS格式时,readdir(3)调用可能在其下进行了read(2)并获得了一系列字节[这是read(2)所提供的]。实际上,IIRC刚开始时只有readdir(2),而getdentsreaddir(3)不存在。

但是,如果read(2)是“短”(例如,两个字节太小),该怎么办?您如何将其传达给应用程序?



目录中的read不会被截取并转换为getdents,因为该操作系统是极简主义的。它希望您知道差异并进行适当的syscall。

您为文件或目录执行open(2) [opendir(3)是包装器,在下面执行open(2)]。您可以读/写/寻找文件,并寻找/获取目录。

但是...做read返回EISDIR。 [旁注:我在最初的评论中已经忘记了这一点]。在它提供的简单“平面数据”模型中,没有一种方法可以传达/控制getdents可以/可以做的所有事情。

因此,与其允许劣等的方法来获取部分/错误的信息,不如让内核和应用程序开发人员轻松通过getdents接口(interface)。

此外,getdents可自动执行操作。如果您正在读取给定程序中的目录条目,则可能有其他程序正在getdents序列的中间创建和删除该目录中的文件或重命名它们。
getdents将显示一个原 subview 。文件存在或不存在。它已被重命名或没有被重命名。因此,无论您周围发生了多少“动荡”,您都不会获得“半修改” View 。当您要求getdents输入20个条目时,您会得到它们(如果只有10个,则为10个)。

旁注:一个有用的技巧是“过度指定”计数。也就是说,告诉getdents您想要50,000个条目[必须提供空格]。通常您会得到大约100左右的东西。但是,现在,您可以获得的是及时的完整目录的原子快照。有时我这样做而不是循环计数1--YMMV。您仍然需要防止立即消失,但至少可以看到它(即,随后的文件打开失败)

因此,您始终会获得“整个”条目,而对于刚刚删除的文件则不会获得任何条目。这并不是说文件仍在那儿,而只是在getdents时它在那儿。另一个进程可能会立即将其擦除,但不会在getdents的中间

如果允许使用read(2),则您必须猜测要读取的数据量,并且不知道在部分状态下完全形成了哪些条目。如果FS具有上面的B型组织,则单个读取无法在单个步骤中自动获得静态部分和可变部分。

从哲学上讲,降低read(2)的速度来执行getdents的操作是不正确的。
getdentsunlinkcreatrmdirrename(等)操作已互锁并序列化,以防止出现任何不一致(更不用说FS损坏或FS块泄漏/丢失)。换句话说,这些系统调用全部“彼此了解”。

如果pgmA将“x”重命名为“z”,而pgmB将“y”重命名为“z”,则它们不会发生冲突。一个先走,另一个再走,但没有FS块丢失/泄漏。 getdents获取整个 View (“x y”,“y z”,“x z”或“z”),但是永远不会同时看到“x y z”。

关于c - 为什么Linux在目录上使用getdents()而不是read()?,我们在Stack Overflow上找到一个类似的问题:https://stackoverflow.com/questions/36144807/

10-14 03:21