Linux:文件系统


磁盘

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

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

硬盘主要有磁盘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

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

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

文件 = 内容 + 属性 {\color{Red} \mathbf{文件 = 内容 + 属性 } } 文件=内容+属性

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 BitmapData 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_group_desc
{
	__le32	bg_block_bitmap;		/* Blocks bitmap block */
	__le32	bg_inode_bitmap;		/* Inodes bitmap block */
	__le32	bg_inode_table;		/* Inodes table block */
	__le16	bg_free_blocks_count;	/* Free blocks count */
	__le16	bg_free_inodes_count;	/* Free inodes count */
	__le16	bg_used_dirs_count;	/* Directories count */
	__le16	bg_pad;
	__le32	bg_reserved[3];
};

我简单翻译一下注释:

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即可

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


软硬链接的使用场景

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

软链接

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

硬链接

  • 为同一个文件创建多个名称
  • 提高文件访问效率,因为硬链接直接指向文件数据块
  • 备份或归档文件时保留文件的硬链接关系

  • 25
    点赞
  • 17
    收藏
    觉得还不错? 一键收藏
  • 打赏
    打赏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

盒马盒马

你的鼓励将是我创作的最大动力

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值