【Linux】Ext系列文件系统(下)

目录

一、宏观认识

二、Block Group

2.1 Data Block

2.2 i节点表(Inode Table) 和 inode

三、块组内部构成

3-1 超级块(Super Block)

3-2 GDT(Group Descriptor Table)

3-3 块位图(Block Bitmap)

3-4 inode位图(Inode Bitmap)

3-5 i节点表(Inode Table)

3-6 Data Block

四、inode和datablock映射

五、目录与文件名

六、路径解析

七、路径缓存

八、挂载分区

 8.1 小实验

 8.2 结论

文件系统总结


个人主页:uyeonashi-优快云博客

一、宏观认识

我们想要在硬盘上储文件,必须先把硬盘格式化为某种格式的文件系统,才能存储文件文件系统的目的就是组织和管理硬盘中的文件

在Linux 系统中,最常见的是 ext2 系列的文件系统。其早期版本为 ext2,后来又发展出 ext3 和 ext4。ext3 和 ext4 虽然对 ext2 进行了增强,但是其核心设计并没有发生变化,我们仍是以较老的 ext2 作为演示对象。

ext2文件系统将整个分区划分成若干个同样大小的块组 (Block Group),如下图所示。只要能管理一个分区就能管理所有分区,也就能管理所有磁盘文件

上图中启动块(Boot Block/Sector)的大小是确定的,为1KB,由PC标准规定,用来存储磁盘分区信息和启动信息,任何文件系统都不能修改启动块。启动块之后才是ext2文件系统的开始。

二、Block Group

ext2文件系统会根据分区的大小划分为数个Block Group。而每个Block Group都有着相同的结构组成。

2.1 Data Block

文件=内容+属性。文件的内容就存放在Date Block区域

数据区:存放文件件内容,也就是⼀个⼀个的Block。根据不同的文件类型有以下几种情况:

  • 对于普通文件,文件的数据存储在数据块中。
  • 对于目录,该目录下的所有文件名和目录名存储在所在目录的数据块中,除了文化名外,ls -l命令看到的其它信息保存在该文件的inode中。
  • Block 号按照分区划分,不可跨分区

2.2 i节点表(Inode Table) 和 inode

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

inode存储文件的属性。文件的属性的大小是一样的。innode是一个结构体,存储文件的各种属性。

struct ext2_inode {
    __le16 i_mode;    // 文件模式,如文件类型和权限
    __le16 i_uid;     // 文件所有者的用户 ID 低 16 位
    __le32 i_size;    // 文件大小(字节)
    __le32 i_atime;   // 最后访问时间
    __le32 i_ctime;   // 创建时间
    __le32 i_mtime;   // 最后修改时间
    __le32 i_dtime;   // 删除时间
    __le16 i_gid;     // 文件所属组 ID 低 16 位
    __le16 i_links_count; // 硬链接计数
    __le32 i_blocks;  // 文件占用的数据块数量
    __le32 i_flags;   // 文件标志
    __le32 i_block[EXT2_N_BLOCKS]; // 指向数据块的指针数组
    // 其他字段...
};

文件名不会作为文件的属性保存在文件的inode里。
inode Table也就是数据块大小为4kb,一个inode为128字节。也就是说一个数据块中存储32个inode。操作系统和文件系统交互的时候,这些数据都会读进去操作系统按需读取。

结构体中有一个指向数据块的指针数组,通过指针找到inode对应的内容。

三、块组内部构成

3-1 超级块(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 */
};

3-2 GDT(Group Descriptor Table)

块组描述符表,描述块组属性信息,整个分区分成多个块组就对应有多少个块组描述符。每个块组描述符存储一个块组 的描述信息,如在这个块组中从哪里开始是inode Table,从哪里开始是DataBlocks,空闲的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];
};

3-3 块位图(Block Bitmap)

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

3-4 inode位图(Inode Bitmap)

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

3-5 i节点表(Inode Table)

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

3-6 Data Block

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

四、inode和datablock映射

• inode内部存在 __le32 i_block[EXT2_N_BLOCKS];/* Pointers to blocks */ ,EXT2_N_BLOCKS =15,就是用来进行inode和block映射的
• 这样文件=内容+属性,就都能找到了。

📌 思考:
请解释:知道inode号的情况下,在指定分区,请解释:对文件进行增、删、查、改是在做什么?

 💡 结论:
        • 分区之后的格式化操作,就是对分区进行分组,在每个分组中写入SB、GDT、BlockBitmap、Inode Bitmap等管理信息,这些管理信息统称: 文件系统
        • 只要知道文件的inode号,就能在指定分区中确定是哪一个分组,进而在哪一个分组确定是哪一个inode
        • 拿到inode文件属性和内容就全部都有了

