你想知道的关于 Linux 上的 inode 的一切

深入了解 Linux 文件系统中的 inode

Linux 文件系统的核心在于 inode(索引节点),这是文件系统内部运作的关键组件,但常常被用户所误解。本文将详细解释 inode 的概念、作用及其工作原理。

文件系统的基本构成

文件系统的基本职责是存储文件和目录。文件被组织在目录中,而目录又可以包含子目录。为了管理这些文件和目录,文件系统需要记录每个文件的位置、名称、所属用户、权限等信息。这些描述数据的数据被称为元数据。

在 Linux 中,例如 Ext4 文件系统,inode目录结构 协同工作,为每个文件和目录存储所有必要的元数据。这些元数据被内核、用户应用程序以及 ls、stat 和 df 等 Linux 实用程序所使用。

inode 与文件系统大小

文件系统不仅仅需要几个 inode 和目录结构,实际上,每个文件和目录都需要一个 inode,并且每个文件都位于某个目录中,因此也需要一个目录结构。目录结构有时也被称为目录项(dentry)。

每个 inode 都有一个唯一的 inode 编号,这个编号在同一文件系统中是独一无二的。不同的文件系统可以使用相同的 inode 编号。文件系统 ID 和 inode 编号的组合才能在整个 Linux 系统中唯一标识一个文件或目录。

值得注意的是,在 Linux 中,我们不是挂载硬盘驱动器或分区,而是挂载分区上的文件系统。因此,您可能在不知情的情况下使用了多个文件系统。如果您的系统有多个硬盘驱动器或分区,则它们通常对应不同的文件系统,即便它们都是相同的类型(例如 Ext4),它们也是不同的文件系统。

所有的 inode 都存储在一个表中。通过 inode 编号,文件系统可以快速计算出 inode 在 inode 表中的偏移量。这就是为什么 inode 中的 “i” 代表索引的原因。

inode 编号通常被声明为一个 32 位无符号长整型,最大值为 2^32,即 4,294,967,295,这意味着理论上可以存在超过 40 亿个 inode。

但实际上,ext4 文件系统中的 inode 数量是在文件系统创建时确定的,默认情况下,每 16KB 的文件系统容量分配一个 inode。目录结构则是在文件系统使用过程中动态创建的,当文件和目录被添加到文件系统时。

您可以使用 df 命令来查看文件系统中 inode 的使用情况。使用 -i 选项可以让 df 命令显示 inode 的使用信息。例如,要查看第一个硬盘驱动器上第一个分区上的文件系统信息,可以输入以下命令:

df -i /dev/sda1

输出结果会显示:

文件系统: 被报告的文件系统。
Inodes: 此文件系统中的 inode 总数。
IUsed: 正在使用的 inode 数量。
IFree: 剩余可用的 inode 数。
IUse%: 已使用 inode 的百分比。
Mounted on: 此文件系统的挂载点。

在这个例子中,我们只使用了 10% 的 inode。文件通常以磁盘块的形式存储在硬盘上,每个 inode 指向存储文件内容的磁盘块。如果存在大量小文件,可能会在磁盘空间耗尽之前用完 inode。然而,这种情况很少发生。

过去,一些将电子邮件存储为单独文件的邮件服务器,常常因为大量小文件而遇到 inode 耗尽的问题。但当这些应用程序改为使用数据库后端后,问题得到了解决。对于普通的家庭系统,inode 耗尽的情况并不常见。并且,使用 ext4 文件系统时,在不重新安装文件系统的情况下无法添加更多的 inode。

您可以使用 blockdev 命令来查看 文件系统中磁盘块的大小,命令如下:

sudo blockdev --getbsz /dev/sda

在本例中,磁盘块的大小为 4096 字节。

接下来,我们可以使用 -B 选项指定块大小为 4096 字节,并查看常规磁盘使用情况:

df -B 4096 /dev/sda1

此输出结果显示:

文件系统: 被报告的文件系统。
4K-blocks: 此文件系统中 4KB 块的总数。
已使用: 正在使用多少个 4KB 块。
可用: 可用的剩余 4KB 块的数量。
Use%: 已使用的 4KB 块的百分比。
Mounted on: 此文件系统的挂载点。

在本例中,文件存储(包括 inode 和目录结构的存储)占用了文件系统上 28% 的空间,而 inode 的使用率为 10%,因此系统处于良好的状态。

inode 元数据

