作者:余性笃厚
个签:努力登上我们所选择的舞台
理解本文将学习到的知识
- 软链接和硬链接形式和原理上的区别
- 为什么软链接的源文件被删除软链接失效,而硬链接却不会失效
- 磁盘未满,创建不了文件的原因之一
- 文件系统的常见组成部分及其存储的内容
- 在 Linux 文件系统中查找文件的过程
- 一些命令,如 df -h、df -i、stat、du 作用
前言
从一次Java岗面试说起,面试官问了一道 Linux 中的硬链接和软链接文件的区别
以及为何内存没满,但是新建不了文件?
之前只是大概看了下八股文,现在稍微深入研究下,希望你看此篇文章不要跳跃阅读,循序渐进看下去!
概念
在 Linux 系统中,链接可分为两种: 一种为硬链接 (Hard Link) ,另一种为软链接 (Soft Link) 或符号链接 (Symbolic Link) ln 命令是用来创建链接文件的,命令格式是 ln [-s] 源文件 链接文件
(注意空格),在默认不带任何参数的情况下,创建的链接是硬链接。举个例子:CentOS 7 中网络配置文件路径较长,为了方便,可以在根路径下创建软链接,[root@centos_100 /] ln -s /etc/sysconfig/network-scripts/ifcfg-ens33 netpro
,当需要修改网络配置时,只需要vim /netpro
嘎嘎方便
注意:
-
本文描述,硬链接即硬链接文件,软链接即软链接文件,是一个意思
-
巧计,软链接英文首字母 s,想创建软链接 ln 后参数为 -s
-
硬链接和软链接就是一个文件,同一个路径下,文件名是不能重复的,否则会报错。
Linux 系统一切皆文件
(后文你会真切感受到这句话意思),这与 Windows 不同,Windows 同一路径下文件夹和文件是可以重名的,且不同类型文件也可以重名
硬链接和软链接的区别分析
* 形式上区别
硬链接和软链接形式上区别在?分析下面图 1 和 图 2 软链接:
- 如图 1,对于创建的软链接,前缀 l 标志表示该文件是软链接(
l
rwxrwxrwx,),并且会显示指向的源文件的路径和名称(-> ../file.txt
) - 如图 2,可以对目录创建软链接
- 如图 3 删除源文件,软链接文件失效
硬链接
- 如图 1,对于创建的硬链接,发现其权限和源文件一致,其实原因就是硬链接文件和源文件共享相同的 Inode【不懂别急,后面会说,耐心看下去】
- 如图 2,不可以对目录创建硬链接
- 如图 4,删除源文件,硬链接文件仍然有效
图 1 软链接和硬链接形式上区别 1
图 2 软链接和硬链接形式上区别 2
图 3 测试删除软链接的源文件
图 4 测试删除硬链接的源文件
前置知识引入
下方我嘎嘎画很多图,你最好 图 + 文字 进行理解!!
图 5 简化的磁盘、分区、文件系统组成图
磁盘
- 概念: 磁盘是指物理存储设备,通常是硬盘驱动器(Hard Disk Drive,HDD)或固态硬盘(Solid State Drive,SSD)等。磁盘是一个整体的硬件设备,用于存储数据
- 路径: 在 Linux 系统中,磁盘的路径通常以
/dev/
开头,例如/dev/sda
或/dev/nvme0n1
,取决于磁盘类型。这样的路径表示整个物理磁盘
分区
- 概念: 分区是指将磁盘划分为逻辑部分的过程,并在每个部分上创建文件系统。每个分区是一个独立的逻辑单元,可以独立地进行格式化、挂载和管理
- 路径: 分区的路径在 Linux 系统中也以
/dev/
开头,例如/dev/sda1
表示磁盘/dev/sda
上的第一个分区,/dev/sda2
表示第二个分区,依此类推
文件系统
- 创建分区时,你可以选择文件系统类型。文件系统是一种
组织和存储数据的方法
,它定义了文件和目录的结构,以及如何在存储设备上存储和检索数据 - 常见的文件系统类型包括但不限于:
ext4: 是 Linux 中最常用的文件系统,提供了高性能和稳定性。ext3是其前身
XFS: 适用于大型文件和大容量的存储设备
NTFS: 是 Windows 系统中常见的文件系统,但也可以在 Linux 中进行读写
FAT32: 适用于兼容性要求较高的存储设备 - 文件系统的组成:
在 Linux 中,一个分区通常对应一个文件系统,而文件系统可以采用不同的格式,如上述ext4、XFS等。每个文件系统都有自己的结构,其中通常包含超级块(Superblock)、目录项(Directory Entry)、Inode 表(Inode Table)、数据块(Data Block)等,
下面将继续深入讲解这几个结构。此外正是因为这些结构的存在占用一定的存储空间,因此实际用于存储用户数据的容量会相对较少
扇区和块
文件储存在硬盘上,硬盘存储的基本单位为扇区(Sector)
,每个扇区的大小通常512B(约0.5KB)或 4KB,过去常见 0.5KB,如今 4KB 已较为普遍。在操作系统读取硬盘时,不是逐个扇区
地读取,因为这样效率太低。相反,操作系统一次性连续读取多个扇区,形成一个称为块(Block)
的单位,又称为数据块/IO块/文件系统块。块通常是扇区大小的倍数,是文件存取的最小单位
。假设一个扇区大小为0.5KB,数据块大小是4KB,则数据块是扇区的 8倍
注意: 上述内容中,"扇区" 附了视频的链接
超级块、目录项、Inode、数据块
简单描述一下 Linux 中查找文件的过程:根据目录项,找到与文件映射的 Inode号,通过 Inode号找到文件对应的 Inode 结构,再通过 Inode 结构中数据块指针找到存储文件内容的数据块
在文件系统中,数据块是存储文件内容的基本单位
,数据块存储文件的实际数据。文件系统将文件的内容分成若干个块,每个块存储一部分文件的数据,而与文件相关的元数据则被储存在 Inode(包括:文件类型、Inode号、权限、文件大小、目录项地址、数据块地址等)
。Inode 即 I-node,英文为 Index node,中文意思 "索引节点" 或 i 节点。Inode 号是作为访问文件的唯一的标识符一种机制,每个文件都有对应的 inode,它包含了与该文件相关的重要信息。
我在下方给出了一个简化的 Inode 结构源码
Inode Table(索引节点表),包含了系统中所有文件和目录的 Inode。每个文件系统都有其独立的 Inode Table,
意味着同一个文件系统下的文件 Inode号 不重复,不同文件系统的 Inode号 可能重复
目录项的结构通常包括文件名或目录名和对应文件的 Inode 号的映射关系。
目录项是存储在特定的数据块中的,这个数据块可以被称为目录块
超级块:存储文件系统的整体信息,如文件系统大小、块大小、Inode 数量等
图 6 简化的查找文件的结构图
在文件系统中寻找文件的过程
下图是从网上扣来的,根据上方前置知识的理解后,我们来看一下查找文件 test.txt 的过程,完整路径 /var/test.txt (这个其实也就是硬链接的查找过程
)
再次强调Linux 系统一切皆文件。
包括 当前目录 . (点)、上层目录 ..(点点)、路径 / 、目录、它们都是文件
下图中先通过超级块获取 / 的 inode号,进而从 i 节点信息表(即 Inode Table)获取 inode 结构,通过 inode 结构中数据块地址,获取数据区中数据块的内容,上面提到过,当数据块存储的是文件名或目录名和对应的 inode号 那么数据块亦可称为目录块,目录块中存储着目录项条目,其中,var 的 inode号 为 786433,继续找到 inode 结构,然后找到 var 对应的目录块,获取 test.txt 的目录项,test.txt 的 inode号 为796326,进而获取 inode 结构,找到 test.txt 的数据块,获取该文件内容
图 7 文件系统中寻找文件的过程
* 原理上区别
这图熟悉吧,这图画的会更复杂一些,因为把软链接文件的查找流程结构图也简单画出来了。硬链接文件查找的过程上面我们已经讲了,要着重软链接,以及软、硬链接原理区别
在上面我们还留下了一个问题顺便解决了,就是 "为什么软链接的源文件被删除软链接失效,而硬链接却不会失效"
硬链接
硬链接是多个文件共享相同的 inode 号码和数据块
。如下图 8,file.txt 和 hlink.txt 和 和 hlink2.txt 指向同一个 inode 结构,也就意味着共用相同数据块。这就是为什么删除硬链接的源文件,硬链接仍然可以访问文件内容的原因,因为说白了源文件或者目标文件,它其实也是硬链接。想要真正地删除这个文件,需要删除所有指向此 inode 结构的文件硬链接不会占用额外的内存,因为它们共享相同的数据块
,只是增加了文件系统中 inode 条目的引用计数
软链接
软链接是一个独立的文件,其中包含指向另一个文件的"路径"字符串
。软链接文件有自己的 inode 号和数据块。如下图 8,slink.txt 有自己的独立的 inode号 和 inode 结构,数据块中存储的是指向源文件的路径,获取源文件路径后还需要按照硬链接文件的查找过程访问文件内容,因此软链接查找文件是需要更多的步骤的软链接会占用一些额外的存储空间,因为它需要存储源文件的路径信息。
当系统访问软链接时,它会解析这个路径,找到源文件。因此,可以举个嘎巴好的例子,软链接更像 Windows 的快捷方式,它占用很少内存,却可以链接到源文件,源文件和快捷方式是独立的两个文件,删除了源文件,那么软链接就获取不到硬链接的资源了
图 8 查找软、硬链接文件的过程
磁盘未满,创建不了文件的原因之一
介绍两命令 df 和 stat 和 du
df -h
:以人类易读的方式显示文件系统的磁盘空间使用情况,以单位(如 KB、MB、GB)展示
df -i
:显示文件系统的 inode 使用情况,包括总 inode 数、已用 inode 数、可用 inode 数、inode 利用率等信息
补充:df
命令的英文全称是 "disk free",翻译为中文就是 "磁盘空间使用情况"stat
:用于显示文件的详细信息,包括文件的权限、所有者、组、大小、创建时间、修改时间等
stat -f
:它显示当前文件所在的文件系统的相关信息,如块大小、基本块大小、块总计、空闲块数、Inodes 总计和空闲 Inodes 数等du -h
:以人类易读的方式查看文件命令用于估算文件或目录的磁盘空间使用情况。默认以块为单位报告磁盘使用情况。每个块的大小取决于文件系统的设置。在常见的 Linux 文件系统中,块的大小通常是 4 KB(4096 字节),如果一个文件不足一个块,它也会占用一个块的空间
关于 Inode 的大小
我们知道,因为 inode 结构需要存储文件的元数据,所以每个 inode 结构会占用一定的空间,这也意味着会消耗硬盘空间。我们不需要关心 inode 到底占多少空间,以及一共占总的多少。你要知道的就是 inode 的总数在格式化时就被确定,一般设置为每 1KB 或 2KB 磁盘空间就创建一个 inode
我的系统是 CentOS Linux release 7.6.1810 (Core),如下图 9,通过计算 47G * 1024 * 1024 / 24637440 ≈ 2 KB,意味着,我的系统是每 2KB 空间分配一个 inode。因此存在 inode 用完了但是磁盘空间没有用完,就会出现无法再创建新文件的情况,尽管磁盘仍有可用空间。
这是因为每个文件都需要一个对应的 inode 来存储文件的元信息,包括权限、所有者、文件大小等。如果 inode 用完了,系统就无法为新文件分配 inode,导致无法创建新文件
如图 10,tempfile 文件内容只有数据 a,实际占用 2 字节,但是文件系统却占用了 4KB,如果有非常多的这种小文件,那么将导致磁盘未满,创建不了文件,当然这种情况其实比较难见
图 9 磁盘分区 inode 信息和磁盘占用情况
图 10 查看文件的元数据
Linux 中简化的 Inode 源码
实际的结构体定义可能会有所不同,具体取决于使用的文件系统和内核版本
c
// 简化的 inode 结构体
struct inode {
struct hlist_node i_hash; // 用于散列链表的节点
struct list_head i_list; // 用于全局链表的节点
struct list_head i_sb_list; // 用于超级块链表的节点
struct list_head i_dentry; // 用于目录项链表的节点
struct super_block *i_sb; // 指向超级块的指针
struct address_space *i_mapping; // 文件的地址空间
unsigned long i_ino; // inode 号
atomic_t i_count; // 引用计数
uid_t i_uid; // 用户 ID
gid_t i_gid; // 组 ID
mode_t i_mode; // 文件权限和类型
struct timespec i_atime; // 最后访问时间
struct timespec i_mtime; // 最后修改时间
struct timespec i_ctime; // 创建时间
struct file_operations *i_fop; // 文件操作集合
struct inode_operations *i_op; // inode 操作集合
struct address_space_operations *i_aop; // 地址空间操作集合
// 其他属性...
// 目录项属性
unsigned int i_generation; // 用于目录项缓存的生成号
unsigned short i_bytes; // 文件名长度
umode_t i_mode; // 目录项文件类型和权限
struct hlist_node i_hash; // 用于散列链表的节点
struct dentry *i_dentry; // 指向目录项的指针
// 数据块属性
struct ext4_inode_info *i_private; // 文件系统特定的私有数据
unsigned long i_state; // inode 状态标志
unsigned long dirtied_when; // 上次脏数据更新的时间
unsigned long dirtied_time_when; // 上次脏时间更新的时间
// 其他属性...
};