为什么在 UNIX/Linux 中不允许硬链接到目录?

use*_*539 137 filesystems directory hard-link symlink

我在教科书中读到 Unix/Linux 不允许硬链接到目录,但允许软链接。是不是因为,当我们有循环,如果我们创建硬链接,一段时间后我们删除原始文件,它会指向一些垃圾值?

如果循环是不允许硬链接的唯一原因,那么为什么允许软链接到目录?

Dan*_*lai 152

这只是一个坏主意,因为无法区分硬链接和原始名称之间的区别。

允许硬链接到目录会破坏文件系统的有向无环图结构,可能会创建目录循环和悬空目录子树,这会使fsck和任何其他文件树遍历器容易出错。

首先,为了理解这一点,让我们来谈谈 inode。文件系统中的数据保存在磁盘上的块中,这些块由 inode 收集在一起。您可以将 inode 视为文件。?但是,inode 缺少文件名。这就是链接的用武之地。

链接只是一个指向 inode 的指针。目录是保存链接的 inode。目录中的每个文件名只是一个指向 inode 的链接。在 Unix 中打开一个文件也会创建一个链接,但它是一种不同类型的链接(它不是一个命名链接)。

硬链接只是指向该 inode 的额外目录条目。当您ls -l,权限后的数字是命名链接数。大多数常规文件都有一个链接。创建一个文件的新硬链接将使两个文件名指向同一个 inode。笔记:

% ls -l test
ls: test: No such file or directory
% touch test
% ls -l test
-rw-r--r--  1 danny  staff  0 Oct 13 17:58 test
% ln test test2
% ls -l test*
-rw-r--r--  2 danny  staff  0 Oct 13 17:58 test
-rw-r--r--  2 danny  staff  0 Oct 13 17:58 test2
% touch test3
% ls -l test*
-rw-r--r--  2 danny  staff  0 Oct 13 17:58 test
-rw-r--r--  2 danny  staff  0 Oct 13 17:58 test2
-rw-r--r--  1 danny  staff  0 Oct 13 17:59 test3
            ^
            ^ this is the link count
Run Code Online (Sandbox Code Playgroud)

现在,您可以清楚地看到没有硬链接之类的东西。硬链接与常规名称相同。在上面的例子中,test或者test2,哪个是原始文件,哪个是硬链接?到最后,你真的无法分辨(甚至通过时间戳),因为两个名称都指向相同的内容,相同的 inode:

% ls -li test*  
14445750 -rw-r--r--  2 danny  staff  0 Oct 13 17:58 test
14445750 -rw-r--r--  2 danny  staff  0 Oct 13 17:58 test2
14445892 -rw-r--r--  1 danny  staff  0 Oct 13 17:59 test3
Run Code Online (Sandbox Code Playgroud)

-i以标记ls显示你在该行的开头索引节点号。请注意如何testtest2具有相同的 inode 编号,但test3具有不同的inode 编号。

现在,如果允许您对目录执行此操作,则文件系统中不同位置的两个不同目录可能指向同一事物。事实上,一个子目录可以指向它的祖父目录,从而创建一个循环。

为什么这个循环是一个问题?因为当您遍历时,无法检测到您正在循环(在遍历时不跟踪 inode 编号)。假设您正在编写du命令,该命令需要遍历子目录以了解磁盘使用情况。怎么du知道它什么时候循环?du为了完成这个简单的任务,它很容易出错,并且必须进行大量的簿记工作。

符号链接是一个完全不同的野兽,因为它们是一种特殊类型的“文件”,许多文件系统 API 往往会自动遵循。请注意,符号链接可以指向不存在的目标,因为它们按名称指向,而不是直接指向 inode。这个概念对于硬链接没有意义,因为仅仅存在“硬链接”就意味着文件存在。

那么为什么可以du轻松处理符号链接而不是硬链接呢?我们能够在上面看到硬链接与普通目录条目没有区别。然而,符号链接是特殊的、可检测的和可跳过的!  du注意到符号链接是一个符号链接,并完全跳过它!

