Linux(13)——Ext系列文件系统

发布于:2025-06-08 ⋅ 阅读:(21) ⋅ 点赞:(0)

目录

一、理解硬件

📄磁盘的概念

二、引入文件系统 

📄引入块的概念

📄引入分区的概念

📄引入inode的概念 

三、ext2文件系统 

📄宏观认识

📄块组的内部结构

✏️超级块(Super Block)

✏️GDT(Group Descriptor Table)

✏️块位图(Block Bitmap)

✏️inode位图(Inode Bitmap)

✏️节点表(Inode Table)

✏️Data Block

📄文件操作的重新理解 

🧠如何理解创建一个文件?

🧠如何理解文件的写操作? 

🧠如何理解删除一个文件?

🧠如何理解目录?

四、软硬链接

📄软链接

📄硬链接


一、理解硬件

📄磁盘的概念

磁盘(Disk)是计算机中用于长期存储数据的一种存储介质。相比内存(RAM)是临时的、断电后数据会消失,磁盘则可以在断电后保留数据,因此是计算机中非常关键的组成部分之一,磁盘在冯诺依曼体系中可以充当输出设备和输入设备。

磁盘的物理结构

磁盘的存储结构 

这里说明一下: 磁盘存储的基本单位是扇区,512个字节。如图:

我们可以使用fdisk -l命令来查看

如何定位一个扇区:

主要是分以下4个步骤:

  1. 可以先定位磁头(header)
  2. 确定磁头要访问哪一个柱面(磁道)(cylinder)
  3. 定位一个扇区(sector)
  4. CHS地址定位  

这个步骤也就是写入数据到磁盘时的步骤。

磁盘的逻辑结构 

首先,我们得明确一个概念,就是在我们看来磁盘就是一个线性结构

我们可以将磁盘想象成磁带,我们把磁带拉直,也就是将磁盘想象成一种线性的结构,如图:

这样我们就把磁盘分成了一个一个的扇区了,这样我们就有了线性地址,也就是LBA

我们进一步想象:

我们可以将一个磁道展开:

就是一个一维数组

我们将一个柱面展开:

也就成了一个二维数组

我们将整个的磁盘展开:

也就是一个三维数组

所以我们就有了寻址的概念了:先找到柱面,再在柱面上确定是在哪个磁道,再在磁道上确定是在哪个扇区,这就是CHS

CHS和LBA

CHS转LBA:

磁头数*每磁道扇区数=单个柱面的扇区总数
LBA=柱面号C*单个柱面的扇区总数+磁头号H*每磁道扇区数+扇区号S-1
即:LBA=柱面号C*(磁头数*每磁道扇区数)+磁头号H*每磁道扇区数+扇区号S-1

说明一下:扇区号是从1开始的,而在LBA中,地址是从0开始的,柱面和磁道都是从0开始的。

LBA转CHS:

柱面号C=LBA//(磁头数*每磁道扇区数)【就是单个柱面的扇区总数】
磁头号H=(LBA%(磁头数*每磁道扇区数))//每磁道扇区数
扇区号S=(LBA%每磁道扇区数)+1

结论:从现在开始,我们的磁盘就是一个元素为扇区的一个一维数组了,数组的下标就是每个扇区的LBA地址了,我的操作系统使用磁盘就可以用一个数字来访问了。

温馨提示:这里的转换可能比较绕,建议画图或是想象磁盘的结构理解。

二、引入文件系统 

📄引入块的概念

磁盘通常也被称为是块设备,操作系统在访问磁盘时,不是以扇区为单位的,而是以块为单位,一般是4KB的大小,也就是连续的8个扇区。(分治的思想)

根据我们上面的知识的铺垫,我们知道其实这里的块也是可以被计算位置的:

当我们已知了LBA,我们就知道了块号=LBA/8;当我们知道了块号,我们也就知道了LBA=块号*8+n(这里的n是指块内的第几个扇区)

