学习笔记—Linux—文件系统

目录

磁盘

物理结构

存储结构

逻辑结构

文件系统

inode

[分区 & 分组](#分区 & 分组)

分区管理

[inode Bitmap & inode Table](#inode Bitmap & inode Table)

[Block Bitmap & Data Blocks](#Block Bitmap & Data Blocks)

[GDT & Super Block & Boot Block](#GDT & Super Block & Boot Block)

重新理解目录

软硬链接

软链接

硬链接

软硬链接的使用场景


磁盘

计算机需要存储数据,主要有两种存储数据的硬件,内存和硬盘,其中内存需要通电,具有较高的数据交换效率,适合用于存储计算时产生的临时数据。对于长期保存的数据,一方面来说我们无法保证计算机永远通电,另一方面内存的价格太高了,因此计算机需要硬盘这种无需通电,价格低廉的存储介质来存储数据。

存储在硬盘中的数据,一般称为**文件。本博客讲解在硬盘中,数据是如何被组织管理的,也就是文件系统**。

硬盘主要有**磁盘SSD,而当代的个人计算机已经不使用磁盘了,而是使用SSD**:

上图中,左侧是**磁盘,右侧是SSD**。那么为什么我们还要讲磁盘,而不是SSD呢?

首先,磁盘的文件系统与SSD是十分相似的,磁盘文件系统的设计考虑了存储介质的通用性,因此可以很好地适用于SSD,不过可能会有针对SSD的特殊优化,但是不会影响文件系统的大框架。

其次,我们个人计算机使用的磁盘叫做**消费级磁盘,虽然个人计算机已经不再使用磁盘了,但是由于低廉的价格,服务器主机使用的依然是磁盘,这种磁盘叫做****企业级磁盘。**

Linux作为一个适用于服务器的操作系统,通过磁盘来理解文件系统,毫无疑问是合适的。

物理结构

先来看看磁盘的物理结构,了解磁盘底层是如何运作的。

对于企业级磁来说,一个磁盘会有多个盘面,也就是上图中的样子。

磁盘的盘面很像我们平常见到的光盘,光盘一般是一面图案,一面用于读取。而磁盘的盘面两面都是可以读写的。上图中,**磁头就是用来对磁盘进行读写的工具,马达**会带着盘面高速旋转,此时磁头再来回摆动,就可以通过磁头访问到整个盘面。由于盘的两面都可读写,因此一个盘的正反两面都是需要一个磁头的。

磁盘之所以叫做磁盘,就是因为其通过磁性来存储数据。计算机数据是由众多**01二进制组成的,而磁铁分为NS两级,可以想象磁盘中有无数个小磁铁,通过改变这个小磁铁的NS极,来改变这个位置表达的01**。


存储结构

简单了解磁盘的物理结构后,再看看磁盘的存储结构:

磁盘的每个盘面,都会被为多个等宽的同心圆环,这样一个圆环叫做**磁道/柱面。而一个磁道又会被划分为很多个小扇形,每个扇形叫做一个扇区**。

扇区是磁盘IO的基本单位,大小一般为固定的**512 byte**

有人可能就会有疑问:既然每个扇区的宽度都是相同的,那么内圈的扇区面积小,外圈扇区面积大,为什么扇区的大小都为**512 byte**?

磁盘在生产的时候,厂家会调整不同区域的密度,内圈区域密度大,外圈区域密度小,最后可以保证每个扇区的大小都是相同的。

计算机通过磁盘访问数据时,通过**CSH**定位法:

  1. 决定访问哪一个**磁道(Cylinder)**
  2. 决定访问哪一个**磁头(Head),也就是决定哪一个盘面**
  3. 决定访问哪一个**扇区(Sector)**

只需要确定**CHS**这三者,就可以确定一个磁盘的扇区,进行写入或读取。


逻辑结构

在操作系统眼中,磁盘不是一个环形结构,而是一个逻辑上的结构来访问。

在播音机中,常会见到这样的磁带:

磁带有两个磁带盘,如右图,左侧的磁带被拉直后,经过读写头,再卷入右侧的磁带盘。

这个过程中,环形的磁带被拉直,形成一个线性结构,那么我们是否可以在逻辑上把环形的磁盘拉直为一个**线性结构**?

这样我们就可以把整个磁盘抽象为一个线性结构,此时操作系统对磁盘的管理,就变成了对数组的增删查改!

但是由于扇区的大小为**512 byte,如果操作系统只能每次按照512 byte为单位进行写入,效率太低了。所以操作系统一般以4 kb为单位进行写入,其中4 kb = 8 * 512 byte,即每次按照八个扇区为一个基本单位进行写入。哪怕用户只写入1 byte**数据,操作系统也会在磁盘中开出4 kb大小的空间。

操作系统会在系统层面把八个**扇区合成一个数据块,并对每个数据块进行重新编址,比如上图中,1 - 8号扇区,就被合成了1号数据块。这个数据块的地址,叫做LBA(Logical Block Address)地址,当操作系统要对磁盘写入时,先把LBA地址转化为线性地址,也就是一个个扇区的地址,然后再把线性地址转化为CHS地址,之后磁盘就可以根据CHS**地址来访问到指定扇区了。


文件系统

简单了解了磁盘的硬件结构后,再来看看操作系统是如何在全局上管理这个磁盘的。

inode

所有被存储在磁盘的数据,都是以文件的形式,那么每个文件是如何被管理的呢?

文件可以被分为两个部分:

文件=内容+属性文件 =内容 +属性

在Linux中,文件的内容和属性是分开管理的,因为文件的内容大小是不确定的,而每个文件的具有属性都是相同的,只是属性值不同。Linux把文件的属性放在结构体**struct inode**中管理。

**Linux 2.6.10内核的struct inode**部分源码如下:

复制代码
struct inode {
	struct hlist_node	i_hash;
	struct list_head	i_list;
	struct list_head	i_dentry;
	unsigned long		i_ino;
	atomic_t		i_count;
	umode_t			i_mode;
	unsigned int		i_nlink;
	uid_t			i_uid;
	gid_t			i_gid;
	dev_t			i_rdev;
	loff_t			i_size;
	struct timespec		i_atime;
	struct timespec		i_mtime;
	struct timespec		i_ctime;
	//......
};

由于**inode的成员太多,我这里只截取了一小部分,大约是总量的四分之一。所有文件的属性都被这样的inode**管理。

以下是**inode**中一些重要的成员及其含义:

  1. i_inoinode 的编号,这个编号是文件系统中唯一标识一个 inode 的数字

  2. i_mode:文件的访问权限和文件类型(常规文件、目录、设备文件等)

  3. i_uidi_gid:文件所有者的用户ID和组ID

  4. i_size:文件大小(以字节为单位)

  5. i_atimei_mtimei_ctime:最后访问时间、最后修改时间和最后状态改变时间

  6. i_blocks:文件占用的磁盘块数

  7. i_links_count:文件的硬链接数

这些**inode**成员记录了文件的基本属性、访问权限、所有权、位置信息等,是文件系统管理文件的关键数据结构。不同的文件系统可能会有一些细微的差异,但基本结构和含义是相同的。

以上重要成员中,你也许对6 7有疑问,这三者会在博客后续讲解。

第一条**i_ino用于在一个文件系统中标识一个唯一的inode,操作系统对文件写入时,就是通过i_ino来查找文件的,我们一般称其为inode编号**。

可以通过ls-i选项查看文件的inode编号:

其中左侧第一栏就是文件对应的inode编号了。


分区 & 分组

现在的计算机已经不怎么会缺少存储空间了,因为硬盘的价格普遍不贵,很多计算机的容量都是以**TB为单位了,服务器中的存储空间就更大了。操作系统不可能一次性对以TB为单位的磁盘进行管理,于是就把磁盘分为很多个区域,然后分别管理每一个区域,这就是磁盘分区**。

提到分区,想必第一个想到的就是各自**Windows主机中的C盘D盘了,以下为我的个人主机,大小约为1TB**:

C盘D盘并不是两个硬盘,而是一个硬盘的两个分区。也就是说是我的Windows中,把1TB 分为了两个分区来管理,当然你也可以自己再划分更多的分区出来。

而对于一个分区,操作系统又会把它划分为很多个分组:

现在操作系统只需要按照相同的方式来管理一个个相同的分组,就可以管理好整个硬盘了。


分区管理

现在开始,我们只讨论一个分区内部如何管理的了,因为每个分区的管理是相同的,只需要理解一个分区如何管理,就知道所有分区如何管理,最后就知道整个磁盘如何管理的了。

操作系统通过**文件系统来管理一个分区,每个分区都会有一套独立的文件系统,现在主流文件系统是Ext3文件系统,本博客后续讲解的是Ext2**文件系统,两者没有太大差别。

值得注意是,一个硬盘可以有多个分区,不同分区是可以搭载不同的文件系统的。

基于**Ext2**文件系统,每个分区都有如下结构:


inode Bitmap & inode Table

一个分组中,会有大量的文件,每个文件都要有自己的**inode来存储自己的信息,一个分组的所有文件的inode结构体,都存储在该分组的inode Table**中。

当新创建文件的时候,要给这个文件分配一个**inode,那么一个分组要如何给一个文件分配inode呢?总不可能是生成一个随机数,然后直接分配这个inode编号对应的inode**给文件吧?

因此每个分组还会维护一张位图**inodeBitmap,其用inode编号对应的位来标记一个inode的状态。通过查找位图,从而快速判断一个inode**是否已经被分配过了。

inode描述一个文件属性的结构体,包含了大量文件的属性。Ext2也有自己的inode,其基于inode增加了某些其它信息,用于帮助管理。

**Linux 2.6.10内核的struct ext2_inode**部分源码如下:

复制代码
struct ext2_inode {
	__le16	i_mode;		/* File mode */
	__le16	i_uid;		/* Low 16 bits of Owner Uid */
	__le32	i_size;		/* Size in bytes */
	__le32	i_atime;	/* Access time */
	__le32	i_ctime;	/* Creation time */
	__le32	i_mtime;	/* Modification time */
	__le32	i_dtime;	/* Deletion Time */
	__le16	i_gid;		/* Low 16 bits of Group Id */
	__le16	i_links_count;	/* Links count */
	__le32	i_blocks;	/* Blocks count */
	__le32	i_flags;	/* File flags */
	//...

	__le32	i_block[EXT2_N_BLOCKS];/* Pointers to blocks */
	//...
};

我们可以从中看到非常多熟悉的成员,比如**i_modei_uid**等等,但是我特意在最后写出来了一个i_block成员,这是一个极为重要的成员,其与硬盘空间分配有关,接下来我们了解一下空间是如何分配的。


Block Bitmap & Data Blocks

刚才提到,文件 = 内容 + 属性,我们已经了解了文件的属性被**inode管理,而inode被分组中的inode Table管理。那么文件系统如何管理内容的呢?这就是Block BitmapD****ata Blocks**的任务了。

分组是用来存储文件的,自然要存储文件的内容,而文件的内容要放到数据块中,而所有的数据块,都由**Data Blocks**管理。

与**inode Bitmap相似的,Block Bitmap**也是一个位图,用于标识哪些数据块被使用了,哪些没被使用,从而快速给文件分配数据块。

那么**Data Blocks**是如何分配数据的?

我们在上一个小节末尾留了一个成员**i_block,其存在于inode**中:

复制代码
struct ext2_inode {
	//...
	__le32	i_block[EXT2_N_BLOCKS];/* Pointers to blocks */
	//...
};

Linux源码对其的注释是/* Pointers to blocks */,也就是指向的数据块。即**inode中的i_block**成员,用于存储该文件使用了哪些数据块。

i_block是一个数组,元素的类型是__le32,本质上是一个**32位的int类型,元素的个数是EXT2_N_BLOCKS,这个值等于15**。

在源码中,**EXT2_N_BLOCKS**定义如下:

复制代码
/*
 * Constants relative to the data blocks
 */
#define	EXT2_NDIR_BLOCKS		12
#define	EXT2_IND_BLOCK			EXT2_NDIR_BLOCKS
#define	EXT2_DIND_BLOCK			(EXT2_IND_BLOCK + 1)
#define	EXT2_TIND_BLOCK			(EXT2_DIND_BLOCK + 1)
#define	EXT2_N_BLOCKS			(EXT2_TIND_BLOCK + 1)

最后值就是**12 + 1 + 1 + 1 = 15**,为什么要周转这么久,一个一个加上去?

了解了**i_block**是如何指向数据块后,我们就可以解决这个问题了:

i_block的数组下标分为四个区域:[0, 11][12][13][14]

前十二个元素**[0, 11]**,它们直接指向存储文件内容的数据块:

哪些数据块存储了文件内容,这些元素就存储对应数据块的编号。

下标为**[12]**的元素,指向一级间接块

当文件使用的数据块超过了**12个,就会启用下标为[12]的元素,其指向一级间接块**,一级间接块中存储了其他数据块的编号,被一级间接块指向的数据块,才是真正存放文件内容的数据块。

上图中,绿色的数据块是存放文件内容的数据块,蓝色数据块是一级间接块。

下标为**[13]**的元素,指向二级间接块

当使用的数据块数量超过一级间接块的范围,就会启用下标为**[13]的元素,其指向二级间接块**,二级间接块指向一级间接块,被一级间接块指向的数据块,才是真正存放文件内容的数据块。上图中,灰色的为二级间接块。

下标为**[14]**的元素,指向三级间接块

当使用的数据块数量超过二级间接块的范围,就会启用下标为**[14]的元素,其指向三级间接块**,三级间接块指向二级间接块。上图中,紫色的为三级间接块。

文件系统利用这样一个间接块的设计,用长度为**15**的数组,保存了文件使用的所有数据块。

现在我们再看到当时**EXT2_N_BLOCKS**的定义过程:

复制代码
/*
 * Constants relative to the data blocks
 */
#define	EXT2_NDIR_BLOCKS		12
#define	EXT2_IND_BLOCK			EXT2_NDIR_BLOCKS
#define	EXT2_DIND_BLOCK			(EXT2_IND_BLOCK + 1)
#define	EXT2_TIND_BLOCK			(EXT2_DIND_BLOCK + 1)
#define	EXT2_N_BLOCKS			(EXT2_TIND_BLOCK + 1)
  1. #define EXT2_NDIR_BLOCKS 12

    • 这个宏定义了 Ext2 文件系统中 inode 中直接块的数量为 12
  2. #define EXT2_IND_BLOCK EXT2_NDIR_BLOCKS

    • 其中 IND表示:singly indirect block 一级间接块
    • 这个宏定义了一级间接块的数组下标为 12
  3. #define EXT2_DIND_BLOCK (EXT2_IND_BLOCK + 1)

    • 其中 DIND表示:doubly indirect block 二级间接块
    • 这个宏定义了二级间接块的数组下标为 13
  4. #define EXT2_TIND_BLOCK (EXT2_DIND_BLOCK + 1)

    • 其中 TIND表示:triple indirect block 三级间接块
    • 这个宏定义了三级间接块的数组下标为 14
  5. #define EXT2_N_BLOCKS (EXT2_TIND_BLOCK + 1)

    • 这个宏定义了 Ext2 文件系统中总共的数据块索引数为 15
    • 包括 12 个直接块、1 个一级间接块、1 个二级间接块和 1 个三级间接块。

GDT & Super Block & Boot Block

现在我们已经讲清楚了一个文件是如何存储的,文件 = 内容 + 属性,属性被**inode Table管理,而内容被Data Blocks管理。那么这个文件系统整体又是如何被管理的呢?这就与Super BlockGroup Descriptor Table还有Boot Block**有关了。

Group Descriptor Table叫做表块组描述符,简称**GDT**,用于宏观描述一个分组。

**Linux 2.6.10内核的struct ext2_group_desc**源码如下:

复制代码
struct ext2_super_block {
	__le32	s_inodes_count;		/* Inodes count */
	__le32	s_blocks_count;		/* Blocks count */
	__le32	s_r_blocks_count;	/* Reserved blocks count */
	__le32	s_free_blocks_count;	/* Free blocks count */
	__le32	s_free_inodes_count;	/* Free inodes count */
	__le32	s_first_data_block;	/* First Data Block */
	//...
};

我简单翻译一下注释:

bg_block_bitmap:组中块位图所在的块号
bg_inode_bitmap:组中索引节点位图所在块的块号
bg_inode_table:组中索引节点表的首块号
bg_free_blocks_count:组中空闲块数
bg_free_inodes_count:组中空闲索引节点数
bg_used_dirs_count:组中分配给目录的节点数

可以看到,**GDT**描述了各个区域的起始位置,以及当前分组的总体状态,每个分组都有自己的GDT

**Super Block**用于存放文件系统本身的结构信息

Linux 2.6.10内核的struct ext2_super_block 部分源码如下:

复制代码
struct ext2_super_block {
	__le32	s_inodes_count;		/* Inodes count */
	__le32	s_blocks_count;		/* Blocks count */
	__le32	s_r_blocks_count;	/* Reserved blocks count */
	__le32	s_free_blocks_count;	/* Free blocks count */
	__le32	s_free_inodes_count;	/* Free inodes count */
	__le32	s_first_data_block;	/* First Data Block */
	//...
};

从上图中可以看出这个Super Block存储了整个文件系统的inode的数量,数据块的数量等等。这种对文件系统宏观描述的结构,因该来说每个分区只需要一个即可,为什么会放在分组里面呢?

并不是所有的分组都有Super Block,只有非常小一部分分组有。**Super Block是代表一个文件系统的核心数据结构,一旦Super Block损毁,整个文件系统都会崩溃,因此文件系统不只把Super Block**保留一份,而是保留多份放到不同分组中。

需要**Super Block时到特定分组去访问,一旦某个分组的Super Block被损毁,用其它的Super Block进行拷贝,从而修复Super Block**。这样可以极大提高文件系统的稳定性。

Boot Block用于计算机开机时告知磁盘的分区情况,帮助计算机加载操作系统等

操作系统本质也是一个软件,开机时计算机要加载操作系统,毫无疑问操作系统也要被存储在硬盘中,**Boot Block**就会存储操作系统的存储位置,从而帮助计算机启动操作系统。

**Boot Block**不仅仅是一个分区只有一个,而是整个硬盘中只有一个,放在整个硬盘的第一个分区头部。


现在总结一下刚才讲解的结构:

  • inode Table:存储当前分组所有文件的**inode**
  • inode Bitmap:标识当前分组的**inode**的使用情况
  • Data Blocks:管理当前分组的数据块
  • Block Bitmap:标识当前分组的数据块的使用情况
  • GDT:宏观描述一个分组
  • Super Block:描述一个分区,文件系统的核心
  • Boot Block:描述整个硬盘的分区情况,帮助计算机加载操作系统

重新理解目录

有了以上知识后,我们再来重新理解一下目录这个结构。目录的本质也是文件,那么目录是如何存储其内部的文件的呢?

目录内部存储的是**文件名** 到**inode编号**的映射关系

这里有一个很重要的知识:文件名不属于文件的属性,也不存在于inode中!

文件名存在于目录中,而不存在于文件本身。我们通过目录来访问文件名,本质是去目录文件中,通过文件名找到对应的inode编号,然后再访问到文件。

比如说现在通过路径**/usr/bin/ls**来访问一个文件,其过程为:

  1. 先在根目录中找到文件名**usr对应的inode编号**
  2. 访问文件**usr,在文件usr中找到文件名bin对应的inode编号**
  3. 访问文件**bin,在文件bin中找到文件名ls对应的inode编号**
  4. 访问文件**ls**

软硬链接

在Linux中,文件和目录有两种特殊的链接方式,分别称为软链接(Soft Link)和硬链接(Hard Link)。这两种链接方式都是用于创建文件或目录的快捷方式,但它们在实现原理和使用场景上存在一些差异。

软链接

软链接也称为符号链接,它是一种特殊的文件,其中包含了另一个文件或目录的路径信息

创建软链接的命令如下:

ln -s 源文件/目录 软链接名称

此处的**-s表示soft**。

示例:

当前目录下有一个**soft.txt**:

现在为其建立一个软链接,名为**link-soft.txt**:

可以看到我们成功创建了一个软链接,使用ls时也指明了**link-soft.txt -> soft.txt,即文件link-soft.txtsoft.txt**的链接。

通过最左侧第一栏可知,软链接和原文件的inode不同,说明是不同的两个文件,只是软链接的文件内部存储的是目标文件的路径。

软链接的主要特点如下:

  1. 文件类型 :软链接是一种特殊的文件类型,在文件列表中以 l 开头表示
  2. 链接目标:软链接中存储的是链接目标的路径信息,而不是实际的文件内容
  3. 独立性:软链接是独立于链接目标的,即使链接目标被删除或移动,软链接仍然存在,只是无法访问实际的文件或目录
  4. 跨文件系统:软链接可以跨越不同的文件系统,链接到不同分区或网络共享中的文件或目录

硬链接

硬链接是另一种创建文件快捷方式的方法,它与软链接有一些不同之处。

创建硬链接的命令如下:

ln 源文件/目录 硬链接名称

示例:

当前目录下有一个**hard.txt**:

现在为其建立一个硬链接,名为**link-hard.txt**:

可以看到我们成功创建了一个硬链接,值得注意的是:硬链接的**inode与原文件的inode** 相同

硬链接本质上,只是在目录内部,多增加了一个文件名与**inode**的映射关系。

从左往右数第三栏,你会发现**soft.txt的值为1,而hard.txt的值为2,这个值叫做硬连接数,即有多少个相同的文件名指向这个inode**。

硬链接的主要特点如下:

  1. 文件类型:硬链接在文件列表中看起来与普通文件没有区别
  2. 链接目标:硬链接指向实际文件的数据块,而不是文件路径
  3. 独立性:硬链接依赖于链接目标,如果链接目标被删除,硬链接也将失效
  4. 跨文件系统:硬链接只能在同一个文件系统内创建,不能跨越不同的文件系统

如果想要删除一个软连接或者硬连接,使用指令**unlink**即可。

还要注意的是:不能给目录创建硬连接,只能给目录创建软链接。


软硬链接的使用场景

软链接和硬链接各有适用的场景:

软链接

  • 在不同文件系统或分区之间创建快捷方式
  • 链接到可能会被移动或删除的文件或目录
  • 创建指向目录的快捷方式

硬链接

  • 为同一个文件创建多个名称
  • 提高文件访问效率,因为硬链接直接指向文件数据块
  • 备份或归档文件时保留文件的硬链接关系
相关推荐
科技林总2 小时前
【系统分析师】12.1 软件架构的概念
学习
FC皇家慕尼黑2 小时前
Claude Code学习--从搭建Nano Claude Code学习CC机制的底层原理
学习·ai·claude code
AI视觉网奇2 小时前
动作迁移算法笔记 2026
人工智能·笔记
happymaker06262 小时前
web前端学习日记——DAY01(HTML基本标签)
前端·学习·html
IMPYLH2 小时前
Linux 的 chmod 命令
linux·运维·服务器
FC皇家慕尼黑2 小时前
Claude Code学习--从搭建Nano Claude Code学习CC机制的底层原理pt2
学习·ai·claude code
handler012 小时前
基础算法:分治
c语言·开发语言·c++·笔记·学习·算法·深度优先
迷茫青年2 小时前
带你进入linux的世界,linux基础知识讲解
linux
码喽7号3 小时前
Springboot学习六:MybatisPlus的多表查询以及分页查询
java·spring boot·学习