笔者在前文《Linux EXT2 文件系统中》中介绍了 EXT2 文件系统中的基本概念,本文继续以 EXT2 文件系统为例介绍文件系统是如何管理文件存储的。
inode
在前文介绍文件系统时我们提到了 inode 和 data block。在 EXT2 文件系统中,inode 用来存放文件的元信息,data block 用来存放文件的内容。inode 包含的文件元信息有:
- 该文件的读写权限(rwx)
- 该文件的拥有者和所属组(owner/group)
- 该文件的大小
- 该文件的 ctime(创建时间)
- 该文件的 atime(最近一次的读取时间)
- 该文件的 mtime(最近修改的时间)
- 该文件的特殊标识,比如 SetUID 等
- 该文件真正内容的指向(文件 data block 的位置)
总之,除了文件名之外的所有文件信息都存在 inode 中。我们可以使用 stat 命令来查看文件的 inode 信息:
$ stat test.txt
inode 的大小
inode 本身是会消耗磁盘空间的,我们可以通过下面的方式查看 inode 的大小:
$ sudo dumpe2fs -h /dev/sdd1 | grep "Inode size"
在笔者的机器上,输出的结果如下:
dumpe2fs 1.42.13 (17-May-2015) Inode size: 256
也就是说一个 inode 占用 256 个字节的磁盘空间。
inode 耗尽问题
因为每个文件需要占用一个 inode,所以生产中会碰到 inode 耗尽导致无法创建新文件的问题。我们可以通过 df 命令 的 -i 选项查看文件系统中 inode 的使用情况:
$ df -i
文件
每个文件都会占用一个 inode,inode 内则有文件数据放置的 block 号码。当我们在 Linux 下的 ext2 文件系统中创建一个一般文件时,ext2 文件系统会分配一个 inode 与相对于该文件大小的 data block(一个或多个) 给该文件。inode 记录该文件的权限与属性,并记录分配到的 data block 号码。例如:假设一个 data block 的大小为 4 KBytes,而要创建一个 100 Kbytes 的文件,linux 就会分配一个 inode 与 25 个data block 来储存该文件!
单个文件,inode 和 data block 的示意图(可以简单的理解为下图的样子):
这种数据存取的方法我们称为索引式文件系统(indexed allocation)。
虽然从实现的层面上来看,可以直接从 inode 找到其对应的 data block,但是我们却不能通过 inode 直接访问文件,因为这会破坏通过权限进行的访问控制。例如,如果没有遍历目录的权限,那么无论文件上的权限是什么,都不能访问该目录中的任何文件。如果可以通过inode访问文件,就可以绕过目录权限。
目录
注意,inode 本身并不记录文件名,文件名的记录是在目录的 data block 当中。
目录本质上也是一个文件,所以当我们在 ext2 文件系统中创建一个目录时,文件系统会分配一个 inode 和至少一块 data block 给这个目录。其中,inode 记录该目录相关的权限与属性,并记录分配到的 data block 号码。而 data block 中则记录着在这个目录下的文件名(目录也是文件)与该文件名的 inode 号码。也就是说目录所占用的 data block 的内容记录着类似下面的信息:
目录项
文件名及其对应的 inode 被称为目录项。其实,目录项是内存中的数据结构,所以实际情况是文件名对应的是指向 inode 的指针。我们可以通过下面的示意图来理解 inode、目录、文件以及目录项之间的关系:
从磁盘读取一个文件的过程
由于目录树是由根目录开始读起,因此通过文件系统的挂载信息可以找到挂载点的 inode 号,此时就能够得到根目录的 inode 内容,并通过这个 inode 读取根目录的 data block 中的文件名称,再一层一层的往下查找,直到读到正确的文件名。下面让我们通过读取 /etc/passwd 文件来理解文件的读取过程。
上面的 ls 命令使用 i 选项输出了 /、 /etc、 /etc/passwd 的 inode 分别为 2、4325377、4329700。
我们通过当前用户 nick 读取 /etc/passwd 文件的内容,其过程如下:
- / 的 inode 号为 2,权限信息允许我们读取 data block 中的内容(有 r 与 x)
- / 的 data block 中记录的 etc/ 目录的 inode 号为 4325377
- etc/ 的 inode 中权限信息包含 rx,因此用户 nick 可以读取 etc/ data block 的内容
- etc/ 的 data block 中记录的 passwd 文件的 inode 号为 4329700
- 文件 passwd 的 inode 中权限信息包含 r,因此用户 nick 可以读取 passwd data block 的内容
- passwd 的 data block 中的内容被读取出来