📄引入分区的概念

我们之前也说了,磁盘是以扇区为单位的,也就是512个字节,我们正常一个磁盘大概有512G,也就是10多个亿的扇区。这样对磁盘的管理是很困难的,我们为了更好的管理磁盘,于是就对磁盘进行了分区操作,划分的分区越多就可以对文件的性质区分的越细,按照更为细分的性质存储在不同的地方来管理文件。

我们如果想看Linux中的磁盘分区信息,可以使用下面的命令:

ls /dev/vda* -l

📄引入inode的概念 

我们在使用Linux的命令ls -l的时候,除了可以看见文件名之外我们还可以看见属性的信息:

文件读取磁盘中的信息,然后再显示出来:

我们要以块为单位来存储数据,那么我们就要我们就要有一个地方来存储文件的元数据(属性),比如创建者,时间和大小等信息,这里我们将保存文件元信息的结构称之为inode,中文叫“索引节点”。我们可以使用ls -li来查看:

左边标红的就是文件的inode编号了。

敲黑板:

Linux下文件的存储是属性和内容分离的。

我们也可以来见一见inode:

/*
 * Structure of an inode on the disk
 */

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 */

 union {
 struct {
 __le32 l_i_reserved1;
 } linux1;
 struct {
 __le32 h_i_translator;
 } hurd1;
 struct {
 __le32 m_i_reserved1;
 } masix1;
 } osd1; /* OS dependent 1 */

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

 __le32 i_generation; /* File version (for NFS) */

 __le32 i_file_acl; /* File ACL */

 __le32 i_dir_acl; /* Directory ACL */

 __le32 i_faddr; /* Fragment address */

 union {
 struct {
 __u8 l_i_frag; /* Fragment number */

 __u8 l_i_fsize; /* Fragment size */

 __u16 i_pad1;
__le16 l_i_uid_high; /* these 2 fields */

 __le16 l_i_gid_high; /* were reserved2[0] */

 __u32 l_i_reserved2;
 } linux2;
 struct {
 __u8 h_i_frag; /* Fragment number */

 __u8 h_i_fsize; /* Fragment size */

 __le16 h_i_mode_high;
 __le16 h_i_uid_high;
 __le16 h_i_gid_high;
 __le32 h_i_author;
 } hurd2;
 struct {
 __u8 m_i_frag; /* Fragment number */

 __u8 m_i_fsize; /* Fragment size */

 __u16 m_pad1;
 __u32 m_i_reserved2[2];
 } masix2;
 } osd2; /* OS dependent 2 */

};

/*
 * 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)

备注:EXT2_N_BLOCKS = 15

三、ext2文件系统 

📄宏观认识

我们之前也说了,计算机为了管理磁盘进行了分区,同时计算机为了更好的对分区划分 ,对分区进行了分组操作:

敲黑板:

每个分区前面都会有一个启动块,且这个启动块的大小是确定的,为1KB。

其实我们的块组也是由很多结构组成的,包括超级块(Super Block)、块组描述符表(Group Descriptor Table)、块位图(Block Bitmap)、inode位图(inode Bitmap)、inode表(inode Table)以及数据表(Data Block)组成:

📄块组的内部结构

✏️超级块(Super Block)

存放文件系统本身的结构信息,描述整个分区的文件系统信息。记录的信息主要有:bolck和inode的总量,未使用的block和inode的数量,一个block和inode的大小,最近一次挂载的时间,最近一次写入数据的时间,最近一次检验磁盘的时间等其他文件系统的相关信息。Super Block的信息被破坏,可以说整个文件系统结构就被破坏了


超级块在每个块组的开头都有一份拷贝(第一个块组必须有,后面的块组可以没有)。为了保证文件系统在磁盘部分扇区出现物理问题的情况下还能正常工作,就必须保证文件系统的super block信息在这种情况下也能正常访问。所以一个文件系统的super block会在多个block group中进行备份,这些super block区域的数据保持一致

这里附上源码:

/*
 * Structure of the 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 */

 __le32 s_log_block_size; /* Block size */

 __le32 s_log_frag_size; /* Fragment size */

 __le32 s_blocks_per_group; /* # Blocks per group */

 __le32 s_frags_per_group; /* # Fragments per group */

 __le32 s_inodes_per_group; /* # Inodes per group */

 __le32 s_mtime; /* Mount time */

 __le32 s_wtime; /* Write time */

 __le16 s_mnt_count; /* Mount count */

 __le16 s_max_mnt_count; /* Maximal mount count */

 __le16 s_magic; /* Magic signature */

 __le16 s_state; /* File system state */

 __le16 s_errors; /* Behaviour when detecting errors */

 __le16 s_minor_rev_level; /* minor revision level */

 __le32 s_lastcheck; /* time of last check */

 __le32 s_checkinterval; /* max. time between checks */

 __le32 s_creator_os; /* OS */

 __le32 s_rev_level; /* Revision level */

 __le16 s_def_resuid; /* Default uid for reserved blocks */

 __le16 s_def_resgid; /* Default gid for reserved blocks */