可以使用 ls 命令和 -i 选项来查看文件的 inode 编号,如下所示:

ls -i geek.txt

这个文件的 inode 编号是 1441801,这个 inode 中存储着该文件的元数据,以及指向文件数据所在的磁盘块的指针。如果文件是碎片化的或者非常大,inode 指向的某些块可能会包含更多指向其他磁盘块的指针,这些块也可能包含指向另一组磁盘块的指针。这种方法有效地解决了 inode 固定大小和只能保存有限数量磁盘块指针的问题。

现在,有一种名为 “范围” 的新方案取代了之前的做法。范围记录存储文件的每组连续块的起始块和结束块。如果文件是非碎片化的,只需要存储第一个块和文件长度。如果文件是碎片化的,则需要存储文件每个部分的起始和结束块。这种方法更高效。

您可以通过查看 inode 内部来确定文件系统是否使用了磁盘块指针或范围。可以使用带有 -R 选项的 debugfs 命令,并将要查看的文件的 inode 传递给它。debugfs 将使用其内部的 “stat” 命令来显示 inode 的内容。由于 inode 编号只在文件系统中唯一,所以还需要告诉 debugfs inode 所属的文件系统。

sudo debugfs -R "stat " /dev/sda1

debugfs 命令会从 inode 中提取信息,并在 less 中呈现出来:

以下是一些重要的信息:

Inode: 我们正在查看的 inode 的编号。
类型: 表明这是一个普通文件,而不是目录或符号链接。
模式: 文件权限的八进制表示形式。
标志: 表示不同的特征或功能。0x80000 表示 “范围” 标志。
世代: 网络文件系统 (NFS) 在通过网络访问远程文件系统时使用此字段。 inode 和代号充当文件句柄的形式。
版本: inode 的版本。
用户: 文件的所有者。
组: 文件的组所有者。
项目: 应始终为零。
大小: 文件的大小。
文件 ACL: 文件的访问控制列表,用于向不在所有者组中的人授予访问权限。
链接: 指向文件的 硬链接 数量。
Blockcount: 分配给该文件的硬盘空间量,以 512 字节块为单位。
碎片: 表示此文件是否碎片化。(这是一个过时的标志。)
Ctime: 文件创建的时间。
Atime: 上次访问此文件的时间。
Mtime: 上次修改此文件的时间。
Crtime: 文件创建的时间。
额外 inode 字段的大小: ext4 文件系统引入的额外字节数,用于存储扩展属性或为未来的内核需求做准备。
Inode 校验和: inode 的校验和,用于检测是否损坏。
范围: 如果使用了范围(在 ext4 上,默认情况下使用),则此字段会记录文件数据所在硬盘块的起始和结束块。

文件名在哪里?

尽管我们已经了解了文件的许多信息,但并没有找到文件名。文件名存储在目录结构中。在 Linux 中,目录也像文件一样拥有一个 inode。但是,目录的 inode 指向的是包含目录结构的磁盘块,而不是文件的数据块。

与 inode 相比,目录结构包含 关于文件信息的有限集合。目录结构只保存文件的 inode 编号、名称和名称的长度。

inode 和目录结构包含了您需要了解的有关文件或目录的所有信息。目录结构位于目录磁盘块中,这样我们知道文件所在的目录。目录结构提供文件名和 inode 编号。inode 则提供了文件的所有其他信息,例如时间戳、权限以及文件数据在文件系统中的位置。

目录的 inode

您可以像查看文件一样轻松查看目录的 inode 编号。

以下示例使用 ls 命令的 -l (长格式)、-i (inode) 和 -d (目录) 选项来查看工作目录:

ls -lid work/

由于使用了 -d (目录) 选项,ls 命令显示目录本身,而不是目录的内容。此目录的 inode 编号是 1443016。

要查看主目录的 inode,可以输入以下命令:

ls -lid ~

主目录的 inode 编号是 1447510。工作目录位于主目录中。现在我们看一下工作目录的内容。使用 -a (全部) 选项,而不是 -d (目录) 选项,会显示通常被隐藏的目录项:

ls -lia work/

使用 -a (all) 选项后,会显示单点 (.) 和双点 (..) 条目。单点条目代表目录本身,双点条目代表父目录。

单点条目的 inode 编号是 1443016,与工作目录的 inode 编号相同。双点条目的 inode 编号与主目录的 inode 编号相同。

