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

时间:2016-03-22 01:42:10

标签: c linux unix architecture filesystem-access

我正在浏览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()相比,似乎没什么好处/没有好处。

3 个答案:

答案 0 :(得分:4)

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

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

由VFS ["虚拟文件系统"]层处理的不连续文件数据。无论FS如何选择组织文件的阻止列表(例如,ext4使用" inode":"索引"或"信息"节点。这些使用& #34; 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具有索引信息到" scatter / gather"文件的FS块)。但是,FS驱动程序也在内部使用它来从目录中读取。也就是说,每个目录都有一个inode来跟踪该目录的索引信息。

因此,对于FS驱动程序,目录很像具有特殊格式信息的平面文件。这些是目录"条目"。这是getdents返回的内容。这个"位于" inode索引层。

目录条目可以是可变长度[基于文件名的长度]。因此,磁盘格式是(称之为" A型和#34;):

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

但是......有些FSes以不同的方式组织起来(称之为" B")

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

因此,类型A组织可能通过用户空间read(2)调用原子读取,类型B将有困难。因此,getdents VFS调用处理此问题。

  

VFS也不能提供一个&#34; linux_dirent&#34;像VFS这样的目录视图提供了一个平面视图&#34;一个文件?

这就是getdents的用途。

  

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

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

但是,如果read(2)是&#34;短&#34;你怎么办? (例如两个字节太小)?你如何与应用程序沟通?

  

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

目录上的

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

您对文件或目录执行open(2) [opendir(3)是包装器,并且open(2)位于下方]。您可以读取/写入/寻找文件和搜索/获取者。

但是......为read执行EISDIR。 [旁注:我在原始评论中忘记了这一点]。在简单的平面数据中#34;它提供的模型,没有一种方式来传达/控制getdents可以/做的所有事情。

因此,对于内核应用程序开发人员来说,通过getdents接口,而不是允许以较差的方式获取部分/错误信息。 / p>

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

getdents将显示原子视图。文件存在或不存在。它已被重命名,或者它没有被重命名。所以,你没有得到一半的修改&#34;无论多少&#34;动荡&#34;正在你周围发生。当你提问getdents 20个参赛作品时,你会得到它们[如果只有那么多则会得到10个]。

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

所以,你总是得到&#34;整体&#34; 已删除文件的条目和条目。 表示该文件仍然存在,只是在getdents时它 。另一个进程可能会立即删除它,但在getdents

的中间

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

放慢read(2)以执行getdents所做的事情,在哲学上是不正确的。

getdentsunlinkcreatrmdirrename(等)操作已互锁且序列化防止任何不一致[更不用说FS损坏或泄漏/丢失FS块]。换句话说,这些系统调用都是彼此了解的,并且#34;。

如果pgmA重命名&#34; x&#34;到&#34; z&#34;和pgmB重命名&#34; y&#34;到&#34; z&#34;,他们没有碰撞。一个是第一个,另一个是第二个,但没有FS块丢失/泄漏。 getdents获取整个视图(be be&#34; x y&#34;,&#34; y z&#34;,&#34; x z&#34;或&#34; z&#34; ),但它永远不会看到&#34; xy z&#34;同时进行。

答案 1 :(得分:2)

在K&amp; R中(实际上,Unix至少通过SVr2,perhaps SVr3),目录条目为16个字节,inode为2个字节,文件名为14个字节。

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

后来对目录的改进允许更长的名称,这意味着大小不同(没有必要使大量条目与最大可能的名称相同)。提供了更新的界面readdir

Linux提供低级接口。根据其manual page

  

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

如您的示例所示,getdents系统调用,对于实施readdir非常有用。实现readdir的方式未指定。早期的readdir(大约30年前)无法使用readmalloc和类似的函数来管理长文件名,因此无法实现早期getdents(大约30年前)从目录。

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

进一步阅读:

答案 2 :(得分:1)

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

并非如此的原因是历史性的。最初,“读取”在目录上起作用,但是在文件系统中返回了实际的原始目录条目。解析起来很复杂,因此添加了 来读取getdirents调用,以提供与文件系统无关的目录条目视图。最终,目录上的“读取”被关闭。同样,可以使目录上的“读取”行为与getdirents相同,而不是被关闭。只是不是,可能是因为它看起来是重复的。

尤其是在Linux中,长时间读取目录时,“ read”返回了一个错误,几乎可以确定某些程序依赖于此行为。因此,向后兼容性要求Linux上的“读取”将永远无法在目录上运行。