/*
 * These fields are for EXT2_DYNAMIC_REV superblocks only.
 *
 * Note: the difference between the compatible feature set and
 * the incompatible feature set is that if there is a bit set
 * in the incompatible feature set that the kernel doesn't
 * know about, it should refuse to mount the filesystem.
 * 
 * e2fsck's requirements are more strict; if it doesn't know
 * about a feature in either the compatible or incompatible
 * feature set, it must abort and not try to meddle with
 * things it doesn't understand...
 */

 __le32 s_first_ino; /* First non-reserved inode */

 __le16 s_inode_size; /* size of inode structure */

 __le16 s_block_group_nr; /* block group # of this superblock */

 __le32 s_feature_compat; /* compatible feature set */

 __le32 s_feature_incompat; /* incompatible feature set */

 __le32 s_feature_ro_compat; /* readonly-compatible feature set */

 __u8 s_uuid[16]; /* 128-bit uuid for volume */

 char s_volume_name[16]; /* volume name */

 char s_last_mounted[64]; /* directory where last mounted */

 __le32 s_algorithm_usage_bitmap; /* For compression */

 /*
 * Performance hints. Directory preallocation should only
 * happen if the EXT2_COMPAT_PREALLOC flag is on.
 */

 __u8 s_prealloc_blocks; /* Nr of blocks to try to preallocate*/

 __u8 s_prealloc_dir_blocks; /* Nr to preallocate for dirs */

 __u16 s_padding1;
 /*
 * Journaling support valid if EXT3_FEATURE_COMPAT_HAS_JOURNAL set.
 */

 __u8 s_journal_uuid[16]; /* uuid of journal superblock */

 __u32 s_journal_inum; /* inode number of journal file */

 __u32 s_journal_dev; /* device number of journal file */

 __u32 s_last_orphan; /* start of list of inodes to delete */

 __u32 s_hash_seed[4]; /* HTREE hash seed */

 __u8 s_def_hash_version; /* Default hash version to use */

 __u8 s_reserved_char_pad;
 __u16 s_reserved_word_pad;
 __le32 s_default_mount_opts;
 __le32 s_first_meta_bg; /* First metablock block group */

 __u32 s_reserved[190]; /* Padding to the end of the block */

};

✏️GDT(Group Descriptor Table)

块组描述符表,描述块组属性信息,整个分区分成多个块组就对应有多少个块组描述符。每个块组描述符存储一个块组的描述信息,如在这个块组中从哪里开始是inode Table,从哪里开始是Data Blocks,空闲的inode和数据块还有多少个等等。块组描述符在每个块组的开头都有一份拷贝。

这里也附上源码:

// 磁盘级blockgroup的数据结构 

/*
 * Structure of a blocks group descriptor
 */

struct ext2_group_desc