% ls -l 
total 4
drwxr-xr-x  3 danny  staff  102 Oct 13 18:14 test1/
lrwxr-xr-x  1 danny  staff    5 Oct 13 18:13 test2@ -> test1
% du -ah
242M    ./test1/bigfile
242M    ./test1
4.0K    ./test2
242M    .
Run Code Online (Sandbox Code Playgroud)

  • 他们似乎允许在 Mac 上通过向 link() 系统调用添加循环检测,并拒绝允许您创建目录硬链接(如果它会创建循环)。似乎是一个合理的解决方案。 (35认同)
  • `允许硬链接到目录会破坏文件系统的有向无环图结构`。你能解释更多关于使用硬链接的循环问题吗?为什么符号链接没问题 (10认同)
  • @psusi mkdir -pa/b; nocheckin ca; mv ca/b; -- nocheckln 有一个理论上的 ln 不检查目录 args,只是传递给链接,因为没有循环,我们都擅长创建 'c'。然后我们将 'c' 移动到 'a/b',然后从 a/b/c -> a/ 创建一个循环——检查 link() 不够好 (10认同)
  • @WhiteWinterWolf,根据这个链接,他们专门添加了对时间机器的支持,但只允许 root 这样做:http://superuser.com/questions/360926/creating-a-hardlinked-directory-loop-on -a-mac (5认同)
  • 周期*非常*糟糕。Windows 有这个问题,“连接”是硬链接目录。如果您不小心将权限应用于整个配置文件,它会发现一系列创建无限循环的连接点。递归遍历目录直到路径长度限制停止为止。 (4认同)
  • 实际上,“文件树步行者”在遍历树时仍然可以轻松避免循环:仅当子目录的“..”指向父目录时才递归。@Lqueryvg的[答案](https://unix.stackexchange.com/a/169230/377281)似乎更直接地解决了真正的问题:“父目录”的想法必须从头开始重新设计。限制目录硬链接要容易得多。 (3认同)
  • @psusi:例如,[这里](http://comments.gmane.org/gmane.comp.file-systems.reiserfs.general/14227) 是对 rename() 实现循环检测的必要性的一些讨论(wrt reiserfs 的计划功能):“循环可能包含比适合内存更多的图节点。循环检测对于重命名语义至关重要,如果循环即将形成的循环不适合内存,则不清楚如何检测它,因为在检查循环时必须锁定树,并且绝对不想在 IO 上保持这样的锁定。” (2认同)
  • `du` 并不是不允许硬链接到 dirs 的好理由。`du` 已经必须通过跟踪 inode 来进行大量的“簿记”,以确保它不会对块进行两次计数。 (2认同)
  • @DannyDulai,你写得好像“du”无法处理硬链接和“簿记”,它“*必须*必须做,只是为了完成这个简单的任务”。事实上,“du”已经可以处理硬链接,并且多年来通过跟踪它已经访问过的 inode 很容易做到这一点。另外,`du`不会像你所说的那样“完全”跳过符号链接;它们有一个 inode 和一个大小,并且必须被计算。你有责任想出一个比“du”更好的例子,而不是让读者“想象”一个,因为你所写的内容歪曲了“du”的实际工作原理。 (2认同)
  • @DannyDulai,请放心,我了解上下文。但是,“du 命令”与“像 du 这样的命令”呢?“完全跳过”与“不深入研究”?这只是挑衅英语吗?我不这么认为。如果您愿意,我可以建议进行一些相当小的编辑,这样可以清除所有这些内容。 (2认同)

小智 18

您可以使用绑定挂载来模拟硬链接目录

sudo mount --bind /some/existing_real_contents /else/dummy_but_existing_directory
sudo umount /else/dummy_but_existing_directory
Run Code Online (Sandbox Code Playgroud)


小智 17

除挂载点外,每个目录都有一个且唯一的父目录:...

一种方法pwd是检查 device:inode for '.' 和 '..'。如果它们相同,则您已到达文件系统的根目录。否则,在父目录中找到当前目录的名称,将其压入堆栈,然后开始比较 '../.' 用“../..”,然后用“../../.” 使用 '../../..' 等。一旦你找到了根,就开始从堆栈中弹出和打印名称。该算法依赖于这样一个事实,即每个目录只有一个父目录。

如果允许硬链接到目录,应该..指向多个父级中的哪一个?这是不允许硬链接到目录的一个令人信服的原因。

指向目录的符号链接不会导致该问题。如果程序想要,它可以lstat()对路径名的每个部分执行一个操作并检测何时遇到符号链接。该pwd算法将返回目标目录的真实绝对路径名。某处有一段文本(符号链接)指向目标目录这一事实几乎无关紧要。这种符号链接的存在不会在图中创建循环。

  • 对此不太确定。如果我们将 `..` 看作是一种到父节点的虚拟硬链接,那么链接的目标只能有一个其他链接到它,这没有技术上的原因。`pwd` 只需要使用不同的算法来解析路径。 (3认同)

Kan*_*han 9

关于这个问题,我想再补充几点。linux 中允许目录的硬链接,但以一种受限的方式。

我们可以测试这一点的一种方法是,当我们列出目录的内容时,我们会发现两个特殊目录“.”。和 ”..”。据我们所知 ”。” 指向同一个目录,“..”指向父目录。

因此,让我们创建一个目录树,其中“a”是父目录,其子目录“b”。

 a
 `-- b
Run Code Online (Sandbox Code Playgroud)

记下目录“a”的 inode。当我们ls -la从目录“a”执行 a 时,我们可以看到“。” 目录也指向同一个 inode。

797358 drwxr-xr-x 3 mkannan mkannan 4096 Sep 17 19:13 a
Run Code Online (Sandbox Code Playgroud)

在这里我们可以发现目录“a”有三个硬链接。这是因为 inode 797358 有三个以“.”为名的硬链接。在“a”目录中,名称为“..”,在目录“b”中,名称为“a”。

$ ls -ali a/
797358 drwxr-xr-x 3 mkannan mkannan 4096 Sep 17 19:13 .

$ ls -ali a/b/
797358 drwxr-xr-x 3 mkannan mkannan 4096 Sep 17 19:13 ..
Run Code Online (Sandbox Code Playgroud)

所以在这里我们可以理解硬链接只用于目录连接它们的父目录和子目录。因此没有子目录的目录将只有 2 个硬链接,因此目录“b”将只有两个硬链接。

阻止自由目录硬链接的原因之一是避免无限引用循环,这会混淆遍历文件系统的程序。

由于文件系统被组织为树,并且树不能具有循环引用,因此应该避免这种情况。

  • 好例子。它消除了我的疑问。因此,这些情况会以特殊的方式处理,以避免无限循环。正确的? (2认同)

Lqu*_*yvg 8

以下都不是禁止硬链接到目录的真正原因;每个问题都很容易解决:

  • 树结构中的循环导致难以遍历
  • 多个父母,那么哪个是“真正的”父母?
  • 文件系统垃圾收集

真正原因(如@托尔比约恩Ravn的安徒生暗示)来当你删除它有多个父目录,从指向的目录..

..现在应该指向什么?

如果该目录从其父目录中删除,但其链接数仍然大于0那么一定有某些东西,某处仍然指向它。你不能..指指点点;许多程序依赖于..,因此系统将不得不遍历整个文件系统,直到找到指向已删除目录的第一件事,只是为了更新... 要么如此,要么文件系统必须维护指向硬链接目录的所有目录的列表。

无论哪种方式,这都会导致性能开销和文件系统元数据和/或代码的额外复杂性,因此设计人员决定不允许这样做。

  • 这也很容易解决:保留子目录的父目录列表,当您添加或删除指向子目录的链接时,您会更新该列表。当您删除规范父级(子级 `..` 的目标)时,更新 `..` 以指向列表中的其他父级之一。 (3认同)
  • 我同意。不是火箭科学来解决。但仍然是性能开销,它会占用文件系统元数据中的一点额外空间并增加复杂性。因此,设计师采用了简单、快速的方法——不允许链接到硬目录。 (3认同)
  • 这不是 POSIX 所说的,但 IMO '..' 不应该是一个文件系统概念,而是在路径上按语法解析,因此 'a/..' 总是意味着 '.'。顺便说一句,这就是 URL 的工作原理。浏览器在到达服务器之前就解析了“..”。而且效果很好。 (2认同)

Pie*_*res 5

在目录上创建硬链接将是不可恢复的。假设我们有:

/dir1
???this.txt
???directory
?  ???subfiles
???etc
Run Code Online (Sandbox Code Playgroud)

我将它硬链接到/dir2.

所以/dir2现在还包含所有这些文件和目录

如果我改变主意怎么办?我不能只是rmdir /dir2(因为它不是空的)

如果我递归删除/dir2......它也会被删除/dir1

恕我直言,这是避免这种情况的充分理由!

编辑 :

评论建议通过对目录进行操作来删除该目录rm。但是rm在非空目录上失败,无论目录是否硬链接,这种行为都必须保留。所以你不能只是rm取消链接。它需要一个新的参数rm,只是说“如果目录 inode 的引用计数 > 1,则只取消链接目录”。

这反过来又打破了另一个最不令人惊讶的原则:这意味着删除我刚刚创建的目录硬链接与删除普通文件硬链接不同......

我将改写我的句子:如果没有进一步的发展,硬链接的创建将是不可恢复的(因为当前的命令无法在不与当前行为不一致的情况下处理删除)

如果我们允许更多的开发来处理这种情况,如果您对系统的工作方式不够了解,那么陷阱的数量以及数据丢失风险,这样的开发意味着,恕我直言,这是限制目录硬链接的充分理由。

  • 你的论点是不正确的。您只需取消链接,而不是执行 rm -rf。如果链接数达到0,那么系统就会知道它也可以删除所有内容。 (5认同)