下⾯,通过touch⼀个新⽂件来看看如何⼯作:

[root@localhost linux]# touch abc
[root@localhost linux]# ls -i abc
263466 abc

为了说明问题,我们将上图简化:

创建一个新文件主要有以下4个操作:
1. 存储属性
        内核先找到一个空闲的i节点(这里是263466)。内核把文件信息记录到其中。
2. 存储数据
        该文件需要存储在三个磁盘块,内核找到了三个空闲块:300,500,800。将内核缓冲区的第一块数据复制到300,下一块复制到500,以此类推。
3. 记录分配情况
        文件内容按顺序300,500,800存放。内核在inode上的磁盘分布区记录了上述块列表。
4. 添加文件名到目录
        新的文件名abc。linux如何在当前的目录中记录这个文件?内核将入口(263466,abc)添加到目录文件。文件名和inode之间的对应关系将文件名和文件的内容及属性连接起来。

五、目录与文件名

• 目录也是文件,但是磁盘上没有目录的概念,只有文件属性+文件内容的概念。
• 目录的属性不用多说,内容保存的是:文件名和Inode号的映射关系

我们平常通过路径+文件名打开文件是通过,先打开我所在的路径,读取对应的目录里的数据内容得到文件名和inode的映射关系,以此来进行文件的查找。

访问文件必须要知道当前⼯作目录,本质是必须能打开当前工作目录文件,查看目录文件的内容!

:~/learn/test_12_1/code.c

要访问code.c就必须打开test_12_1(当前工作目录),以获取test.c对应的inode进而对文件进行访问。以此类推要从最开始一层一层的往下剥。所以,找到任何Linux文件,都必须从跟目录开始,进行路径解析直到找到对应的文件。

六、路径解析

何文件,都有路径,访问目标文件,如/home/whb/code/test/test/test.c,都要从根目录开始,依次打开每⼀个目录,根据目录名,依次访问每个目录下指定的目录,直到访问到test.c。这个过程叫做Linux路径解析。

访问文件,都是指令/工具访问,本质是进程访问,进程有CWD!进程提供路径。open文件,就提供了路径。

上面所有行为:本质就是在磁盘文件系统中,新建目录文件。而你新建的任何文件,都在你或者系统指定的目录下新建,这不就是天然就有路径了嘛!系统+用户共同构建Linux路径结构。

七、路径缓存

面聊了,我们访问任何文件都要从跟目录进行路径解析。可是这样太慢了,所以Linux会进行缓存历史路径结构。Linux中,在内核中维护树状路径结构的内核结构体叫做: struct dentry
在Linux中,进行路径解析的时候,会把我们历史访问的所以的目录(路径)形成一颗多叉树,进行保存。

struct dentry {
    /* RCU lookup touched fields */
    unsigned int d_flags;		/* 目录项标志,用于表示目录项的状态和属性 */
    seqcount_t d_seq;		/* 顺序计数器,用于 RCU(Read-Copy Update)同步机制 */
    struct hlist_bl_node d_hash;	/* 哈希链表节点,用于将目录项插入到哈希表中 */
    struct dentry *d_parent;	/* 指向父目录项的指针,用于构建目录树结构 */
    struct qstr d_name;		/* 目录项的名称,以 `struct qstr` 结构体表示 */
    struct inode *d_inode;		/* 指向关联的 inode 的指针,inode 存储文件元数据 */
    unsigned char d_iname[DNAME_INLINE_LEN];	/* 内联存储的目录项名称,提高性能 */

    /* Ref lookup touched fields */
    struct lockref d_lockref;	/* 用于引用计数的锁,控制目录项的生命周期 */
    const struct dentry_operations *d_op;	/* 指向目录项操作函数集的指针,包含如 d_hash、d_compare 等操作 */
    struct super_block *d_sb;	/* 指向关联的超级块的指针,超级块存储文件系统的全局信息 */
    unsigned long d_time;		/* 目录项的时间戳,用于缓存管理 */

    /* d_lru list */
    struct list_head d_lru;		/* 链表节点,用于将目录项插入到最近最少使用(LRU)链表中 */

    /* d_child and d_rcu can share memory */
    union {
        struct list_head d_child;	/* 链表节点,用于将目录项插入到父目录的子目录项链表中 */
        struct rcu_head d_rcu;		/* RCU 头,用于 RCU 机制下的延迟释放 */
    };

    /* d_subdirs and d_alias can share memory */
    union {
        struct list_head d_subdirs;	/* 链表头,用于管理子目录项链表 */
        struct hlist_node d_alias;	/* 哈希链表节点,用于将目录项插入到 inode 的别名链表中 */
    };