这就是为什么可以使用 cd .. 命令在目录树中向上移动一级的原因。当在应用程序或脚本名称前添加 ./ 时,shell 会知道从哪里启动应用程序或脚本。

inode 和链接

一个格式良好且可访问的文件需要三个组件:文件、目录结构和 inode。文件是存储在硬盘上的数据,目录结构包含文件的名称及其 inode 编号,而 inode 则包含文件的所有元数据。

符号链接是一种特殊的文件系统条目,看起来像文件,但实际上是指向现有文件或目录的快捷方式。让我们看看它们是如何工作的。

假设我们有一个包含两个文件的目录,一个是脚本,另一个是应用程序,如下所示:

可以使用 ln 命令和 -s(符号)选项来 创建一个指向脚本文件的软链接,命令如下:

ln -s my_script.sh geek.sh

我们创建了一个名为 geek.sh 的链接,它指向 my_script.sh。可以使用 ls 命令查看这两个文件:

ls -li *.sh

geek.sh 显示为蓝色,表示这是一个链接。权限标志的第一个字符是 ‘l’,箭头 ‘->’ 指向 my_script.sh。这些都表明 geek.sh 是一个符号链接。

不出所料,两个脚本文件具有不同的 inode 编号。更令人惊讶的是,软链接 geek.sh 没有与原始脚本文件相同的用户权限。实际上,geek.sh 的权限更加宽松,所有用户都拥有完全权限。

geek.sh 的目录结构包含链接的名称和它的 inode 编号。当您尝试使用该链接时,它的 inode 会被引用,就像普通文件一样。链接的 inode 指向的磁盘块不包含文件的数据,而是包含原始文件的名称。文件系统会将您重定向到原始文件。

如果删除原始文件,并尝试查看 geek.sh 的内容,会发生什么?

rm my_script.sh
cat geek.sh

符号链接断开,重定向失败。

现在,创建一个指向应用程序文件的硬链接:

ln special-app geek-app

查看这两个文件的 inode:

ls -li

这两个文件看起来都是普通文件。geek-app 没有任何指示表明它是一个链接。此外,geek-app 与原始文件具有相同的用户权限。然而,令人惊讶的是,两个应用程序都具有相同的 inode 编号:1441797。

geek-app 的目录项包含名称 “geek-app” 和一个 inode 编号,而这个编号与原始文件的 inode 编号相同。因此,我们有两个名称不同的文件系统条目,它们都指向同一个 inode。实际上,任意数量的条目都可以指向同一个 inode。

使用 stat 程序 查看目标文件:

stat special-app

可以看到有两个硬链接指向这个文件。这个信息存储在 inode 中。

现在,删除原始文件并尝试使用带有 安全密码 的链接:

rm special-app
./geek-app correcthorsebatterystaple

令人惊讶的是,该应用程序仍然正常运行。这是因为当您删除文件时,inode 并不会被立即释放重用。目录结构中指向该 inode 的条目会被标记为 inode 编号为零,并且文件数据所在的磁盘块也标记为可以被新的文件数据覆盖。但是,如果指向该 inode 的硬链接数大于 1,则硬链接计数器会减 1,并且被删除文件的目录结构中的 inode 编号设置为零。硬盘上的文件内容仍然可以被现有的硬链接访问。

再次使用 stat 命令查看 geek-app:

stat geek-app

这些详细信息来自与之前的 stat 命令相同的 inode (1441797)。链接计数已减少了一个。

因为现在只有一个指向该 inode 的硬链接,删除 geek-app 将会真正删除该文件。文件系统会释放 inode,并将目录结构标记为 inode 为零。之后,新文件可以覆盖硬盘上的数据存储。

inode 的开销

虽然这个系统非常优雅,但也存在开销。为了读取文件,文件系统必须执行以下步骤:

  • 找到正确的目录结构
  • 读取 inode 编号
  • 找到正确的 inode
  • 读取 inode 信息
  • 根据 inode 中的链接或范围找到相关的磁盘块
  • 读取文件数据

如果数据不连续,则需要进行更多的跳转。

想象一下,ls 命令为了显示一个包含许多文件的长格式列表,必须完成多少工作。需要进行多次文件系统访问才能获取必要的信息。

为了加快文件系统的访问速度,Linux 会尽可能多地进行预读缓存。这很有帮助,但是,如同任何文件系统一样,开销有时是显而易见的。

现在您知道原因了。