{
 __le32 bg_block_bitmap; /* Blocks bitmap block */

 __le32 bg_inode_bitmap; /* Inodes bitmap */

 __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];
};

✏️块位图(Block Bitmap)

Block Bitmap中记录着Data Block中哪个数据块已经被占用,哪个数据块没有被占用。

✏️inode位图(Inode Bitmap)

每个bit表示一个inode是否空闲可用

✏️节点表(Inode Table)

  • 存放文件属性如 文件大小,所有者,最近修改时间等
  • 当前分组所有Inode属性的集合
  • inode编号以分区为单位,整体划分,不可跨分区 

✏️Data Block

  • 数据区:存放文件内容,也就是一个一个的Block。根据不同的文件类型有以下几种情况:
  • 对于普通文件,文件的数据存储在数据块中
  • 对于目录,该目录下的所有文件名和目录名存储在所在目录的数据块中,除了文件名外,ls -l命令看到的其它信息保存在该文件的inode中
  • Block号按照分区划分,不可跨分区 

敲黑板:

磁盘分区并格式化后,每个分区的inode个数就确定了。

📄文件操作的重新理解 

🧠如何理解创建一个文件?

答:我们通过遍历inode位图来找到一个空的inode,在inode位图中找到inode,再将文件的属性信息写到inode的结构中,将文件的文件名和inode指针添加到目录。

🧠如何理解文件的写操作? 

首先我们要通过文件的inode编号来找到对应的inode结构,再通过inode结构来找到存储文件内容的数据块,将数据写入;如果不存在数据块或者数据块已经写满了,那么可以通过遍历块位图来找到一个空闲的块,再将数据写入块号,最后要建立数据块和inode结构的关系。

🧠如何理解删除一个文件?

首先我们要将文件对应的inode在inode位图中设置为无效,同时还要将文件申请的数据块在位图中设置为无效。我们发现这里并没有直接我们想象中的删除,其实本质上也没有删除,这也就说明了为什么我们删除文件是可以在删除后短时间内恢复的原因,但是如果长时间后,其他我们新创建的文件就会来覆盖我们之前置无效的inode和数据快了,这样就不能恢复了。

🧠如何理解目录?

其实目录也是像我们创建文件一样的操作被保存的,也是有inode和数据内容的,inode中存的就是目录的属性信息,内容就是该目录下的文件名和对应文件的inode指针。

四、软硬链接

📄软链接

我们直接上代码:

我们可以通过这个命令来软链接

ln -s test test.s

我们这里可以发现软链接的文件的inode编号和原来的可执行文件的inode编号是不一样的,也就是说软链接的文件是一个独立的文件了,而且文件的大小也是软链接后的要小一点。

这里的软链接文件实际上是保存了目标文件的路径的,相当于我们Windows下的快捷方式了。

📄硬链接

 我们也是直接上代码:

我们可以通过下面的命令来硬链接

ln test test.h

我们这里也发现,硬链接的inode编号实际上是和可执行文件是一样的,而且文件的大小也是相同的,这里需要注意的我们创建硬链接后我们的硬链接文件和源文件的硬链接数都变成了2。

硬链接文件就是源文件的一个别名,一个文件有几个文件名,该文件的硬链接数就是几。

这里就有了这样一个问题了:为什么我们创建了一个目录的硬链接数是2呢?

我们知道我们创建一个普通文件的硬链接数是2,但是我们发现我们创建一个目录的硬连接数是2:

这是因为我们创建的目录默认是有两个隐藏文件的,那就是.和..了,这两个文件就是硬链接,我们可以分别看看他们的inode编号:

这就是说其实.和..其实就和对应的目录是同样的文件。

我们在这个目录下创建了一个目录,于是就多了一个硬链接数了,就是之前dir目录下的..了。

敲黑板:

这两种链接方式最大的区别就是软链接是创建了文件的,有独立的inode,而硬链接是没有创建文件的,没有独立的inode。