如果 Linux 上的所有内容都是文件,那么什么是目录?

Ser*_*nyy 20 files directory

初学者经常听到一句话“一切都是 Linux/Unix 上的文件”。但是,目录是什么?它们与文件有何不同?

Ser*_*nyy 26

注意:最初这是为了支持我对为什么ls命令中的当前目录被标识为链接到自身的回答但我觉得这是一个值得独立的话题,因此这个问答

理解 Unix/Linux 文件系统和文件:一切都是一个 inode

本质上,目录只是一个特殊文件,其中包含条目列表及其 ID。

在我们开始讨论之前,重要的是区分几个术语并了解目录和文件真正代表什么。对于 Unix/Linux,您可能听说过“一切都是文件”的说法。好吧,用户通常理解的文件是这样的:/etc/passwd- 具有路径和名称的对象。实际上,名称(无论是目录或文件,还是其他任何东西)只是一串文本 - 实际对象的属性。该对象称为inode或 I-number,并存储在磁盘上的 inode 表中。开放程序也有 inode 表,但这不是我们现在关心的问题。

Unix 的目录概念正如 Ken Thompson 在1989 年的一次采访中所说:

...然后其中一些文件是仅包含名称和 I-number 的目录。

丹尼斯·里奇 1972 年的演讲中可以得出一个有趣的观察结果:

“……目录其实不过是一个文件,但它的内容是由系统控制的,内容是其他文件的名称。(目录在其他系统中有时也称为目录。)”

...但在谈话的任何地方都没有提到 inode。然而,1971年手动format of directories指出:

文件是目录的事实由其 i-节点条目的标志字中的位指示。

目录条目的长度为 10 个字节。第一个词是条目所代表的文件的 i-节点,如果非零;如果为零,则条目为空。

所以它从一开始就存在。

目录和 inode 配对也在UNIX 文件系统中的目录结构如何存储?. 目录本身是一种数据结构,更具体地说:对象列表(文件和 inode 编号)指向有关这些对象的列表(权限、类型、所有者、大小等)。所以每个目录都包含它自己的 inode 号,然后是文件名和它们的 inode 号。最著名的是inode #2,它是/directory。(请注意,尽管/dev/run是虚拟文件系统,因此由于它们是其文件系统的根文件夹,因此它们也具有 inode 2; 即一个 inode 在它自己的文件系统上是唯一的,但是附加了多个文件系统,你有非唯一的 inode )。从链接问题借用的图表可能更简洁地解释了它:

目录-iNode-块

存储在 inode 中的所有信息都可以通过stat()系统调用访问,如 Linux man 7 inode

每个文件都有一个包含有关该文件的元数据的 inode。应用程序可以使用返回 stat 结构的 stat(2)(或相关调用)或返回 statx 结构的 statx(2) 检索此元数据。

是否可以仅知道其 inode 编号(ref1ref2)来访问文件?在某些 Unix 实现上这是可能的,但它绕过了权限和访问检查,因此在 Linux 上它没有实现,您必须遍历文件系统树(find <DIR> -inum 1234例如通过)以获取文件名及其相应的 inode。

在源代码级别,它在Linux 内核源代码中定义,并且也被许多在 Unix/Linux 操作系统上工作的文件系统采用,包括 ext3 和 ext4 文件系统(Ubuntu 默认)。有趣的是:数据只是信息块,Linux 实际上有inode_init_always 函数,可以确定 inode 是否是管道 ( inode->i_pipe)。是的,套接字和管道在技术上也是文件 - 匿名文件,它们在磁盘上可能没有文件名。FIFOUnix 域套接字在文件系统上确实有文件名。

数据本身可能是唯一的,但 inode 编号不是唯一的。如果我们有一个名为 foobar 的 foo 硬链接,它也将指向 inode 123。该 inode 本身包含有关该 inode 占用哪些实际磁盘空间块的信息。从技术上讲,这就是您可以.链接到目录文件名的方式。嗯,几乎:你不能自己在 Linux 上创建到目录的硬链接,但是文件系统可以以非常严格的方式允许到目录的硬链接,这限制了只有硬链接...作为硬链接。

