
一、文件系统的引入1.引入块的概念其实硬盘是典型的“块”设备操作系统读取硬盘数据的时候其实不会一个个扇区地读取这样效率太低而是一次性读取连续多个扇区即一次性读取一个“块”BLock硬盘的每个分区是被划分成一个个“块”。一个“块”的大小是由格式化的时候确定的并且不可以更改最常见是4Kb即连续8个扇区组成一个“块”。“块”是文件存取的最小单位。注意磁盘就是一个三维数组我们把它看待为一个“一维数组”数组下标就是LBA每个元素都是扇区每个扇区都有LBA那么8个扇区一个块每个块的地址我们也能算出来。知道LBA块号LBA/8知道块号LBA块号*8nn为第几个扇区2.“分区”其实磁盘可以变成多个分区partition的以Windows来看一块磁盘被分为c盘d盘这个cd就是分区。分区实质是对硬盘的一种格式化。但是Linux的设备都是以文件形式存在那是怎么区分呢柱面是分区的最小单位我们可以利用参考柱面号码的方式进行分区其本质就是设置每个区的起始柱面和结束柱面号码。此时我们可以将硬盘上的柱面分区进程平铺想象成大的平面如图3.引入“inode”index node概念之前我们说文件属性内容我们使用ls -l 的时候看到的除了文件名还能看到文件元数据属性每行包含 模式 硬链接数 文件所有者 所属组 大小 最后修改时间 文件名ls -l读取存储在磁盘上的文件信息然后显示出来。其实这个信息除了通过这种方式来读取还有一个stat命令能够看到更多信息[xhx_homeiZ2zeiwy4tf4rghoyh5d1yZ file_blog]$ stat log.txt File: log.txt Size: 5 Blocks: 8 IO Block: 4096 regular file Device: fd01h/64769d Inode: 868065 Links: 1 Access: (0644/-rw-r--r--) Uid: ( 1001/xhx_home) Gid: ( 1001/xhx_home) Access: 2026-04-23 21:12:19.586261635 0800 Modify: 2026-04-23 21:12:16.924181829 0800 Change: 2026-04-23 21:12:16.924181829 0800 Birth: -到这我们要思考一个问题文件数据都存储在“块”中那么我们很显然我们还必须找到一个地方存储文件的源信息属性信息比如文件的创建者文件的创建日期文件的大小等等。这种储存文件元信息的区域叫inode中文译名“索引结点”。每一个文件都有对应的inode里面包含一些与该文件有关的一些信息。下面我们深入了解一下文件系统。注意Linux下文件的存储是属性和内容分离存储的。Linux下保存文件属性的集合叫inode一个文件一个inodeinode内有一个唯一的标识符叫做inode号所以一个文件的属性inode长什么样子呢// 简易版 ext2 inode 结构体 struct inode { // 文件元数据 uid_t i_uid; // 所属用户 gid_t i_gid; // 所属组 mode_t i_mode; // 权限文件类型 off_t i_size; // 文件大小 time_t i_atime; // 访问时间 time_t i_mtime; // 修改时间 time_t i_ctime; // 元数据修改时间 // 硬链接计数 nlink_t i_nlink; // 核心数据块指针重点 // 共15个块指针存【全局逻辑块号】 uint32_t i_block[15]; /* * i_block[0] ~ i_block[11] 12个直接块 * i_block[12] 一级间接块 * i_block[13] 二级间接块 * i_block[14] 三级间接块 */ // 磁盘占用块数量 uint32_t i_blocks; };注意文件名属性并未纳入到inode数据结构内部任何文件的内容大小可以不同但属性大小一定是相同的。二、ext2文件系统1.宏观认识所有的准备工作都已经做完是时候认识下文件系统了。我们想要在硬盘上存储文件必须先把硬盘格式化为某种格式的文件系统才能存储文件。文件系统的目的就是组织和管理硬盘中的文件。ext2文件系统是ext3 ext4等的基础设计核心保持一致我们以ext2来演示ext2文件系统将整个分区划分成若干个同样大小的块组BLock Group如下图所示只要能管理一个分区就可以管理所有分区也就能管理所有的磁盘文件。2.Disk1MBRMaster Boot Record 主引导记录位于磁盘最开头的512字节扇区不属于任何分区包含分区表和引导代码是BIOSbasic input output system启动时最先读取的位置负责找到并加载活动分区的引导扇区。2Partition 分区将磁盘划分为多个独立的逻辑区域每个分区可以独立格式化安装文件系统互不干扰。3.Partition1Boot Sector引导扇区每个分区最开头的扇区存放该分区的引导代码负责加载分区上的操作系统内核是文件系统之前的启动块。2EXT2 File System文件系统的具体实现4.File System1BLock Group 块组文件系统为了提升性能将整个分区再划分为多个大小相同的块组每个块组都有独立的管理结构减少碎片提升局部性。5.BLock Group1Super BLock超级块存放文件系统本身的结构信息描述整个分区系统信息。记录的信息主要有BLock和inode总量未使用的block和inode一个block和inode大小最近一次挂载时间最近一次写入数据时间最近一次检验磁盘的时间等其他文件系统的相关信息。Super Block的信息被破坏可以说整个文件系统结构就被破坏了。超级块在每个块组的开头都有一份拷贝第一个块组必须有其他可以没有。是为了保证文件系统在磁盘部分扇区出现物理问题还能正常工作就必须保证文件系统的Super Block信息在这种情况下也能访问所以一个文件系统的Super Block会在多个块组中去备份这些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 doesnt * know about, it should refuse to mount the filesystem. * * e2fscks requirements are more strict; if it doesnt know * about a feature in either the compatible or incompatible * feature set, it must abort and not try to meddle with * things it doesnt 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 */ };2GDT Group Descriptor Table 块组描述符表块组描述符表描述块组属性信息整个分区分成多少个块组就对应有多少个描述符。每个块组描述符存储一个块组的描述信息如在这个块组中从哪里开始是inode Table哪里开始是Data block空闲的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]; };3Block Bitmap块位图Block Bitmap中记录着Data Block 中哪个数据块已经被占用哪个数据块没有被占用。4inode Bitmapinode位图每个bit位表示一个inode是否空闲可用5inode Tablei结点表存放文件属性 如 文件大小 所有者 最近修改时间等等当前分组所有inode属性的集合一个inode管理一个文件inode编号以分区为单位整体划分不可垮分区所以一个文件不可以垮分区存储。6Data Block存放文件内容也就是一个一个的Block。是Block Group里最主要部分对与普通文件文件的数据存储在数据块中。对与目录该目录下的所有文件名和目录名存储在所在目录的数据块中除了文件名外ls -l命令可以看到其他信息保存在该文件的inode中。Block 号按分区划分不可夸分区。6.inode和Data Block 映射inode内部存在__le32 i_block[EXT2_N_BLOCKS];/* Pointers to blocks */ , EXT2_N_BLOCKS 15就是用来进行inode和Block映射的。这样文件内容属性就都能找到了。// 简化EXT2 迷你 inode 结构体 #define BLOCK_SIZE 4096 #define DIRECT_PTRS 12 // 12个直接块 #define INDIRECT1 12 // 一级间接 #define INDIRECT2 13 // 二级间接 #define INDIRECT3 14 // 三级间接 // 全局块号inode 里存的都是【分区全局唯一Block号】 typedef unsigned int block_t; typedef unsigned int inode_no_t; struct inode { // 1. 文件元数据 unsigned int i_mode; // 权限文件类型 unsigned int i_size; // 文件大小 unsigned int i_nlink; // 硬链接计数 unsigned int i_uid; unsigned int i_gid; // 2. 核心数据块指针数组 // 全部存【全局逻辑块号】(跨块组依靠这个) block_t i_block[15]; // 3. 磁盘占用块数量 unsigned int i_blocks; // 时间域省略... };7.格式化格式化 清空分区 给分区装修成标准文件系统结构裸分区只是一块空白磁盘空间操作系统没法存文件格式化就是在分区里批量建好 块组、超级块、位图、inode 表、数据块。分区之后的格式化操作就是对分区进行分组在每个分组中写入Super BlockGDTBlock Bitmap等等的管理信息这些管理信息统称为文件系统。只有知道文件的inode号就可以在指定分区中确定是哪一个分组进而在分组内找见是哪一个inode拿到inode文件属性和内容就都有了8.样例下面我们touch一个新文件来解释一下是如何工作[xhx_homeiZ2zeiwy4tf4rghoyh5d1yZ file_blog]$ touch abc [xhx_homeiZ2zeiwy4tf4rghoyh5d1yZ file_blog]$ ls -i abc 868055 abc创建一个新文件主要有这些操作:存储属性内核先找到一个空闲的inode节点内核把文件信息记录其中。存储数据该文件需要存储在三个磁盘块内核找到了三个空闲块300 500 800.将内核缓冲区的第一块数据复制到300第二块复制到500以此类推对应块的Block Bitmap值由0变1记录分配情况文件内容按300,500,800存放内核在inode上的磁盘分布区inode Table记录了上述块列表。添加文件名到目录内核将入口868055 abc添加到其目录文件。文件名和inode之间的对应关系将文件名和文件内容及属性连接起来。文件名和inode的映射在我们访问文件的时候使用文件名实际操作系统将文件名和inode号进行映射所以我们使用文件名访问文件其实就是使用inode号来访问。DIR* dir opendir(argv[1]); struct dirent* entry; while((entryreaddir(dir))!NULL){ printf(filename:%s inode:%lu\n,entry-d_name,(unsigned long)entry-d_ino); } closedir(dir); return 0; //dirent 记录磁盘上目录文件的文件名inode号 //DIR 遍历目录 和FILE类似所以访问文件必须打开当前目录根据文件名获得对应inode号然后进行文件访问所以访问文件必须要知道当前工作目录本质是必须能打开当前工作目录文件查看目录文件的内容9.路径解析当前工作目录的文件打开也需要上一级目录inode所以我们要把路径的所有目录全部解析出来出口是‘/’根目录。任何文件都有路径访问目标文件比如/home/a/b/c都要从根目录开始依次打开每一个目录根据目录名依次访问每个目录下指定的目录直到访问到c这个过程就叫做Linux的路径解析。跟目录的inode无须查找开机之后就必须知道路径访问文件都是指令/工具进行访问本质是进程访问进程有CWD进程提供路径。open文件提供了路径磁盘文件系统中新建目录文件都在系统指定的目录新建这不就有了天然路径了吗系统用户共同构造Linux路径结构10.路径缓存问题1Linux磁盘中存在真正的⽬录吗答案不存在只有⽂件。只保存⽂件属性⽂件内容问题2访问任何⽂件都要从/⽬录开始进⾏路径解析答案原则上是但是这样太慢所以Linux会缓存历史路径结构问题2Linux⽬录的概念怎么产⽣的答案打开的⽂件是⽬录的话由OS⾃⼰在内存中进⾏路径维护Linux中在内核中维护树状路径结构的内核结构体叫做struct dentrystruct 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 them 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 */ struct list_head d_alias; /* inode alias list */ 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_PROFILING struct dcookie_struct *d_cookie; /* cookie, if any */ #endif int d_mounted; unsigned char d_iname[DNAME_INLINE_LEN_MIN]; /* small names */ };每个文件其实都有对应的dentry结构包括普通文件。这样所有被打开的文件就可以在内存中形成整个树形结构。内核读取目录后在内存创建dentry内存inode整个树形节点也同时会隶属于LRUleast recently used最近最少使用结构中进行结点淘汰整个树形结点也同时属于Hash方便快速查找。更重要的是这个树型结构整体构成了Linux的路径缓存结构打开访问任何文件都先在这棵树下根据路径查找找到就返回inode属性和内容没找到就从磁盘加载新路径添加dentry结构缓冲新路径。11.挂载分区我们已经能够根据inode号在指定分区找文件了也已经能根据目录文件内容找到指定的inode了在我们指定的分区我们可以为所欲为。可是inode不是不能夸分区吗Linux不是可以有多个分区吗我怎么知道我在哪一个分区分区写入文件系统无法直接使用需要和指定的目录关联进行挂载才能使用所以可以根据访问目标文件的“路径前缀”准确判断我在哪一个分区。三、文件系统总结