    /* 私有数据,由文件系统特定使用 */
    void *d_fsdata;
};
  • 每个文件其实都要有对应的dentry结构,包括普通文件。这样所有被打开的⽂件,就可以在内存中形成整个树形结构
  • 整个树形节点也同时会隶属于LRU(Least Recently Used,最近最少使用)结构中,进行节点淘汰
  • 整个树形节点也同时会隶属于Hash,方便快速查找
  • 更重要的是,这个树形结构,整体构成了Linux的路径缓存结构,打开访问任何文件,都在先在这棵树下根据路径进行查找,找到就返回属性inode和内容,没找到就从磁盘加载路径,添加dentry结构,缓存新路径

八、挂载分区

我们已经能够根据inode号在指定分区找文件了,也已经能根据目录文件内容,找指定的inode了,在指定的分区内,我们可以为所欲为了。可是:

问题:inode不是不能跨分区吗?Linux不是可以有多个分区吗?我怎么知道我在哪一个分区???

 8.1 小实验

$ dd if=/dev/zero of=./disk.img bs=1M count=5 #制作⼀个⼤的磁盘块,就当做⼀个分区 
$ mkfs.ext4 disk.img # 格式化写⼊⽂件系统 
$ mkdir /mnt/mydisk # 建⽴空⽬录 
$ df -h # 查看可以使⽤的分区 
Filesystem Size Used Avail Use% Mounted on
udev 956M 0 956M 0% /dev
tmpfs 198M 724K 197M 1% /run
/dev/vda1 50G 20G 28G 42% /
tmpfs 986M 0 986M 0% /dev/shm
tmpfs 5.0M 0 5.0M 0% /run/lock
tmpfs 986M 0 986M 0% /sys/fs/cgroup
tmpfs 198M 0 198M 0% /run/user/0
tmpfs 198M 0 198M 0% /run/user/1002
$ sudo mount -t ext4 ./disk.img /mnt/mydisk/ # 将分区挂载到指定的⽬录 
$ df -h
Filesystem Size Used Avail Use% Mounted on
udev 956M 0 956M 0% /dev
tmpfs 198M 724K 197M 1% /run
/dev/vda1 50G 20G 28G 42% /
tmpfs 986M 0 986M 0% /dev/shm
tmpfs 5.0M 0 5.0M 0% /run/lock
tmpfs 986M 0 986M 0% /sys/fs/cgroup
tmpfs 198M 0 198M 0% /run/user/0
tmpfs 198M 0 198M 0% /run/user/1002
/dev/loop0 4.9M 24K 4.5M 1% /mnt/mydisk
$ sudo umount /mnt/mydisk # 卸载分区 
whb@bite:/mnt$ df -h
Filesystem Size Used Avail Use% Mounted on
udev 956M 0 956M 0% /dev
tmpfs 198M 724K 197M 1% /run
/dev/vda1 50G 20G 28G 42% /
tmpfs 986M 0 986M 0% /dev/shm
tmpfs 5.0M 0 5.0M 0% /run/lock
tmpfs 986M 0 986M 0% /sys/fs/cgroup
tmpfs 198M 0 198M 0% /run/user/0
tmpfs 198M 0 198M 0% /run/user/1002

 📌 注意:
/dev/loop0 在Linux系统中代表第一个循环设备(loop device)。循环设备,也被称为回环设备或者loopback设备,是一种伪设备(pseudo-device),它允许将文件作为块设备(block device)来使用。这种机制使得可以将文件(比如ISO镜像文件)挂载(mount)为文件系统,就像它们是物理硬盘分区或者外部存储设备一样

whb@bite:/mnt$ ls /dev/loop* -l
brw-rw---- 1 root disk 7, 0 Oct 17 18:24 /dev/loop0
brw-rw---- 1 root disk 7, 1 Jul 17 10:26 /dev/loop1
brw-rw---- 1 root disk 7, 2 Jul 17 10:26 /dev/loop2
brw-rw---- 1 root disk 7, 3 Jul 17 10:26 /dev/loop3
brw-rw---- 1 root disk 7, 4 Jul 17 10:26 /dev/loop4
brw-rw---- 1 root disk 7, 5 Jul 17 10:26 /dev/loop5
brw-rw---- 1 root disk 7, 6 Jul 17 10:26 /dev/loop6
brw-rw---- 1 root disk 7, 7 Jul 17 10:26 /dev/loop7
crw-rw---- 1 root disk 10, 237 Jul 17 10:26 /dev/loop-control

 8.2 结论

• 分区写入文件系统,无法直接使用,需要和指定的目录关联,进行挂载才能使用。
• 所以,可以根据访问目标文件的"路径前缀"准确判断我在哪一个分区。

文件系统总结


本篇完!

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

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

余额充值