目录树

文件系统将目录树实现为树数据结构之一。特别是,

  • ext3 和 ext4 使用 HTree
  • xfs 使用 B+ 树
  • zfs 使用哈希树

这里的关键点是目录本身是树中的节点,子目录是子节点,每个子目录都有一个返回父节点的链接。因此,对于目录链接,裸目录的 inode 计数最少为 2(链接到目录名称/home/example/和链接到 self /home/example/.),并且每个额外的子目录都是一个额外的链接/节点:

# new directory has link count of 2
$ stat --format=%h .
2
# Adding subdirectories increases link count
$ mkdir subdir1
$ stat --format=%h .
3
$ mkdir subdir2
$ stat --format=%h .
4
# Count of links for root
$ stat --format=%h /
25
# Count of subdirectories, minus .
$ find / -maxdepth 1 -type d | wc -l
24
Run Code Online (Sandbox Code Playgroud)

Ian D. Allen 的课程页面上找到的图表显示了一个简化的非常清晰的图表:

WRONG - names on things      RIGHT - names above things
=======================      ==========================

    R O O T            --->         [etc,bin,home]   <-- ROOT directory
   /   |   \                         /    |      \
etc   bin   home       --->  [passwd]  [ls,rm]  [abcd0001]
 |   /   \    \                 |      /    \       |
 |  ls   rm  abcd0001  --->     |  <data>  <data>  [.bashrc]
 |               |              |                   |
passwd       .bashrc   --->  <data>                <data>
Run Code Online (Sandbox Code Playgroud)

右图中唯一不正确的是文件在技术上不被认为是在目录树本身上:添加文件对链接数没有影响:

$ mkdir subdir2
$ stat --format=%h .
4
# Adding files doesn't make difference
$ cp /etc/passwd passwd.copy
$ stat --format=%h .
4
Run Code Online (Sandbox Code Playgroud)

像访问文件一样访问目录

引用Linus Torvalds 的话

“一切都是文件”的全部意义不是你有一些随机的文件名(事实上,套接字和管道表明“文件”和“文件名”彼此无关),而是你可以使用 common操作不同事物的工具。

考虑到目录只是文件的一个特例,自然必须有 API 允许我们以类似于常规文件的方式打开/读取/写入/关闭它们。

这就是dirent.hC 库出现的地方,它定义了dirent结构,您可以在man 3 readdir 中找到它:

# new directory has link count of 2
$ stat --format=%h .
2
# Adding subdirectories increases link count
$ mkdir subdir1
$ stat --format=%h .
3
$ mkdir subdir2
$ stat --format=%h .
4
# Count of links for root
$ stat --format=%h /
25
# Count of subdirectories, minus .
$ find / -maxdepth 1 -type d | wc -l
24
Run Code Online (Sandbox Code Playgroud)

因此,在您的 C 代码中,您必须定义struct dirent *entry_p,当我们打开一个目录opendir()并开始读取它时readdir(),我们会将每个项目存储到该entry_p结构中。当然,每个项目都将包含在dirent上面显示的模板中定义的字段。

可以在我关于如何在当前工作目录中列出文件及其 inode 编号的回答中找到有关其工作原理的实际示例。

请注意,fdopen 上POSIX 手册指出“[点] 和点-点的目录条目是可选的”,而readdir 手册状态 struct dirent仅需要具有d_named_ino字段。

关于“写入”目录的注意事项:写入目录是修改其“列表”条目。因此,创建或删除文件与目录写入权限直接相关,添加/删除文件是对该目录的写入操作。

  • 我拒绝接受套接字是文件;) “一切都可以作为文件访问”会更准确吗? (2认同)