理解 inode

文件系统创建(格式化)时,就把存储区域分为两大连续的存储区域。一个用来保存文件系统对象的元信息数据,这是由 inode 组成的表,每个 inode 默认是 256 字节或者 128 字节。另一个用来保存“文件系统对象”的内容数据,划分为 512 字节的扇区,以及由 8 个扇区组成的 4K 字节的块。块是读写时的基本单位。一个文件系统的 inode 的总数是固定的。这限制了该文件系统所能存储的文件系统对象的总数目。典型的实现下,所有 inode 占用了文件系统 1% 左右的存储容量。

inode 是什么

先从硬盘说起,硬盘的最小存储单位称为 扇区(Sector),扇区是物理概念,是看得到摸得着的东西,扇区的大小一般为 512 字节(某些新磁盘的扇区有 4096 字节)。

系统在读取磁盘时,并不会一个一个扇区的读取,这样效率太低,而是一次读取多个连续的扇区,即一次性读取一个 (Block),块是逻辑概念,是文件系统中的概念,一个块通常为 4096 字节或者为 8192 字节,即对应 8 个扇区或者 16 个扇区。

块是文件存储的最小单位,也就是说,一个文件即使只有 1 字节大小,也要占用一个块,即 4096 字节。因此,小文件过多(小于块大小的文件)很容易浪费磁盘空间。

一个文件其实包含两种数据,分别是 元数据(metadata)、数据(data)。

  • 元数据 包括 硬连接数文件大小文件类型文件权限文件所有者文件所有组文件创建日期文件元数据修改日期文件数据修改日期文件访问日期文件数据块的起始位置和结束位置 等。
  • 数据 其实就是我们普通意义上的文件数据、文件内容。

注意,元数据中并没有包含文件名,那么文件名存储在哪呢?存储在它所属的目录文件的 文件数据 中(目录项),即存储在其它文件的 数据 区域。这个后面会详细说明。

文件系统创建时,会把存储区域划分为两个,一个用于存储文件元数据,一个用于存储文件数据。也就是说一个文件的元数据和数据其实是分开存储的。一般元数据区域会占用整个存储空间的 1% 左右的大小。

元数据区域的结构类似于数组,每个元素(一个元素只存储一个文件的元数据)的长度为 128 字节或 256 字节,每个元素都有一个下标(或者称为索引号),而这个索引号就是我们常说的 inode 号。因此我们只要得到了一个文件的 inode 号,就可以根据元素的长度(128 字节或 256 字节,可通过文件系统属性查看)来直接定位到该元素,然后读取文件的元数据,进而读取到文件的数据。

一般来说,元数据区域的大小在文件系统被创建时已经被确定了(一些新的文件系统支持动态扩展元数据区域的大小),因此,如果文件过多(特别是小文件过多),会出现 inode 不够用,而数据区却还有空间的情况。

总结:每个文件都有一个唯一的 inode 号,inode 号其实就是该文件的元数据“元素”在元数据“数组”中的下标/索引。只要知道了这个下标,就能够计算出元数据的位置,读取了元数据,就能够访问文件内容了。

相关 linux 命令

查看文件元数据
stat 命令,查看文件、文件系统的状态。常用参数:

  • -L:始终跟随符号链接
  • -f:显示文件系统状态
  • -t:显示简略状态信息

stat / 的执行结果:

查看 inode 使用情况
df -i,查看 inode 节点数量,已使用数量,未使用数量:

查看 inode 节点大小

查看文件的 inode 号
lsstat 都可以查看,一般使用 ls,使用 -i 参数:

创建文件链接
ln -f source.file target.file:创建硬链接
ln -sf source.file target.file:创建软链接

特殊的目录文件

在 Linux 中,目录其实也是一个文件,它和普通文件没什么两样,也分为 元数据数据 两个部分。

一个空目录的 元数据 信息:

有几个比较重要的信息,Size: 4096Inode: 532820Links: 2

  • Size: 4096:这个目录文件的大小为 4096 字节
  • Inode: 532820:这个目录文件的 inode 号为 532820
  • Links: 2:这个目录文件的硬连接数为 2

前两个都没什么疑问,关键是硬链接数为什么是 2 呢?其实很好理解,第 1 个硬链接,是该目录的父目录所指向而产生的,第 2 个硬链接是该目录下的 . 目录文件所指向而产生的。如果在该目录下新建一个文件夹的话,硬链接数就会变成 3,因为新建的目录下有一个 .. 目录文件,指向它的父目录,即这里所说的 test 目录。

再来看一下根目录 / 的硬连接数,如下:

根目录下有 15 个文件夹,每个文件夹下都有一个 .. 文件指向根目录,因此这里就有 15 个硬链接指向根目录,然后根目录下还有两个特殊目录:.... 其实就是根目录自己了,因此硬链接数变为 16,.. 是哪个目录呢?其实也是根目录自己(不信可以自己试一下,目录文件中,只有根目录文件的父目录指向自己),因此最终的硬链接数为 17,符合上面的 ls -ld / 的输出。

那么目录文件的数据区存储的是什么东西呢?为什么空目录的数据区大小不是 0 字节而是 4096 字节?我们知道,每个目录下都有两个特殊的目录文件,它们就是 ...,前者是当前目录的硬链接,后者是其父目录的硬链接。

