《Linux 程序设计》的第三章讲文件操作。在提到 目录
时有这么一段文字:
文件,除了本身包含的
内容
以外,它还会有一个名字
和一些属性
,即“管理信息”,包括文件的创建 / 修改日期和它的访问权限。这些属性被保存在文件的inode
中,它是文件系统中的一个特殊数据库,它同时还包含文件的长度和文件在磁盘上存放的位置。系统使用的是文件的 inode 编号,目录结构为文件命名仅仅是为了便于人们使用。
目录
是用于保存其他文件的节点号
和名字
的文件。目录文件中的每个数据项都是指向某个文件节点的链接,删除文件名就等于删除与之对应的链接。你可以通过使用ln
命令在不同的目录中创建指向同一个文件的链接。
刚开始,我不能清晰的理解这两段内容,特别是 inode 。所以我查找了 inode 的资料,发现它是一个重要的基础概念。当我深入了解了 inode 之后,我甚至觉得它是学好 Linux 文件系统的关键。
什么是 inode
当 Linux 创建文件时,会完成两件事情。第一,Linux 在存储设备上保留一块空间用来存储数据。第二,Linux 创建一个称为 索引节点
(index node,简称 inode) 的接口,用来存放文件的属性信息。每一个文件都有对应的 inode ,具体有以下内容:
- 文件大小,以字节为单位
- 包含该文件的设备名称
- 属主的用户标识(Uid)
- 组标识(Gid)
- 文件的权限
- 文件时间戳,共 3 个:inode 上一次修改时间(ctime)、文件上一次修改时间(mtime)、文件上一次访问时间(atime)
- 指向该文件的链接数
- 文件类型(普通、目录、特殊、符合连接等)
- 分配给该文件的块数
需要解释下最后一条:“分配给文件的块数”。文件都存储在 块
中:文件存储的物理介质,比如硬盘,最小存储单位叫做 扇区
(sector),每个扇区存储 512 字节。操作系统读取硬盘时,不会一个个扇区地读取,这样效率太低,而是一次性连续读取多个扇区,即读取一个 块
(block)。文件存取的最小单位就是块,最常见一个块是 4KB 。
文件系统将所有的 inode 存放在一个大表中,这个表称为 inode 表。在 inode 表中,每个 inode 由唯一编号表示。使用命令 stat
可以查看文件的 inode 信息,比如:
其中红圈中的数字就是 inode 的编号。
inode 也会消耗硬盘空间,所以硬盘格式化的时候,操作系统自动将硬盘分成两个区域。一个是数据区,存放文件数据;另一个是 inode 区,存放 inode 所包含的信息。在我的 Ubuntu 20.4 系统中,一个 inode 占用 256 字节。inode 节点的总数,在格式化时就给定,一般是每 1KB 或每 2KB 就设置一个 inode。因此有可能发生 inode 已经用光,但是硬盘还未存满的情况。这时,就无法在硬盘上创建新文件。
查看每个硬盘分区的 inode 总数和已经使用的数量,可以使用 df -i
命令。df
是 disk free-space (磁盘可用空间) 的简称。
需要注意的是,inode 信息中并没有文件的文件名。下面解释为什么会这样。
当处理目录时,就好像目录实际包含文件一样。例如 home 目录中有 tmp.txt 文件。但是,该目录中并不包含文件。实际上,该目录只包含有文件的名称和文件的 inode 编号。因此,目录的大小相当小。
下面举例说明。当在 home 目录中创建文件 tmp.txt 文件时,首先,系统在硬盘上保留存放该文件的存储空间。接下来,系统查看 inode 表,查找一个空闲的 inode。假设系统查找到的 inode 编号是 #1000,系统会将 tmp.txt 文件的属性信息填到这个 inode 中。最后,系统在 home 目录中存放一个条目。该条目包含名称 tmp.txt ,以及一个编号为 1000 的inode 号。
每当程序需要使用文件时,表面上通过文件名,打开文件。实际上,系统内部这个过程分成三步:首先,系统找到这个文件名对应的 inode 编号;其次,通过 inode 编号,获取 inode 信息;最后,根据 inode 信息访问文件即可。
文件名和 inode 之间的连接称为链接。从概念上讲,链接将文件名和文件本身连接起来。系统使用的是文件的 inode 编号,目录结构为文件命名仅仅是为了便于人们使用。这就是为什么 inode 不包含文件名的原因。
实际上,一个 inode 可以由不止一个文件名引用,换句话说,就是一个文件可以有不止一个名称。这称为 多重链接
。
创建新链接 :ln
硬链接
一般情况下,文件名和 inode 编码是"一一对应"关系,每个 inode 编码对应一个文件名。但是,Unix/Linux系统允许多个文件名指向同一个 inode 编码。
这意味着,可以用不同的文件名访问同样的内容;对文件内容进行修改,会影响到所有文件名;但是,删除一个文件名,不影响另一个文件名的访问。这种情况就被称为"硬链接"(hard link)。
每当创建文件时,文件系统都会自动在文件名和文件之间创建一个链接。如果希望为已有文件创建一个新链接,使用 ln
命令。以下命令都是创建硬链接:
ln tmp.txt new_name.txt
:本来只有 tmp.txt 文件,现在 new_name.txt 和 tmp.txt 指向同一个文件。ln /home/desktop/tmp.txt /etc
:目录 etc 中也会有 tmp.txt,修改 etc 目录中的 tmp.txt 文件的内容,在 home/desktop 目录下的 tmp.txt 文件内容也会跟着修改。
软连接
硬链接允许为同一个文件指定不止一个名称。但是这样的链接有两个限制:
- 不能为目录创建链接。
- 不能为不同文件系统中的文件创建链接
为了实现不同文件系统中的目录或文件链接时,需要创建 符号连接
(symbol link)。使用带 -s
选项的 ln
命令。符号链接包含的不是文件的 inode 编号,而是原文件的路径名。每当访问符号链接时,系统借助该路径名查找文件(类似于 Windows 中的快捷方式)。
当使用 ls -l
显示符号链接文件时,需要注意:
- 文件类型指示符(最左边的字符) 是小写的
l
,表示 link,链接。 - 实际的符号链接在输出行的右边显示 (下图红框内)
注意符号链接文件的大小是 10 字节,这恰好是存储路径名占用的大小。如果要查看文件本身的长列表,必须指定实际的路径名:ls -l ../tmp.txt
。
这种链接方式称为软连接。文件 A 和文件 B 的 inode 编码虽然不一样,但是文件 A 的内容是文件 B 的路径。读取文件 A 时,系统会自动将访问者导向文件 B。因此,无论打开哪一个文件,最终读取的都是文件B。这时,文件 A 就称为文件 B 的"软链接"(soft link)或者"符号链接(symbolic link)。
这意味着,文件 A 依赖于文件 B 而存在,如果删除了文件 B,打开文件 A 就会报错:“No such file or directory”。这是软链接与硬链接最大的不同:文件 A 指向文件 B 的文件名,而不是文件 B 的 inode 编码,文件 B 的 inode "链接数"不会因此发生变化。
基本文件命令的底层逻辑
创建文件:touch,创建目录:mkdir
创建新文件或目录时,Linux 会留出相应的存储空间并创建 inode。然后 Linux 在适当的目录中记录一条新条目,内容是新创建的文件名和 inode 编号。
复制文件:cp
- 复制已有文件时,Linux 用源文件的内容替换目标文件的内容。但是 inode 编号并不进行修改。
- 复制不存在的文件时,Linux 首先用新文件自己的 inode 编号创建一个全新的文件。然后将旧文件的内容复制到新文件中。在复制之后,将会有两个相同的文件。老文件名对应于老的 inode 编号,新的文件名对应于新的 inode 编号。
重命名文件或移动文件:mv
重命名或移动文件时,Linux 改变文件名,或者移动目录条目,或者两者都进行,但是保持相同的 inode 编号。这就是同一条命令既可以重命名文件,又可以移动文件的原因。
创建链接:ln
创建已有文件的新链接时,Linux 使用指定的文件名创建一个新的目录条目,并指向原始文件的 inode 编号。这样,一个文件拥有两个文件名,但是两个文件名指向相同的 inode 编号。
移除链接:mv、rmdir
移除链接时,Linux 通过移除目录条目,消除文件名和 inode 编号之间的连接。如果文件已经没有链接,Linux 会删除该文件。
重点要理解链接的移除与文件的删除并不是一回事。如果文件还有不止一个链接,那么 Linux 不会删除文件,直到移除最后一个链接。
参考资料:
3. 《UNIX & Linux 大学 教程》
4. 《Linux 程序设计》
5. 《理解inode》 - 阮一峰
读后有收获,资助博主养娃 - 千金难买知识,但可以买好多奶粉 (〃‘▽’〃)