为什么我可以 cat /dev?

为什么我可以 cat /dev?

我可以cat /dev,我可以ls /dev,我不可以less /dev。为什么只cat允许我访问cat这个目录,而不允许访问其他目录?

zsh 中此行为的图片。

答案1

从历史上看(直到 V7 UNIX,或 1979 年左右),系统read调用对文件和目录都有效。read对目录执行的操作将返回一个简单的数据结构,用户程序将解析该结构以获取目录条目。事实上,V7ls工具正是这样做的 -read对目录执行的操作是解析生成的数据结构,以结构化列表格式输出。

随着文件系统变得越来越复杂,这种“简单”的数据结构也变得越来越复杂,以至于需要readdir添加库函数来帮助程序解析输出read(directory)。不同的系统和文件系统可能具有不同的磁盘格式,这变得越来越复杂。

当 Sun 推出网络文件系统 (NFS) 时,他们希望完全抽象出磁盘上的目录结构。read(directory)然而,他们并没有让返回结果成为与平台无关的目录表示,而是添加了一个新的系统调用 - getdirents- 并禁止read在网络挂载的目录上使用。此系统调用迅速适应了各种 UNIX 版本中的所有目录,使其成为获取目录内容的默认方式。(历史摘自https://utcc.utoronto.ca/~cks/space/blog/unix/ReaddirHistory

因为readdir现在是读取目录的默认方式,read(directory)所以在大多数现代操作系统上通常不实现(返回 -EISDIR)(例如,QNX 是一个值得注意的例外,它实现readdirread(directory))。但是,由于大多数现代内核中的“虚拟文件系统”设计,读取目录是否有效实际上取决于单个文件系统。

确实,在 macOS 上,挂载点devfs底层的文件系统/dev确实支持读取(https://github.com/apple/darwin-xnu/blob/xnu-4570.1.46/bsd/miscfs/devfs/devfs_vnops.c#L629):

static int
devfs_read(struct vnop_read_args *ap)
{
        devnode_t * dn_p = VTODN(ap->a_vp);

    switch (ap->a_vp->v_type) {
      case VDIR: {
          dn_p->dn_access = 1;

          return VNOP_READDIR(ap->a_vp, ap->a_uio, 0, NULL, NULL, ap->a_context);

READDIR如果您尝试读取,这将明确调用/dev(读取文件在 下/dev由单独的函数处理 - devfsspec_read)。因此,如果程序调用read上的系统调用/dev,它将成功并获得目录列表!

这实际上是 UNIX 早期遗留下来的一个功能,很长时间没有被触及了。我怀疑保留这个功能是出于向后兼容的原因,但也可能是因为没有人足够关心删除这个功能,因为它实际上并没有损害任何东西。

答案2

较少的是一个文本文件查看器,是一个用于复制任意数据的工具。因此较少的执行自己的检查,以确保您不会打开包含大量数据或行为非常奇怪的程序。另一方面,根本没有这样的检查——如果内核允许你打开某些东西(即使它是一个管道、一个设备或者更糟糕的东西),将会阅读它。

那么为什么操作系统允许打开目录?传统上在 BSD 风格的系统中全部目录可以被读取为文件,这就是程序首先列出目录的方式:仅通过解释存储在磁盘上的目录结构。

后来,这些磁盘结构开始与内核使用的目录不同:以前的目录是线性列表,后来的文件系统开始使用哈希表、B 树等。因此,直接读取目录不再简单 - 内核为此开发了专用函数。(我不确定这是否是主要原因,或者它们是否主要是出于缓存等其他原因而添加的。)

一些 BSD 系统继续让您打开所有目录进行读取;我不知道他们是否向您提供磁盘中的原始数据,或者是否返回模拟的目录列表,或者是否让文件系统驱动程序决定。

因此,也许 macOS 是内核允许的操作系统之一只要文件系统提供数据。不同之处在于,/dev早期devfs的文件系统允许这样做,而/现代的 APFS 文件系统则忽略了此功能,因为此功能没有必要。

免责声明:我实际上没有对 BSD 或 macOS 进行过任何研究。我只是在即兴发挥。

相关内容