为什么Linux在目录而不是read()上使用getdents()?

Gio*_*iuc 6 c unix architecture linux filesystem-access

我正在浏览K&R C,我注意到要阅读目录中的条目,他们使用:

while (read(dp->fd, (char *) &dirbuf, sizeof(dirbuf)) == sizeof(dirbuf))
    /* code */
Run Code Online (Sandbox Code Playgroud)

dirbuf系统特定的目录结构和dp->fd有效的文件描述符在哪里.在我的系统上,dirbuf本来是一个struct linux_dirent.请注意,a struct linux_dirent具有用于条目名称的灵活数组成员,但是为了简单起见,我们假设它没有.(在这种情况下处理灵活的阵列成员只需要一些额外的样板代码).

但是,Linux不支持这种结构.当read()用于尝试如上所述读取目录条目时,read()返回-1errno设置为EISDIR.

相反,Linux专门用于读取目录的getdents()系统调用,即系统调用.但是,我注意到它的工作方式与上面几乎相同.

while (syscall(SYS_getdents, fd, &dirbuf, sizeof(dirbuf)) != -1)
    /* code */
Run Code Online (Sandbox Code Playgroud)

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

Cra*_*tey 5

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

关于使用read()从文件读取字节不能说同样的话吗?文件内数据的磁盘格式不必在文件系统之间是统一的,甚至不必在磁盘上是连续的-因此,我希望再次从磁盘读取一系列字节是我希望委托给文件系统驱动程序的方式。

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

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

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

我假设FS驱动程序会知道磁盘上常规文件中数据的位置-即使数据是零散的。

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

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

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

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

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

static part|variable length name
static part|variable length name
...
Run Code Online (Sandbox Code Playgroud)

但是...某些FS的组织方式有所不同(称为“ Type B”):

<static1>,<static2>...
<variable1>,<variable2>,...
Run Code Online (Sandbox Code Playgroud)

因此,用户空间调用可能自动读取类型A的组织read(2),类型B会遇到困难。因此,getdentsVFS调用将处理此问题。

VFS不能像VFS一样显示目录的“ linux_dirent”视图吗?

getdents是为了什么。

再一次,我假设FS驱动程序知道每个文件的类型,因此当在目录而不是一系列字节上调用read()时可以返回linux_dirent。

getdents没有一直存在。当dirents固定的大小和当时只有一个文件系统格式,该readdir(3)呼叫可能确实read(2)下方,取得了一系列的字节[这是什么read(2)提供。其实,IIRC,在开始的时候,只有readdir(2)getdentsreaddir(3)不存在。

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

我的问题更像是因为FS驱动程序可以确定文件是目录还是常规文件(并且我假设它可以),并且由于它最终必须拦截所有read()调用,所以为什么不读取( )在实现为读取linux_dirent的目录上?

readgetdents由于操作系统极简,因此不会拦截目录上的内容并将其转换为该目录。它希望您知道差异并进行适当的syscall。

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

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

因此,与其允许一种劣等的方式来获取部分/错误的信息,不如让内核应用程序开发人员通过该getdents界面。

进一步,原子地getdents做事。如果您正在读取给定程序中的目录条目,则可能有其他程序正在该目录中创建和删除文件或对其进行重命名。getdents

getdents将呈现一个原子视图。文件存在或不存在。它已被重命名或没有被重命名。因此,无论您周围发生了多少“动荡”,您都不会获得“半修改”视图。当您要求getdents输入20个条目时,您会得到它们(如果只有那么多个,则为10个)。

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

因此,您始终会获得“整个”条目,而对于刚刚删除的文件则不会获得任何条目。这是不是说,该文件仍然存在,仅仅是它那里的时间。另一个过程可能会立即删除它,但不会在中间getdentsgetdents

如果read(2) 允许,你就必须有多少数据读取猜测,也不会知道在部分国家得到充分形成的条目。如果FS具有高于B型组织,单个读也不能原子得到一个步骤的静态部分和可变部分。

从哲学上讲,放慢速度read(2)去做些什么是getdents不正确的。

getdentsunlinkcreatrmdir,和rename(等)的操作互锁和序列化,以防止任何不一致[更不用说FS腐败或泄露/丢失FS块。换句话说,这些系统调用全部“彼此了解”。

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