「Linux」- inode 与 dentry

问题描述

今天有人在问 inode 有关的问题:对于存放大量小文件的磁盘,磁盘空间占用不多,但是 inode 占用比较多,问有没有什么好的处理方法?

该笔记将记录:在 Linux 中,与 inode 的内容,以及常见问题的解决方案;

解决方案

为了方便管理,Linux FS 为每个文件都分配两个数据结构:
1)index node(索引节点,inode):记录文件的元信息
2)directory entry(目录项,dentry):记录目录结构;

索引节点(inode)

索引节点,简称为 inode,

作用

1)inode 用来记录文件的元数据,比如 inode Number、文件大小、访问权限、修改日期、数据的位置等;但其并不存储文件名;
2)inode 与文件一一对应,每个文件对应一个 inode,即 inode 是每个文件的唯一标志;

形态

1)inode 是存储在磁盘中的数据。虽然 inode 也有内存缓存,其为加速访问,但是 inode 最终是存放在磁盘中的。

2)它跟文件内容一样,都会被持久化存储到磁盘中,所以 inode 同样占用磁盘空间;每个 inode 节点的大小,一般是 128 Byte 或 256 Byte,而 inode 的总数,在格式化时就已给定;

索引编号(inumber)

每个「inode」都有一个号码,操作系统用「索引编号」来识别不同的文件;

这里值得重复一遍,在 Unix/Linux 系统的内部,并不使用文件名,而使用「索引编号」来识别文件。对于系统来说,「文件名」只是「索引编号」便于识别的别称;

查看文件的「索引编号」:ls -i .bashrc

命令操作

查看 inode 的使用情况:df -i

查看 inode 的大小:dumpe2fs -h /dev/hda | grep "Inode size"

调整 inode 的数量:
1)在格式化时,需要提前确定,通常命令有相应的选项,参考文档即可。
2)在使用过程中,若出现 inode 数量不足:如果无法扩容磁盘,则要备份数据,重新格式化磁盘,并指定 inode 的数量;

目录项(dentry)

目录项,简称为 dentry;

作用

1)dentry 用来记录文件的名字、inode 指针;
2)记录其与其他 dentry 的关联关系:多个关联的目录项,就构成文件系统的目录结构。即 dentry 维护的正是文件系统的树状结构;

形态

其与 inode 不同,dentry 是由内核维护的一个内存数据结构,所以通常也被叫做 dentry cache(目录项缓存)。dentry 只是内存中的一个数据结构,一个内存缓存,是通过程序代码定义的,并非像 inode 一样存储在磁盘中;

dentry 与 inode 是多对一的关系,即一个文件能够具有多个名称 —— 硬连接是最好的例子;

所以,在 FS 中,我们看到的目录是个 dentry,我们看到的文件也是个 dentry(其指向 inode);

硬连接(Hard Link)

通过硬链接为文件创建的别名,就会对应不同的 dentry,不过这些 dentry 本质上还是引用到同个文件,所以它们的 inode 相同。

硬连接文件的目录项和原文件目录项指向同一个索引节点,索引节点的引用计数会 +1;
如果原目录项被删除(即原文件被删除),或硬连接被删除,只要索引节点的引用计数不为 0,其它 dentry 就可以访问到文件数据;
硬连接不能指向目录。

软连接不仅会有新的 dentry,还会创建一个新的 inode,inode 对应的逻辑块中存储的数据是被链接文件的目录和索引节点(重点)。即软连接和被链接文件具有不同的索引节点。如原文件被删除了,因为原文件的 inode 没有被其他引用,所以变为 0,那么 inode 和 inode 对应的数据块就会被删除,那么此时软连接访问时发现自己的 inode 中存储的被链接文件的目录在磁盘中不存在了,就会出错;

命令操作

WIP

文件存储的细节(inode and dentry)

实际上,磁盘读写的最小单位是扇区,然而扇区只有 512B 大小,如果每次都读写这么小的单位,效率一定很低。所以,文件系统又把连续的扇区组成了逻辑块(又称为 Cluster(簇),每个簇可能包含2,4,8,16…个扇区。为了提高磁盘I/O效率,这里有个4KB对齐的概念。),然后每次都以逻辑块为最小单元,来管理数据。常见的逻辑块大小为 4KB,也就是由连续的 8 个扇区组成。(现在的磁盘已经使用 4KiB 扇区,不过这不属于本部分讨论的内容)

如图所示,存在三个 dentry 结构:

第一个是 /etc 目录:
1)父目录:应该指向其他 dentry 结构,但是图示中已省略;
2)子目录/文件列表:其下包含两个文件:flie1、file2
3)索引节点指针:指向 inode 结构,该 inode 和 dentry 来共同描述该目录的信息;

第二个是 file1 文件:
1)父目录:即该文件所属的父目录;
2)file1:文件名;
3)…:该文件的其他信息;
4)索引节点指针:指向 inode 结构,该 inode 和 dentry 来共同描述该文件的信息;

为什么日志切割需要重启软件?

日志切割脚本(logrotate),你会发现在移动(重命名)日志文件之后,需要进行重启(或 reload 等类似操作)动作,软件才能将日志写入新的文件中;

原因是这样的:
1)首先,移动文件或重命名文件,只是改变文件名,不影响「索引编号」;
2)其次,在系统内部,打开一个文件分成三步:(1)首先,系统找到这个「文件名」对应的「索引编号」;(2)然后,通过「索引编号」获取「索引节点」信息;(3)最后,根据「索引节点」信息,找到文件数据所在的「块」,读出数据;

所以就算移动文件或重命名文件,但是软件还使用的是原来的「索引编号」找到的「索引节点」,所以读取的还是以前的数据块;

关于监控

所以在日常服务的监控中,磁盘空间不足的原因,除了物理空间不足外,还可能是「索引节点」已用完。这两点都是需要监控的;

扇区(Sector)、块(Block)

文件储存在硬盘上,硬盘的最小存储单位叫做 Sector(扇区),每个 Sector 储存 512 Byte(相当于 0.5 KB);

但是,操作系统读在取硬盘时,一次读取一个 Block(块)。一个 Block 由多个 Sector 组成,大小通常是 4 KB,即连续 8 个 Sector 组成一个 Block;

而文件则是存储在 Block 里,Block 是存储文件的最小单位。一个大小为 8 KB 的文件,占用两个 Block;一个大小 12 KB 的文件,占用三个 Block。那大小为 9KB 的文件占用几个 Block 呢?答案是“三个”,因为“文件是存储在 Block 里的,块是存储文件的最小单位”。同样的,大小为 10 KB 文件也要占用三个 Block 。所以说,大小为 10KB 的文件,实际占用的硬盘空间大小是 12KB;

这么存储不是浪费磁盘空间么?为什么不以“位”为单位进行存储?因为以 bit 为单位的读取数据效率太低;

但是我们的文件还有一些其他的“元信息”,比如所属用户、所属组、修改时间、访问时间、权限等等,这些信息存在哪里呢?这些信息存储在一个叫「inode」的地方,中文称之为「索引节点」;

参考文献

Wikipedia/inode
理解 inode
Inode 占满导致 No space left on device 解决
How can I increase the number of inodes in an ext4 filesystem?
ext4 file-system max inode limit – can anyone please explain?