目录文件中存储的内容其实很简单,就是该目录下的文件名以及对应的 inode 号码。可以理解为一张表,每个表项有两个值,一个是文件名,一个是对应的 inode 号码。还有,这里有必要强调一点,在 Linux 中,不喜欢将目录称为文件夹,应称为”目录”。

所以目录文件的大小是由该目录下的文件数目决定的,文件数目如果很多,那么目录文件的大小就会比较大,对比一下 /home/usr/bin 目录,后者文件数量非常多:

/home 目录的大小是 4096 字节,而 /usr/bin 目录则是 40960 字节,相差十倍。

说到这里,不由得联想到 Unix 中的哲学思想:一切皆为文件。目录也是文件。

然后,我们再来理解一下目录的权限,对于普通文件,rwx 的意义非常清楚明白,但是对于目录文件,这有点让人摸不着头脑,比如,目录文件只有 r 权限只能 ls 浏览这个目录,ls -l 却不能提供任何有用的信息,会提示没有权限,而且你不能 cd 进一个没有 x 权限的目录,等等等等,让人有点犯晕。

但实际上,你回归到本质,就会发现,原来之前让我犯晕的目录权限问题这么简单。我们说了,目录也是文件,目录文件中保存的是一张表,每个条目都有两个字段,文件名称和对应的 inode 号。

我们常说,一个软件或者说工具,不能一味的求全,必须先求精,一个短小精悍的命令,往往比一个功能很多但每个功能都不精的命令受欢迎的多(至少我是这样的)。

在这里也一样,目录文件中的每个目录项都包含两个信息,文件名、inode 值。如果仅需要 r 权限就能读取这两个信息,那就太不安全了,因此大佬们想到一个好方法,读取目录项的文件名需要 r 权限,读取目录项的 inode 值需要 x 权限。

这样理解就很好解释了,假如一个目录只有 r 权限,那么你只能读取里面的文件名列表,即使用 ls 可以正常显示,但是 ls -lls -ils --color 等都不能使用,为什么呢,因为它们都会尝试读取目录项中的 inode 值,但是读取 inode 值需要 x 权限,所以会提示权限不足。同时,你也不能 cd 进这个目录(这个我还没彻底理解,按道理来说,cd 应该不需要读取 inode 信息,当然也可能需要,具体不深究了)。

如果一个目录只有 x 权限, 那么你只能 cd 进去,你无法通过 ls 获取任何信息。

因此,目录文件几乎总是同时拥有 r 权限和 x 权限的。最后来说说 w 权限。

如果一个目录只有 w 权限,那么你也不能在目录下创建新文件,你必须还拥有 x 权限,才能往这个目录中写入新文件,这个我也还没搞懂(Linux 真是博大精深)。

关于文件名的疑问

回到开头那一节,一个文件的 inode 元素(元数据)中并不存在该文件的文件名,既然文件名不存储在元数据区,也不可能存储在数据区,那么一个文件的文件名究竟存放在哪个地方呢?

智障,上一节不是说了吗,文件名存储在目录文件的数据区中,即存储为一个目录项。

比如文件 /root/work/test.txt,我们来一步一步分析:

  • test.txt 存储在 work 目录文件的数据区
  • work 存储在 root 目录文件的数据区
  • root 存储在 / 根目录文件的数据区
  • / 个人猜测,应该是存储在每个进程的某块内存区域中(chroot 启发)

这时,再理解为什么每个目录文件下都有两个特殊的目录文件/硬链接 ...。其实主要还是为了方便,. 文件使得位于当前目录下的程序可以不依靠根目录,而直接访问该目录下的其它文件。.. 文件使得位于当前目录下的程序可以不依靠根目录,而直接访问上一级目录,以及上一级的上一级目录,等等。

目录为什么不能硬链接

软链接和硬链接之间其实区别还是很大的,首先,软链接可以跨分区,硬链接不行,软链接可以指向一个不存在的文件,但是硬链接不可以。如果软链接到的目标文件被删除了,那么根据软链接文件时无法读取其内容的,但是硬链接文件却不同,他们完全没影响,而且硬链接文件和源文件是完全等价的,因为源文件其实也是一个硬链接,只不过它是自动创建的。

从本质来讲,软链接是一个实实在在的文件,它的内容就是所指向的文件的路径,仔细观察软链接文件的大小就知道了,就是等于所指向的文件的路径的长度大小。软链接文件和被指向文件是完全不同的两个文件,它们有这不同的 inode 值。

而硬链接并不是一个实实在在的文件,创建一个硬链接并不会占用磁盘中的一个 inode 资源,该操作只会在对应的目录文件中添加一个目录项,更新 links 数量,仅此而已。

那么 links 数有什么用呢?起到一个“引用计数”的作用,对于普通文件,在创建之处,只有一个 links 数量,当创建一个硬链接时,links 数会自动加一,即引用计数加一,当这个硬链接被删除时,Links 自动减一,当最早创建的硬链接也被删除时,links 变为了 0,系统就会回收这个 inode 号,删除对应的文件内容。

那么为什么目录不能创建硬连接呢?
Why are hard links not allowed for directories?

其中,Directory hardlinks break the filesystem in multiple ways 回答的最好。

简单的一句话就是,findlocate 等工具在遍历寻找文件时,会陷入无限的循环中,除非你指定了 -maxdepth,限制 find 搜索的最大深度,不然是停不下来的。