8

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

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

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

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

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

while (syscall(SYS_getdents, fd, &dirbuf, sizeof(dirbuf)) != -1)
    /* code */

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

4

3 回答 3

5

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

用 read() 从文件中读取字节不能说同样的话吗?文件中数据的磁盘格式不需要跨文件系统统一,甚至不需要在磁盘上连续 - 因此,从磁盘读取一系列字节将再次成为我希望委托给文件系统驱动程序的事情。

由 VFS [“虚拟文件系统”] 层处理的不连续文件数据。无论 FS 选择如何组织文件的块列表(例如 ext4 使用“inode”:“索引”或“信息”节点。这些使用“ISAM”(“索引顺序访问方法”)组织。但是,一个MS/DOS FS 可以有一个完全不同的组织)。

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

VFS 层(即来自用户空间系统调用)将“向下调用”到 FS 驱动程序,FS 驱动程序将执行操作,执行它认为满足请求所需的任何操作。

我假设 FS 驱动程序会知道数据在磁盘上常规文件中的位置——即使数据是碎片化的。

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

用户空间程序只会看到它的缓冲区填满了正确的数据,而不考虑 FS 索引和块获取必须有多复杂。

也许将其称为传输 inode 数据更为恰当,因为文件存在 inode(即 inode 具有索引信息以“分散/收集”文件的 FS 块)。但是,FS 驱动程序也在内部使用它来读取目录。也就是说,每个目录都有一个 inode 来跟踪该目录的索引信息。

因此,对于 FS 驱动程序,目录很像具有特殊格式信息的平面文件。这些是目录“条目”。这就是getdents回报。这“位于”inode 索引层之上。

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

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

但是......一些FS以不同的方式组织自己(称之为“B型”):

<static1>,<static2>...
<variable1>,<variable2>,...

因此,类型 A 的组织可能会被用户空间调用以原子方式读取read(2),而类型 B 会遇到困难。所以,getdentsVFS 调用处理这个。

VFS 不能像 VFS 一样呈现一个目录的“linux_dirent”视图吗?呈现一个文件的“平面视图”?

这就是getdents目的。

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

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

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

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

readon a dir 不会被拦截并转换为,getdents因为操作系统是极简主义的。它希望您知道差异并进行适当的系统调用。

open(2)为文件或目录做[opendir(3)是包装器,在open(2)下面做]。您可以读取/写入/查找文件和查找/getdents 目录。

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

因此,与其允许以劣质方式获取部分/错误信息,不如让内核应用程序开发人员通过getdents界面更简单。

此外,原子地getdents做事。如果您正在读取给定程序中的目录条目,则可能有其他程序正在该目录中创建和删除文件或重命名它们 - 就在您的序列中间。getdents

getdents将呈现原子视图。文件存在或不存在。它已被重命名或没有。因此,无论您周围发生了多少“动荡”,您都不会得到“半修改”的观点。当你要求getdents20 个条目时,你会得到它们 [如果只有那么多,你会得到 10 个]。

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

所以,你总是得到“完整”的条目,而刚刚删除的文件没有条目。这并不是说该文件仍然存在,只是说它. 另一个进程可能会立即删除它,但不会在中间getdentsgetdents

如果允许read(2) 您将不得不猜测要读取多少数据,并且不知道哪些条目在部分状态下完全形成。如果 FS 具有上述 B 类组织,则单次读取无法单个步骤中原子地获取静态部分和可变部分。

放慢脚步read(2)去做该做的事情在哲学上是不正确的getdents

getdents, unlink, creat, rmdir, 和rename(etc.) 操作是互锁和序列化的,以防止任何不一致 [更不用说 FS 损坏或泄漏/丢失的 FS 块]。换句话说,这些系统调用都“相互了解”。

如果 pgmA 将“x”重命名为“z”并且 pgmB 将“y”重命名为“z”,它们不会发生冲突。一个先行,然后再行,但没有 FS 块丢失/泄漏。getdents获取整个视图(无论是“x y”、“y z”、“x z”还是“z”),但它永远不会同时看到“xy z”。

于 2016-03-24T06:35:56.073 回答
3

在 K&R 中(实际上,至少是 SVr2 的 Unix,也许是 SVr3),目录条目是 16 个字节,其中 2 个字节用于 inode,14 个字节用于文件名。

使用read是有道理的,因为磁盘上的目录条目都是相同的大小。16 字节(2 的幂)也有意义,因为它不需要硬件乘法来计算偏移量。(我记得 1978 年左右有人告诉我,Unix 磁盘驱动程序使用浮点并且速度很慢……但那是二手的,虽然很有趣)。

后来对目录的改进允许使用更长的名称,这意味着大小不同(将巨大的条目与可能的最大名称完全相同是没有意义的)。提供了一个更新的界面,readdir.

Linux 提供了一个较低级别的接口。根据其手册页

这些不是您感兴趣的接口。查看 符合 POSIX 的 C 库接口的readdir(3) 。此页面记录了裸内核系统调用接口

如您的示例所示,getdents是一个系统调用,对实现很有用readdir。实施方式readdir未指定。readdir早期(大约 30 年前)不能实现为库函数,使用readmalloc类似的函数来管理从目录中读取的长文件名,没有什么特别的原因。

在这种情况下(可能)将功能移入内核是为了提高性能。因为getdents一次读取多个目录条目(与 不同readdir),这可以减少读取小目录的所有条目的开销(通过减少系统调用的数量)。

进一步阅读:

于 2016-03-22T23:54:15.057 回答
2

您的怀疑是正确的:让 read 系统调用在目录上工作并返回一些标准化数据,而不是让单独的 getdents 系统调用更有意义。getdents 是多余的,降低了界面的统一性。其他答案断言“读取”作为接口在某种程度上不如“getdents”。他们是不正确的。如您所见,“read”和“getdents”的参数和返回值是相同的;只是“读取”仅适用于非目录,而“getdents”仅适用于目录。“getdents”可以很容易地折叠成“read”来获得一个统一的系统调用。

不是这种情况的原因是历史性的。最初,“读取”对目录起作用,但在文件系统中返回实际的原始目录条目。这解析起来很复杂,因此除了读取之外还添加了 getdirents 调用,以提供目录条目的文件系统独立视图。最终,目录上的“读取”功能被关闭。目录上的“读取”也可以与 getdirents 的行为相同,而不是被关闭。它只是不是,可能是因为它似乎是重复的。

特别是在 Linux 中,“read”在长时间读取目录时返回错误,几乎可以肯定某些程序依赖于这种行为。因此,向后兼容性要求 Linux 上的“读取”永远不会在目录上工作。

于 2018-07-10T02:18:42.567 回答