我浏览了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)
,而getdents
和readdir(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
的操作是不正确的。getdents
,unlink
,creat
,rmdir
和rename
(等)操作已互锁并序列化,以防止出现任何不一致(更不用说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/