【Linux学习笔记】ext2文件系统的深度剖析
【Linux学习笔记】ext2文件系统的深度剖析
🔥个人主页:大白的编程日记
🔥专栏:Linux学习笔记
文章目录
- 【Linux学习笔记】ext2文件系统的深度剖析
- 前言
- 一.ext2文件系统
- 1.1宏观认识
- 二. Block Group
- 三. 块组内部构成
- 3.1 超级块(Super Block)
- 3.2 GDT(Group Descriptor Table)
- 3.3 块位图(Block Bitmap)
- 3.4 inode位图(Inode Bitmap)
- 3.5 节点表(Inode Table)
- 3.6 Data Block
- 3.7 inode和datablock映射(弱化)
- 3.8 目录与文件名
- 3.9 路径解析
- 3.10 路径缓存
- 3.11 挂载分区
- 3.11.1 一个实验:
- 3.8.2 一个结论
- 四 软硬连接
- 4.1 硬链接
- 4.2 软链接
- 4.3 软硬连接对比
- 4.4 软硬连接的用途:
- 五. 文件系统总结
- 后言
前言
哈喽,各位小伙伴大家好!上期我们讲了磁盘的物理和逻辑结构 今天我们讲的是ext2文件系统的深度剖析。话不多说,我们进入正题!向大厂冲锋!
一.ext2文件系统
1.1宏观认识
所有的准备工作都已经做完,是时候认识下文件系统了。我们想要在硬盘上储文件,必须先把硬盘格
式化为某种格式的文件系统,才能存储文件。文件系统的目的就是组织和管理硬盘中的文件。在
Linux系统中,最常见的是ext2系列的文件系统。其早期版本为ext2,后来又发展出ext3和ext4。
ext3
和ext4
虽然对ext2
进行了增强,但是其核心设计并没有发生变化,我们仍是以较老的ext2作为 演示对象。
ext2文件系统将整个分区划分成若干个同样大小的块组(BlockGroup),如下图所示。只要能管理一个分区就能管理所有分区,也就能管理所有磁盘文件。
二. Block Group
上图中启动块(Boot Block/Sector
)的大小是确定的,为1KB,由PC标准规定,用来存储磁盘分区信息和启动代码,任何文件系统都不能修改启动块。启动块之后才是ext2文件系统的开始。
ext2文件系统会根据分区的大小划分为数个Block Group
。而每个Block Group
都有着相同的结构组成。政府管理各区的例子:
三. 块组内部构成
3.1 超级块(Super Block)
存放文件系统本身的结构信息,描述整个分区的文件系统信息。记录的信息主要有:block
和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 compatible feature set and* 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 number */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 EXT4_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;/* Default mount options */__le32 s_first_meta_bg;/* First metablock block group */__le32 s_reserved[190]; /* Padding to the end of the block */
};
3.2 GDT(Group Descriptor Table)
块组描述符表,描述块组属性信息,整个分区分成多少个块组就对应有多少个块组描述符。每个块组描述符存储一个块组的描述信息,如在这个块组中从哪里开始是
inode Table
,从哪里开始是Data Blocks,空间的inode和数据块还有多少个等等。块组描述符在每个块组的开头都有一份拷贝。
// 磁盘级blockgroup的数据结构
struct ext2_group_desc
{__le32 bg_block_bitmap; /* Blocks bitmap block */__le32 bg_inode_bitmap; /* Inodes bitmap block */__le16 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;__le16 bg_reserved[3];
};
3.3 块位图(Block Bitmap)
Block Bitmap
中记录着Data Block
中哪个数据块已经被占用,哪个数据块没有被占用。
3.4 inode位图(Inode Bitmap)
- 每个bit表示一个
inode
是否空闲可用。
3.5 节点表(Inode Table)
- 存放文件属性如文件大小,所有者,最近修改时间等
- 当前分组所有inode属性的集合
inode编号以分区为单位,整体划分,不可跨分区
3.6 Data Block
数据区:存放文件内容,也就是一个一个的Block。根据不同的文件类型有以下几种情况:
- 对于普通文件,文件的数据存储在数据块中。
- 对于目录,该目录下的所有文件名和目录名存储在所在的数据块中,除了文件名外,ls -l命令看到的其它信息是保存在该文件的inode中。
- Block 号按照分区划分,不可跨分区
3.7 inode和datablock映射(弱化)
- inode内部存在
__le32 i_block[EXT2_N_BLOCKS]; /* Pointers to blocks */,EXT2_N_BLOCKS =15,
就是用来进行inode和block映射的。 - 这样文件=内容+属性,就都能找到了。
🔥 思考:
请解释:知道inode号的情况下,在指定分区,请解释:对文件进行增、删、查、改是在做什么?
💡 结论:
- 分区之后的格式化操作,就是对分区进行分组,在每个分组中写入SB、GDT、Block Bitmap、Inode
Bitmap等管理信息,这些管理信息统称:文件系统 - 只要知道文件的inode号,就能在指定分区中确定是哪个分组,进而在哪个分组确定是哪个inode 拿到inode文件属性和内容就全部都有了
下面,通过touch一个新文件来看看如何工作。
[root@localhost linux]# touch abc
[root@localhost linux]# ls -i abc
263466 abc
为了说明问题,我们将上图简化:
创建一个新文件主要有以下4个操作:
-
存储属性
内核先找到一个空闲的节点(这里是263466)。内核把文件信息记录到其中。 -
存储数据
该文件需要存储在三个磁盘块,内核找到了三个空闲块:300,500,800。将内核缓冲区的第一块数据复制到300,下一块复制到500,以此类推。 -
记录分配情况
文件内容按顺序300,500,800存放。内核在inode上的磁盘分布区记录了上述块列表。 -
添加文件名到目录
新的文件名abc。Linux如何在当前的目录中记录这个文件?内核将入口(263466,
abc)添加到目录文件。文件名和inode之间的对应关系将文件名和文件的内容及属性连接起来。
3.8 目录与文件名
问题:
- 我们访问文件,都是用的文件名,没用过inode号啊?
- 目录是文件吗?如何理解?
答案:
- 目录也是文件,但是磁盘上没有目录的概念,只有文件属性+文件内容的概念。
- 目录的属性不用多说,内容保存的是:文件名和inode号的映射关系
// 验证说明代码,课堂不写了,直接复制粘贴即可
// readir.c
#include <stdio.h>
#include <string.h>
#include <stdlib.h>
#include <dirent.h>
#include <sys/types.h>
#include <unistd.h>int main(int argc, char *argv[]) {if (argc != 2) {fprintf(stderr, "Usage: %s <directory>\n", argv[0]);exit(EXIT_FAILURE);}DIR *dir = opendir(argv[1]); // 系统调用,自行查阅if (!dir) {perror("opendir");exit(EXIT_FAILURE);}struct dirent *entry;while ((entry = readdir(dir)) != NULL) { // 系统调用,自行查阅// Skip the "." and ".." directory entriesif (strcmp(entry->d_name, ".") == 0 || strcmp(entry->d_name, "..") == 0) {continue;}printf("Filename: %s, Inode: %lu\n", entry->d_name, (unsigned long)entry->d_ino);}closedir(dir);return 0;
}
3.9 路径解析
问题:打开当前工作目录文件,查看当前工作目录文件的内容?当前工作目录不也是文件吗?我们访问当前工作目录不也是只知道当前工作目录的文件名吗?要访问它,不也得知道当前工作目录的inode吗?
- 答案1:所以也要打开:当前工作目录的上级目录,额……,上级目录不也是目录吗??不还是上面的问题吗?
- 答案2:所以类似“递归”,需要把路径中所有的目录全部解析,出口是“/”根目录。
- 最终答案3:而实际上,任何文件,都有路径,访问目标文件,比如:
/home/whb/code/test/test.c
都要从根目录开始,依次打开每一个目录,根据目录名,依次访问每个目录下指定的目录,直到访问到test.c。这个过程叫做Linux路径解析。
💡 注意: 所以,我们知道了:访问文件必须要有目录+文件名=路径的原因
根目录固定文件名,inode号,无需查找,系统开机之后就必须知道
可是路径是谁提供?
- 你访问文件,都是指令/工具访问,本质是进程访问,进程有CWD!进程提供路径。
- 你open文件,提供了路径
可是最开始的路径从哪里来?
- 所以Linux为什么要有根目录,根目录下为什么要有那么多缺省目录?
- 你为什么要在家目录,你自己可以新建目录?
- 上面所有行为:本质就是在磁盘文件系统中,新建目录文件。而你新建的任何文件,都在你或者系统指定的目录下新建,这不就是天然就有路径了嘛!
- 系统+用户共同构建Linux路径结构。
3.10 路径缓存
问题1:Linux磁盘中,存在真正的目录吗?
答案:不存在,只有文件。只保存文件属性+文件内容
问题2:访问任何文件,都要从/目录开始进行路径解析
答案:原则上是,但是这样太慢,所以Linux会缓存历史路径结构
问题3:Linux目录的概念,怎么产生的?
答案:打开的文件是目录的话,由OS自己在内存中进行路径维护
- Linux中,在内核中维护树状路径结构的内核结构体叫做:
struct dentry
struct dentry {atomic_t d_count;unsigned int d_flags; /* protected by d_lock */spinlock_t d_lock; /* per dentry lock */struct inode *d_inode; /* Where the name belongs to - NULL is negative *//* The next three fields are touched by __d_lookup. Place here so they all fit in a cache line. */struct hlist_node d_hash; /* lookup hash list */struct dentry *d_parent; /* parent directory */struct qstr d_name;struct list_head d_lru; /* LRU list *//* d_child and d_rcu can share memory */union {struct list_head d_child; /* child of parent list */struct rcu_head d_rcu;} d_u;struct list_head d_subdirs; /* our children */unsigned long d_time; /* used by d_revalidate */struct dentry_operations *d_op;struct super_block *d_sb; /* The root of the dentry tree */void *d_fsdata; /* fs-specific data */
#ifdef CONFIG_PROFILINGstruct cookie_struct *d_cookie; /* cookie, if any */
#endifint d_mounted;unsigned char d_iname[DNAME_INLINE_LEN_MIN]; /* small names */
};
💡 注意:
- 每个文件其实都要有对应的dentry结构,包括普通文件。这样所有被打开的文件,就可以在内存中形成整个树形结构
- 整个树形节点也同时会隶属于LRU(Least Recently Used, 最近最少使用)结构中,进行节点淘汰
整个树形节点也同时会隶属于Hash,方便快速查找 - 更重要的是,这个树形结构,整体构成了Linux的路径缓存结构,打开访问任何文件,都在先在这棵树下根据路径进行查找,找到就返回属性inode和内容,没找到就从磁盘加载路径,添加dentry结构,缓存新路径
3.11 挂载分区
我们已经能够根据inode号在指定分区找文件了,也已经能根据目录文件内容,找指定的inode了,在指定的分区内,我们可以为所欲为了。可是:
- inode不是不能跨分区吗?Linux不可以有多个分区吗?我怎么知道我找的文件在哪一个分区?
3.11.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 95M 0 95M 0% /dev
$ tmpfs 198M 72K 197M 1% /run
$ /dev/vda1 5G 2G 2G 42% /
$ tmpfs 96M 0 96M 0% /dev/shm
$ tmpfs 5.0M 4.0K 5.0M 1% /run/lock
$ tmpfs 96M 0 96M 0% /sys/fs/cgroup
$ tmpfs 198M 0 198M 0% /run/user/0
$ sudo mount -t ext4 ./disk.img /mnt/mydisk/ # 将分区挂载到指定的目录
$ df -h
$ Filesystem Size Used Avail Use% Mounted on
$ udev 95M 0 95M 0% /dev
$ tmpfs 198M 72K 197M 1% /run
$ /dev/vda1 5G 2G 2G 42% /
$ tmpfs 96M 0 96M 0% /dev/shm
$ tmpfs 5.0M 4.0K 5.0M 1% /run/lock
$ tmpfs 96M 0 96M 0% /sys/fs/cgroup
$ tmpfs 198M 0 198M 0% /run/user/0
$ /dev/loop0 4.9M 2.4K 4.5M 1% /mnt/mydisk
$ sudo umount /mnt/mydisk # 卸载分区
3.8.2 一个结论
- 分区写入文件系统,无法直接使用,需要和指定的目录关联,进行挂载才能使用。
- 所以,可以根据访问目标文件的“路径前缀”准确判断我在哪一个分区。
四 软硬连接
4.1 硬链接
我们看到,真正找到磁盘上文件的并不是文件名,而是inode。其实在Linux中可以让多个文件名对应于同一个inode。
[root@localhost linux]# touch abc
[root@localhost linux]# ln abc def
[root@localhost linux]# ls -li abc def
263466 abc
263466 def
- abc和def的链接状态完全相同,他们被称为指向文件的硬链接。内核记录了这个连接数,inode 263466的硬链接数为2。
- 我们在删除文件时干了两件事情:1.在目录中将对应的记录删除,2.将硬连接数-1,如果为0,则将对应的磁盘释放。
4.2 软链接
硬链接是通过inode引用另外一个文件,软链接是通过名字引用另外一个文件,但实际上,新的文件和被引用的文件的inode不同,应用常见上可以想象成一个快捷方式。在shell中的做法
[root@localhost linux]# ln -s abc.s abc
[root@localhost linux]# ls -li
263563 -rw-r--r-- 2 root root 0 9月 15 17:45 abc
263488 -rw-r--r-- 2 root root 3 9月 15 17:53 abc.s -> abc
263563 -rw-r--r-- 2 root root 0 9月 15 17:45 def
261678 abc.s
acm
下面解释一下文件的三个时间:
- Access 最后访问时间
- Modify 文件内容最后修改时间
- Change 属性最后修改时间
4.3 软硬连接对比
- 软连接是独立文件
- 硬链接只是文件名和目标文件inode的映射关系
4.4 软硬连接的用途:
硬链接
- . 和 … 就是硬链接
- 文件备份
软连接
- 类似快捷方式
五. 文件系统总结
所以的文件是fs_struct结构体找到pwd和根目录进行然后
他会通过path结构体里面vfmount挂载分区和dentry树的进行路径路径解析的进而找到文件的内容和属性!
后言
这就是ext2文件系统的深度剖析。大家自己好好消化!今天就分享到这! 感谢各位的耐心垂阅!咱们下期见!拜拜~