当前位置: 首页 > article >正文

Linux 基础IO (五)深入理解文件系统

目录一、文件系统引入“块”概念引入“分区”概念引入“inode”概念引入文件系统分区(Partition)ext2文件系统块组(Block Groups)Data Blocks(数据块)Block Bitmap(块位图Inode Table(inode 表Inode Bitmap(inode 位图)GDT(Group Descriptor Table块组描述符表超级块(Super Block二、目录1. 目录是文件吗?2. 目录文件的内容长什么样3. 目录的权限4.那目录权限是怎么生效的5.目录权限和文件权限三、路径解析四、路径缓存struct dentry挂载分区九、文件系统总结五、问题六、总结上篇文章我们学习了磁盘的相关内容本文我们将深入学习文件系统如果说磁盘Disk是 “装文件的容器”它的作用是提供物理存储空间的话。那么文件系统File System 就是用来“管理文件的规则”它的作用就是告诉系统怎么存、怎么找、怎么用这些装文件的地方。本质上就是一套软件规则决定如何管理目录、权限、路径。一、文件系统引入“块”概念磁盘就是一块物理硬盘其实硬盘是典型的“块”设备操作系统读取硬盘数据的时候其实是不会一个个扇区地读取这样效率太低而是一次性连续读取多个扇区即一次性读取一个“块”(block)。硬盘的每个分区是被划分为一个个的“块”。一个“块”的大小是由格式化的时候确定的并且不可以更改最常见的是4KB即连续八个扇区(512字节*8)组成一个“块”。“块”是文件存取的最小单位。即 一个块 4KB 八个扇区 512字节*8这个图是Linux系统中执行 fdisk -l 命令后的磁盘信息输出展示了 /dev/vda 磁盘的详细参数磁盘 /dev/vda 的容量为42.9GB对应42949672960字节、83886080个扇区扇区单位每个扇区大小为512字节逻辑/物理扇区尺寸、I/O最小/最优尺寸均为512字节注意磁盘就是一个三维数组我们把它看待成为一个“一维数组”数组下标就是LBA每个元素都是扇区每个扇区都有LBA那么8个扇区一个块每一个块的地址我们也能算出来。知道LBA块号 LBA/8知道块号LAB 块号*8 nn是块内第几个扇区引入“分区”概念其实磁盘是可以被分成多个分区partition的以Windows观点来看你可能会有一块磁盘并且将它分区成C,D,E盘。那个C,D,E就是分区。分区从实质上说就是对硬盘的一种格式化。但是Linux的设备都是以文件形式存在那是怎么分区的呢柱面是分区的最小单位我们可以利用参考柱面号码的方式来进行分区其本质就是设置每个区的起始柱面和结束柱面号码。此时我们可以将硬盘上的柱面分区进行平铺将其想象成一个大的平面如下图所示引入“inode”概念之前我们说过 文件数据属性我们使用 ls -l 的时候看到的除了看到文件名还能看到文件元数据属性。每行包含7列权限模式 硬链接数 文件所有者 组 大小 最后修改时间 文件名ls -l 读取存储在磁盘上的文件信息然后显示出来其实这个信息除了通过这种方式来读取还有一个stat命令能够看到更多信息到这我们要思考一个问题文件数据内容都储存在“块”中那么很显然我们还必须找到一个地方储存文件的元信息属性信息比如文件的创建者、文件的创建日期、文件的大小等等。这种储存文件元信息的区域就叫做inode中文译名为“索引节点”。每一个文件都有对应的inode里面包含了与该文件有关的一些属性信息。为了能解释清楚inode我们需要深入了解一下文件系统。所以文件属性的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文件系统中inode的代码结构体定义它是管理文件的核心结构既包含文件权限、大小、时间戳等元属性又通过长度为15的 i_block 指针数组关联文件数据块前12个直接指向数据块后3个为间接指针支持大文件同时包含不同系统的扩展字段以保证兼容性是ext2中“文件属性内容”的实现载体。再次注意文件名属性并未纳入到inode数据结构内部inode的大小一般是128字节或者256字节这里我们统一认为是128字节任何文件的内容大小可以不同但是属性大小一定是相同的到目前为止相信大家还有两个问题我们已经知道硬盘是典型的“块”设备操作系统读取硬盘数据的时候读取的基本单位是“块”。“块”又是硬盘的每个分区下的结构难道“块”是随意的在分区上排布的吗那要怎么找到“块”呢还有就是上面提到的存储文件属性的inode又是如何放置的呢而文件系统就是为了组织管理这些的引入文件系统我们首先先来看下面一幅图:Disk是磁盘 , 上面介绍过了磁盘是真实的物理设备。比如说硬盘、SSD、移动硬盘、U盘这类。“一个磁盘” 一块真实的物理硬盘。分区(Partition)“一个磁盘又被分为多个分区”就是把这块硬盘切成若干块每块叫一个分区(Partition)。每个分区内部又被分成引导扇区(Boot Sector)和文件系统(File System)。引导扇区(Boot Sector)包含启动代码并且每个分区唯一 , 但是不是所有分区都有引导系统 ,文件系统(File System)则是分区内部最核心的部分。假设一个磁盘的存储空间是800GB要一下就将800GB直接管理起来很困难但是我们可以进行间接管理就像中国一样虽然中国很大但是我们可以将中国划分为诸多省份进行管理省份再进行划分为诸多市进行管理等同理磁盘的管理也是这样的道理我们可以借鉴中国区域划分的管理方式将磁盘的800GB空间进行划分为不同的区只要我们将区管理好了那么这800GB的磁盘空间也就管理好了如上我们已经划分为不同的分区了实际上是使用的一个partion结构体进行划分分区仅仅是在逻辑上进行划分即使划分的区域跨盘面也无所谓因为物理空间上盘面并没有改变虽然将800GB进行分区管理了第一个分区被划分了200GB对我们来说200GB仍然很大如何做呢很简单再划分不就是了我们可以将200GB再进行分组划分为20组10GB的空间进行管理此时我只需要将一个10GB的空间管理好那么就可以采用同样的方法去管理剩下的19组10GB的空间当我将这20组10GB的空间都管理好的时候那么200GB的空间就管理好了那么我再使用管理200GB这个分区的方法和手段去管理剩下分区这样800GB对应的所有的分区就被我管理好了此时800GB磁盘就被我管理好了此时问题就又来了如何管理好分组的10GB空间呢那么此时就需要使用到 linux ext2 文件系统进行管理了ext2文件系统每个分区里都有不同的文件系统 , 比如分区A 可以用ext4,分区B可以用xfs,分区C可以用fat32,分区D可以用ntfs,分区只是“切割”出来的一块空间不强制要求内部用什么格式。而现在大多数分区使用的都是ext系列的文件系统,比如说最常见的ext4文件系统 , 此外还有ext2文件系统和ext3文件系统 ext2是最早的 Linux 文件系统。ext3 是在ext2基础上加“日志”。ext4 是 ext3 的超级升级版是现在的主流。但是这里今天我们讲的是ext2文件系统,ext3文件系统和ext4文件系统虽然对ext2文件系统进行了增强但是其核心设计并没有发生变化我们仍是以较老的ext2文件系统作为演示对象。块组(Block Groups)以ext2为例 , ext2内部被分成多个“块组”Block Groups)。每个块组都包含超级块Super Block块组描述符GDBlock Bitmap块位图Inode Bitmapinode 位图Inode Tableinode 表Data Blocks数据块接下来我们将一一介绍:Data Blocks(数据块)我们都知道文件是由属性和内容两部分组成的我们先看文件的内容部分:数据块(Data Blocks)就是真正存放文件内容的地方。就是磁盘上被分配给“文件数据”的那一块块 4KB 大小的空间。每个块固定大小是4KB 4096字节文件内容被切分成一块块的Data Blocks文件内容就储存在这些连续或离散的多个Data Blocks中比如一个文件内容大小是10KB则需要 3个数据块 (4KB × 3 12KB)。那Data Blocks怎么被管理的呢我们在下面会介绍Block Bitmap(块位图Block Bitmap 是一个位图表示一个“块的空闲状态表”。每个比特位对应一个Data Block数据块。0表示对应的数据块是空闲的1表示已占用。从而快速判断哪个数据块能用。这个位图也是由多个块位图块组成每个块位图块的大小也是4KB。每一个位图块是 4KB 4096 字节 32768 个 bit。所以一个块位图块可以表示32768个数据块是否空闲。当我们要写文件时首先查 Bitmap 位图并找 bit0 的空闲数据块然后把这个比特位设成1最后把数据写进对应的 Data Block。举个例子 : 比如有一个大小为4MB的文件应该怎么通过Block Bitmap(块位图)和Data Blocks(数据块)进行存储?首先一个 4MB 的文件默认一个数据块大小为 4KB则需要 4MB / 4KB 1024 个数据块。1024 个数据块对应 Block Bitmap 位图中的 1024 个 bit 位也就是1024 bit / 8 128 字节。同时我们知道一个块位图块是4KB所以这4MB的文件只占了一个块位图块(4KB)里的极一小部分。还有个问题 : 位图中的一个个比特位是怎么知道当前对应的数据块是有无内容呢?Block Bitmap 里的每一个比特位不是凭空知道对应的数据块有是没有内容的。它是由操作系统(文件系统驱动) 在每一次磁盘操作时主动维护的。当我们在写文件时发生了这三件事1.系统要存文件内容需要找空闲的数据块。系统扫描 Block Bitmap。找到第一个值为 0 的 bit。这代表对应的 Data Block 是空的。2.标记占用更新位图: 系统选中了这个空闲块比如 Block100。它会把 Block Bitmap 中第 100 位的值从 0 改成 1。这一刻位图才“知道”这个块有内容了。3.写入数据 : 系统把要写的内容写入到 Data Block100。最后完成Inode Table(inode 表Inode Table 其实是一个巨大的数组里面存放的是结构体 inode。那这个inode结构体又是什么呢? 如果说文件的内容是存放在数据块Data Blocks中那么文件的属性就存放在这个inode结构体中这个Inode Table表则是管理每个inode结构体的数组。下面我们再来看一下这个结构体inode:inode 本质上就是一个“结构体”。每个文件对应一个 inode结构体。inode里主要存放的是“文件的属性” 还有“指向数据块的指针”。还有这个结构体的大小是128字节。在 Linux 内核里它是这样的结构体简化版struct inode { umode_t i_mode; // 文件类型 权限 uid_t i_uid; // 所有者 ID gid_t i_gid; // 所属组 ID loff_t i_size; // 文件大小 struct timespec i_atime; // 最后访问时间 struct timespec i_mtime; // 最后修改时间 struct timespec i_ctime; // 最后状态变更时间 unsigned int i_nlink; // 硬链接数 // 关键指向数据块的指针数组 uint64_t i_block[15]; };里面存储的这些文件属性我们也非常熟悉为什么这么说呢?我们用 ls -lai 命令查看文件的信息对应的就是这个文件 inode结构体里的大部分“文件属性”。比如权限、大小、归属、时间、链接数等等。每个文件都有唯一的inode编号(inode Number)这个编号就是它在数组中的位置但这个inode编号并不在inode结构体中它存在于文件的目录项(Dentry)中这个目录项有文件的文件名和文件编号等等后续我们通过目录项查找文件的inode编号然后在Inode Bitmap中查找该编号对应的比特位是0还是1。如果是 0说明这个 inode 编号目前是“空闲”状态。系统会立刻报错(如No such file or directory)因为目录项指向了一个不存在的文件。如果是1说明这个 inode 正在被占用是有效的。然后再在Inode Table表中通过inode编号作为数组下表索引到文件对应的inode结构体里面有该文件的文件属性还有这个 i_block[15] 指针数组根据数组里存的数据块编号去磁盘的 Data Blocks 区域读取对应的数据块。从而读取到文件的内容需要注意的是文件名不属于文件属性(文件名≠文件的inode编号)那inode结构体里的这个 i_block[15] 指针数组又是什么? 如果说这个数组是指向数据块的数组指针那为什么是15个呢? 那数据块最多只能存储4*1560KB大小的文件内容吗?答案肯定不是这样的15 个数组指向 15 个数据块每个 4KB一共 60KB文件不可能这么小。这 15 个不是“直接存数据块”而是由15个不同级别的指针组成的inode 里的 15 个指针中前 12 个是直接块指针直接存放数据块号指向存储文件内容的数据块第 13 个是一次间接块指针指向的索引块里只存数据块号第 14 个是二次间接块指针指向的索引块里存的是一级索引块的块号第 15 个是三次间接块指针再往上多嵌套一层索引块所有中间的间接块都只存块号层层索引只有最底层的数据块才真正存放文件内容这些 inode、指针、索引块和数据块默认都在磁盘上需要时才加载到内存。我们可以简单算一下:一个数据块大小是 4KB一个块号占 4 字节所以一个块能存 1024 个块号直接12 × 4KB 48KB一次间接1024 × 4KB 4MB二次间接1024×1024 ×4KB 4GB三次间接1024×1024×1024 ×4KB 4TB其实ext系列文件系统就是这么大再总结一下:只有真正存文件内容的叫数据块其他所有中间块全都是存“块号”的索引块Inode Bitmap(inode 位图)Inode Bitmap(inode 位图就是系统用来快速标记“哪些 inode 已被使用、哪些空闲”的一个位图。每个比特位用 0 和 1 标记每个 inode 是否空闲。Inode Bitmapinode 位图 和 Block Bitmap数据块位图)一样都是由多个 4KB 的块位图块组成。它们的结构和原理是完全对称的。Inode Bitmap 也是“存放在块里的位图”。它由一个或多个 4KB 的块组成。每个块里的每 1 bit 对应 1 个 inode。1 个 4KB 的 inode 位图块能管理 32768 个 inode。如果文件系统里的 inode 总数超过 32768那就会用多个块位图块来存整个 inode 位图。比如一个分区有 100,000 个 inode则需要的位图块数 100,000 ÷ 32,768 ≈ 4 个块。Inode Bitmap 的比特位位置和 Inode Table 的数组下标在逻辑上是完全一致的。Bitmap 中的第 N 位代表 Inode Table 中的第 N 个结构体是否被占用。值为 1表示该位置的 inode 已被分配有人用。值为 0表示该位置的 inode 空闲没人用。Inode Bitmap 中比特位的位置就是 Inode Table 数组的组内下标。它们指向的是同一个东西同一个 inode 结构体。GDT(Group Descriptor Table块组描述符表GDTGroup Descriptor Table块组描述符表描述的是块组的属性信息整个分区分成多个块组就对应有多少个块组描述符。每个块组描述符存储一个块组的描述信息如在这个块组中从哪里开始是inode 表从哪里开始是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];保留字段用于扩展 };超级块(Super Block超级块是文件系统的“总管理员”。它存的是整个分区的“全局信息”不存单个文件的信息。没有超级块文件系统就不知道磁盘怎么管理。存放文件系统本身的结构信息描述整个分区的文件系统信息。记录的信息主要有block 和 inode 的总量未使用的 block 和 inode 的数量一个 block 和 inode 的大小最近一次挂载的时间最近一次写入数据的时间最近一次检验磁盘的时间等其他文件系统的相关信息。Super Block 的信息若被破坏可以说整个文件系统的结构就被破坏了。超级块在每个块组的开头都有一份拷贝第一个块组必须有后面的块组可以没有。为了保证文件系统在磁盘部分扇区出现物理问题的情况下还能正常工作就必须保证文件系统的 super block 信息在这种情况下也能正常访问。所以一个文件系统的 super block 会在多个 block group 中进行备份这些 super block 区域的数据保持一致。下面代码展示了 ext2/ext3/ext4 超级块 struct ext2_super_block 的完整字段。/* * EXT2/EXT4 文件系统核心超级块注释 * 包含字段含义、关联操作、及扩展说明 */ __le32 s_free_blocks_count; /* 空闲块计数器记录当前可用 block 数量 */ __le32 s_free_inodes_count; /* 空闲索引节点计数器记录当前可用 inode 数量 */ __le32 s_first_data_block; /* 首个数据块挂载时首个数据块位置 */ __le32 s_log_block_size; /* 日志块大小记录 block 单元尺寸单位字节 */ __le32 s_log_frag_size; /* 日志片段大小记录 fragment 单元尺寸 */ __le32 s_blocks_per_group; /* 每块组块数每个块组包含的 block 总数 */ __le32 s_frags_per_group; /* 每块组片段数每个块组包含的 fragment 总数 */ __le32 s_inodes_per_group; /* 每块组索引节点数每个块组包含的 inode 总数 */ __le32 s_mtime; /* 挂载时间文件系统最后挂载的时间戳 */ __le32 s_wtime; /* 写入时间文件系统最后写入的时间戳 */ __le16 s_mnt_count; /* 挂载计数文件系统被挂载的总次数 */ __le16 s_max_mnt_count; /* 最大挂载计数允许挂载的最大次数阈值 */ __le32 s_feature_compat; /* 兼容特性集支持的兼容功能标识 */ __le32 s_feature_incompat; /* 不兼容特性集不支持但可兼容的功能标识 */ __le32 s_feature_ro_compat; /* 只读兼容特性集只读模式下支持的功能标识 */ __u8 s_uuid[16]; /* 文件系统 UUID全局唯一标识 */ char s_volume_name[16]; /* 卷名称文件系统别名 */ char s_last_mounted[64]; /* 最后挂载目录上次挂载的路径 */ __le32 s_algorithm_usage_bitmap; /* 压缩算法标识用于数据压缩的算法标记 */ /* * 预分配相关字段EXT4/EXT4 特性增强 */ __u8 s_prealloc_blocks; /* 预分配块数尝试预分配的 block 数量 */ __u8 s_prealloc_dir_blocks; /* 目录预分配块数目录文件预分配的 block 数量 */ __u16 s_padding1; /* 填充字段保持结构对齐 */ /* * 日志系统相关字段EXT3/EXT4 核心特性 */ __u8 s_journal_uuid[16]; /* 日志系统 UUID日志文件系统唯一标识 */ __le32 s_journal_inum; /* 日志索引节点号日志文件对应的 inode 编号 */ __le32 s_journal_dev; /* 日志设备号日志文件所在设备标识 */ __le32 s_last_orphan; /* 最后删除项用于日志回收的索引节点 */ __le32 s_hash_seed[4]; /* 哈希种子用于目录项哈希计算的密钥 */ __u8 s_def_hash_version; /* 默认哈希版本哈希算法版本标识 */二、目录1. 目录是文件吗?目录也是文件目录是特殊的文件。它不存储文件内容而是存储 文件名和inode编号 的映射表。它在磁盘上有对应的 inode 编号和数据块。2. 目录文件的内容长什么样目录的数据块里存的是一系列的目录项(dentry)。我们上面也提到过通过目录项查找文件名和inode编号来找文件。目录项的结构如下struct ext2_dir_entry { __le32 inode; // 对应文件的 inode 编号 __le16 rec_len; // 记录长度 __le16 name_len; // 文件名长度 char name[256]; // 文件名 };假设一个目录里有多个文件。那它的数据块内容就如上:3. 目录的权限目录也是文件文件的属性在对应的inode结构体中那目录的属性也就在目录本身对应的inode结构体中目录的权限属于目录的属性所以目录的权限是存在它自己的inode结构体里(i_mode)。而目录的内容也就是目录下面的若干个文件名列表是存在它自己的数据块里。目录的权限和普通文件一样存在目录 inode 的 i_mode 字段 里。struct inode { umode_t i_mode; // 文件类型 权限 ... }目录的 i_mode 会包含1.文件类型 2.权限rwx (所有者、所属组、其他drwxr-xr-x 2 cxj cxj 4096 Mar 10 17:47 test这里的 rwxr-xr-x 就是这个目录 inode 的 i_mode。4.那目录权限是怎么生效的目录权限不是管“读取文件内容”的而是管你能不能操作目录结构的。目录权限对应三种操作:权限对目录的真实含义对应行为r可以列出目录内容可以 lsw可以修改目录结构可以创建、删除、重命名文件x可以进入该目录可以 cd、访问路径目录的 r 权限允许你列出目录内容读取到目录的数据块里的目录项并且能看到文件名列表。目录的 w 权限允许你修改目录结构比如创建新文件删除文件重命名文件和移动文件。但是要注意拥有 w 权限不等于能修改文件内容这是目录权限和文件权限的区别。目录的 x 权限是必须拥有 x 权限才能进入该目录才能访问该目录下的任何文件才能通过路径访问子目录所以没有 x 权限 无法访问该目录的任何内容。5.目录权限和文件权限目录是文件目录的权限存在它的 inode 里。目录的权限不是控制文件内容而是控制你能不能“看到”和“修改”这个目录的结构。而文件权限则是控制文件内容的读写执行。三、路径解析路径解析就是从根目录出发一级一级进入每一个子目录在每个目录的数据块里查找下一级的名字最终找到目标文件对应的 inode 号的过程。我们举个例子比如有一串路径 /home/cxj/test/log.txt 路径解析就是系统把这串路径字符串一级一级拆开最终找到对应文件的 inode 的过程。那是怎么做的呢?首先第一步是找到根目录 inode当你使用绝对路径以 / 开头时系统的第一步永远是 找到根目录。系统内部知道根目录的 inode 编号固定是 2。系统会根据 inode 号 2直接去 Inode Table 里取出对应的 inode 结构体。这个 inode 结构体里的 i_block 指针指向的就是根目录的数据块。而这个数据块里存的内容就是根目录下所有文件和子目录的目录项列表(记录了名字和对应的inode 号。第二步是遍历每一级目录系统会把你输入的路径 /home/cxj/test/log.txt 按照 / 这个符号切开得到一个名字列表[home, cxj, test, log.txt] 。然后系统会拿着这个列表一级一级往下找先是查找 home系统拿着名字 home去当前的根目录数据块里遍历所有的目录项。对比每一个目录项里的 d_name(文件名)直到找到名字完全匹配的那一项。找到后取出这个目录项里的 d_inode(home 对应的 inode 号比如 10)。然后进入 home 目录系统拿到 inode 号 10再次去 Inode Table 里找到对应的 inode 结构体。这就意味着当前操作的目录已经从“根目录”切换到了“home 目录”。然后系统会从这个新的 inode 结构体里取出 i_block 指针找到 home 目录自己的数据块。这个数据块里存的就是 home 目录下的所有文件和子目录的目录项。第三步就是重复上述过程系统继续拿着下一个名字 cxj在 home 目录的数据块里查找。找到后取出 cxj 对应的 inode 号进入 cxj 目录。再拿着 test在 cxj 目录的数据块里找进入 test 目录。这个过程就像递归一样一级一级深入目录树。第四步是找到目标文件的 inode 编号当系统遍历到最后一个名字 log.txt 时流程就到了最后一步系统现在处于 test 目录。它在 test 目录的数据块里遍历目录项找到名字叫 log.txt 的那一项。取出它的 d_ino(比如 100)这就是 目标文件的 inode 号。至此路径解析结束。系统手里已经拿到了 log.txt 的 inode 号 100。接下来的操作(比如 cat 读文件)就可以拿着这个 inode 号去 Inode Table 取 inode 结构体再通过 i_block 去数据块里读取文件内容了。这也就是访问文件必须要有 目录文件名路径 的原因但是路径谁提供你访问文件都是指令/工具访问本质是进程访问进程有CWD进程提供路径。四、路径缓存路径缓存DCache就是系统为了避免重复做路径解析把已经找过的“文件名 → inode”的映射关系存到内存里的机制。它不是磁盘上的东西是内存里的高速缓存。1.为什么要路径缓存举个例子当你连续两次 cat /home/cxj/test/log.txt 。如果不做缓存那么第一次解析系统从根目录开始一级一级查全程跑一遍。第二次解析系统又从根目录开始再跑一遍。这样肯定不现实。所以就有了路径缓存当第一次解析完后系统把 /home/cxj/test/log.txt 的结果(log.txt的 inode存到内存里。第二次解析系统直接去内存里找一秒钟拿到 inode不用再去磁盘查。2. 路径缓存存在哪路径缓存存在 DCache(目录项缓存)中。它的核心数据结构就是 struct dentry。每一个访问过的文件/目录都会有一个 dentry 对象存放在内存缓存中。dentry 里存着文件名、对应的 inode 指针、父目录等。struct dentry先描述:字段类型核心含义d_countatomic_t引用计数有多少地方在用这个 dentry比如进程、文件句柄计数0 就不能被回收。d_inodestruct inode *绑定的 inode这个 dentry 对应磁盘上的哪个文件inode。 关键没有这个就不知道对应哪个文件。d_parentstruct dentry *父目录 dentry当前目录的上一级目录。 比如 /home/ 是 /home/test.txt 的父目录。d_namestruct qstr文件名当前文件/目录的名字字符串长度。d_hashstruct hlist_node哈希节点把 dentry 存到哈希表里快速查找用。d_lrustruct list_headLRU 链表最近最少使用链表。缓存满了就从这里淘汰最久不用的 dentry。d_subdirsstruct list_head子目录链表当前目录下的所有子目录/文件的 dentry 都挂在这里。d_sbstruct super_block *超级块当前文件系统的根比如 /dev/sda1 这个分区。再组织 : 缓存是怎么工作的根节点就是你的根目录 / 。子节点就是下面的每一个目录、文件都是它的子节点。通过根 →一级目录→二级目录/文件→... 严格分层进行组织。d_parent 指针指向每一个节点父目录的dentry。d_subdirs 指针指向每一个目录的子文件/子目录的dentry。最终就形成了一棵清晰的目录树想找哪个文件沿着树往下查就行。这样当系统查找文件时就是在这棵内存树里从根开始一级级往下找 d_subdirs找到目标节点后通过 d_inode 操作磁盘实体。第一次查找系统根据路径尝试从 DCache 找 dentry。如果找不到(缓存未命中)。系统去磁盘执行完整的路径解析找到 inode。系统就会新建一个 dentry 对象把这个映射关系写入DCache。那么第二次查找系统根据路径就会从 DCache 找该文件的 dentry。直接取出对应的 inode跳过磁盘遍历。挂载分区挂载分区简单说就是把一个硬盘或分区“嫁接”到 Linux 系统目录树的某个目录上让这个目录能访问新分区的内容。在 Linux 里所有文件和目录都属于一棵统一的根目录树不管它来自哪个硬盘、哪个分区。而挂载就是给系统“新增一个分支”你找一个空目录叫挂载点然后把一个新分区的根目录“挂”到这个目录上。挂载之后你进入这个目录看到的就不再是原来的内容而是新分区里的所有文件和子目录。比如你有一个新分区想用来存数据先新建目录 /data再把分区挂载到 /data 之后进入 /data 就是在访问这个新分区的内容。本质上挂载是让 Linux 统一目录树和各个物理分区建立关联的方式所有分区最终都会以目录的形式融入这棵系统树方便统一访问。下面我们模拟一个分区并把它挂载到一个挂载点上(空目录):ls /dev/vda* 这条命令会列出你电脑里所有虚拟硬盘分区让你知道系统都识别了哪些硬盘。df -h 这条命令是查看所有已经挂载好的分区还剩多少空间。df意思是“磁盘文件系统”专门查分区使用情况。vda代表这是一块虚拟硬盘。2 代表这是这块硬盘上的第 2 个分区。通常挂载在 / 也就是根目录相当于整个操作系统的“大本营”。/dev/vda2 就是你 Linux 系统的系统盘操作系统和你所有的文件都存在这里。分区格式化就是给分区装“文件系统”格式化 mkfs.ext4 就是把 5MB 的disk.img文件模拟成一个分区然后给它装 ext4 系统。在分区上写好一套“文件系统规则”让系统知道这个分区怎么存文件、怎么找数据、怎么管理空间。这是格式化的本质。创建挂载点 : /mnt 是系统的标准目录通常需要管理员权限才能在其中创建新目录在 /mnt 下创建目录是为了作为挂载点为了后续我们挂载这个disk.img分区做准备把 ./disk.img 这个“虚拟分区文件” 挂载到 /mnt/myda3 这个目录(挂载点)上。上面就是我们模拟并挂载分区文件系统并成功的在分区下创建并写入了文件下面就是如何卸载这个分区系统:九、文件系统总结我们先看一幅图:这张图展示了 Linux 进程与文件系统交互的核心数据结构关系从进程到打开的文件再到磁盘文件的完整链路。最外层是struct task_struct (进程描述符这是 Linux 内核进程的结构体里面和文件相关的两个关键成员一个是 struct fs_struct (进程文件系统信息)记录进程的根目录 root 、当前工作目录(pwd等路径信息每个路径用 struct path 表示struct vfsmount *mnt 这个路径所在的挂载分区struct dentry *dentry 这个路径对应的目录项节点。另一个就是 struct files_struct (进程打开文件表)这就是进程的「文件描述符表」里面有一个数组 struct file * fd_array[]数组下标就是我们常说的文件描述符 fd比如 0标准输入1标准输出)每个数组元素是一个 struct file *指向一个已打开的文件对象。还struct file (打开文件对象是内核描述一个被打开的文件的核心结构体里面的关键字段有struct path f_path指向文件的路径信息挂载点 dentryf_path.dentry是文件对应的 dentry 节点连接到目录项树f_path.mnt是文件所在的挂载分区f_pos是当前文件读写偏移量每次读写后会更新f_flags / f_mode是打开方式只读/只写/读写等和权限f_op是文件操作函数集合比如 read / write 的底层实现)。有个 struct path (路径对象)它把「挂载点」和「dentry」打包在一起一个 struct path 就代表了系统里的一个绝对路径比如 /home/cxj/test.txt 就对应这个文件的绝对路径mnt指向 /home 所在的分区dentry指向 test.txt 文件的目录项节点。进程通过 files_struct 管理所有打开的文件用文件描述符 fd 索引。每个 fd 对应一个 struct file记录这个文件怎么被打开读写模式、偏移量等。struct file 通过 f_path 找到文件的 dentry再通过 dentry 找到 inode最终操作磁盘上的文件。同时fs_struct 记录进程自己的根目录和当前工作目录用来解析相对路径。五、问题还有几个问题:1. 文件的权限不是在文件的inode结构体里吗那为啥 struct file 结构体也有文件的权限这两个有啥不同struct inode 里存的是文件本身的权限磁盘上的真实权限永久有效inode 里的权限( i_mode 是文件的固有属性存在磁盘上关机不丢。比如你用 chmod 644 file.txt 改的就是这里。它定义了谁能读、谁能写、谁能执行这个文件。所有打开这个文件的进程都要先和它做权限校验。struct file 里存的是这次打开操作的访问模式临时权限只在本次打开期间生效。file 里的权限( f_mode 是本次打开时的操作权限只存在内存里文件关闭就消失。它来自你调用 open() 时传入的标志比如 O_RDONLY / O_WRONLY / O_RDWR。它限制的是这次打开之后你能对这个文件做什么操作。2. 那files_struct结构体和file结构体又是啥关系?struct files_struct 是进程的“文件描述符表” struct file 是被打开的文件对象。它们的关系是一个进程的 files_struct 里用数组管理着多个 file 指针数组下标就是我们常说的文件描述符fd。3. 那就是在创建文件的时候就有这个 file 结构体了吗这是内核创建的结构体吗还有pcbstruct file 不是创建文件时而是打开文件时由内核创建。你用 touch / creat 新建文件时只会在磁盘上生成 inode 数据块内存里不会自动生成 struct file 。只有当你调用 open() 打开这个文件时内核才会在内存里创建一个 struct file 对象代表这次打开操作。每次 open() 都会创建一个新的 struct file。当你调用 close() 时内核会减少它的引用计数计数为 0 时就销毁这个结构体。并且 struct file 完全由 Linux 内核创建和管理用户态程序比如你的 C 代码只能拿到它的“代理”——文件描述符fd不能直接访问这个结构体。4. struct files_structstruct file都是在内存中吗还是磁盘还是cpu?全部都在内存里不在磁盘也不在 CPU 寄存器里。结构体位置生命周期task_struct内存进程存在期间一直存在进程退出后销毁struct files_struct内存随进程创建而创建进程退出后销毁struct file内存open() 到最后一个 close() 之间存在struct dentry内存路径缓存DCache长时间不用会被回收struct inode内存(缓存) 磁盘(持久化内存里是缓存副本磁盘里是原始数据struct super_block内存(缓存) 磁盘(持久化内存里是缓存副本磁盘里是原始数据内存 就是 介于 磁盘 和 CPU 之间的中间层。三者位置关系从上到下1. CPU —— 最快、最贵、最小只跟内存打交道2. 内存RAM —— 中速、中价、中量中间层3. 磁盘硬盘 —— 最慢、便宜、量大持久存储数据流动只有一条路磁盘 → 内存 → CPU。CPU 绝不直接读磁盘太慢了等不起。5. 目录项就是 dentry 吗?目录项就是 dentry是 Linux 内核用来管理路径和快速查找文件的内部结构。每个文件或目录在内存里都对应一个 dentry 对象。六、总结本文深入探讨了Linux文件系统的核心机制。文件系统作为管理文件的规则体系通过块(block)、分区(partition)、inode等概念实现对磁盘空间的高效组织。重点分析了ext2文件系统的结构组成超级块记录全局信息块组描述符管理分区属性块位图和inode位图标记资源使用状态inode表存储文件属性数据块存放文件内容。详细解释了目录作为特殊文件的实现原理路径解析的逐级查找过程以及路径缓存(DCache)的加速机制。最后阐述了进程与文件系统交互的数据结构关系包括task_struct、files_struct、file等关键结构体的作用及生命周期。整个文件系统通过多级抽象将物理磁盘转换为逻辑目录树为操作系统提供统一、高效的文件管理能力。谢谢大家的观看!

相关文章:

Linux 基础IO (五)深入理解文件系统

目录 一、文件系统 引入“块”概念 引入“分区”概念 引入“inode”概念 引入文件系统 分区(Partition) ext2文件系统 块组(Block Groups) Data Blocks(数据块) Block Bitmap(块位图) Inode Table(inode 表) Inode Bitmap(inode 位图) GDT(…...

收单 vs 代付 vs 收付:支付三业务快速区分

想分清收单和代付?一个例子就能看明白:收单:消费者用微信、支付宝等第三方平台付款时,资金先进入第三方支付账户,再转给商户。核心是第三方平台参与资金中转,是商户侧的收款服务。代付:消费者用…...

基于PLC的加热炉控制设计:西门子S7-200PLC组态王画面、IO表、电路图、说明书及可仿真

基于PLC的加热炉控制的设计,西门子S7-200PLC组态王画面,IO表,电路图,说明书,可仿真搞工业自动化的人都知道,PLC控制加热炉是个经典项目。这次拿西门子S7-200开刀,咱们先看现场硬件配置——炉体温…...

2. OpenClaw小龙虾(macOS)+飞书本地部署:小白10分钟搞定,保姆级教程

OpenClaw是一个开源的AI智能体,让你可以在本地部署AI助手,操作本地文件。支持通过飞书、企业微信、QQ、钉钉和Telegram等国内外通讯平台随时指挥。支持 Claude、GPT、Gemini、DeepSeek、MiniMax、通义千问和Kimi等多种模型。 集文件管理、知识管理、日程…...

装傻生存指南:软件测试从业者的AI对抗方法论

第一章 智能监控时代的测试者困境 1.1 算法评估的隐形战场 用户价值评分模型解析(LTV预测算法) 行为威胁评估矩阵:点击热图/操作路径/会话时长的量化监控 案例:某电商测试员因高频触发边界条件被风控系统标记 1.2 无害废物的…...

【材料学】基于matlab DIAGNOSE热塑性复合材料的三维拓扑映射【含Matlab源码 15183期】

💥💥💥💥💥💥💞💞💞💞💞💞💞💞欢迎来到海神之光博客之家💞💞💞&#x1f49…...

C语言的反汇编

1.C语言的反汇编这个函数在K5里面随便写在一个地方让Keil生成反汇编:为例方便复制,制作反汇编的指令如下:fromelf --text -a -c --outputxxx.dis xxx.axfxxx.dis,是输出一个什么名字的反汇编,所以xxx填testxxx.axf&…...

C++版序列二次规划SQP求解非线性优化问题,支持多种约束条件,全源码开源,含demo与Vis...

C版序列二次规划SQP cpp程序 求解非线性优化问题的序列二次规划法cpp程序,支持目标函数和约束条件均为非线性函数,支持等式约束,不等式约束,混合约束。 源码全开源,代码及头文件共7个文件(包含描述示例demo…...

程序员生存图鉴2026:技术深耕、职业破局与可持续发展

在技术迭代加速、职场竞争白热化的2026年,程序员的生存逻辑已从“单纯会编码”升级为“技术硬实力职业软实力可持续发展”的综合比拼。本文基于CSDN百万程序员调研数据,围绕技术能力、职业发展、社区生态、生存现状、工具资源五大核心维度,拆…...

【认识-掌握】Elasticsearch的用法

Elasticsearch认识与安装倒排索引传统遍历,数据量越大遍历时间越长。性能会变差IK分词器基础概念Mapping映射属性索引库操作字段只能添加不能修改文档CRUDJavaRestClient索引库操作DSL查询叶子查询复合查询排序和分页高亮显示基于java客户端的操作基本查询排序和分页…...

COMSOL太赫兹超表面BIC与能带折叠

comsol太赫兹超表面BIC与能带折叠。超表面结构里藏着不少反直觉的物理现象,特别是当能带折叠遇上BIC(连续谱中的束缚态),总能在仿真结果里搞出些让人挠头的惊喜。最近用COMSOL折腾太赫兹频段的超表面时,发现这两个机制…...

医疗HIS系统Java如何通过控件优化病历图片文件夹的浏览器端分片加密断传?

《Java老鸟的奇幻漂流:20G文件上传与100元预算的史诗级对决》 1. 甲方需求 vs 现实预算(魔幻现实主义版) 甲方:“要支持20G文件夹上传哦,保留层级结构那种~” 我:“没问题老板,您预算是…&…...

中断很难?看完这篇就懂了

1.内核,总线,外设这三个概念是理解中断的必要前提,一个芯片具有内核、总线、外设这三个结构内核:芯片里的内核有很多架构,如ARM架构内核,它包含了许多核心部件,是整个芯片的大脑总线&#xff1a…...

MWC2026观察:通用算力开始进入“超节点时代”

导读:AI重塑CPU产业角色ChatGPT问世之后,全球算力产业的叙事几乎被GPU主导。但这恰恰遮蔽了另一个更重要的变化:AI时代以CPU为基础的通用算力并没有被削弱,反而重塑了产业地位。今天的大模型系统,从数据预处理、检索增…...

Claude 终极新手指南(2026年3月爆款版)

从 0 到熟练:这篇就够了。 顺便说一句:上周 Anthropic 那波更新,有点“把门直接踹开”的感觉。 这篇指南的目标很简单:把你从“摸索学习曲线”直接带到“立刻产出结果”。 即使你已经用 Claude 很久了,也很可能还能从中…...

强化学习算法ppo

最容易上手、最适合入门的强化学习算法是 PPO(Proximal Policy Optimization) —— 没有之一。相比于 Q-Learning、DDPG、DQN 等算法,PPO 的 “易上手” 体现在:代码实现简单、训练极少崩溃、调参门槛低、适配场景广,完…...

基于springboot企业车辆管理系统

一、系统核心定位 基于 SpringBoot 的企业车辆管理系统,是专为企业(尤其是拥有多辆公务车、货运车的中大型企业)打造的 “车辆调度 - 使用 - 维护 - 成本” 全流程数字化平台。该系统解决传统车辆管理中 “调度混乱、用车申请繁琐、维护不及时…...

springboot基于微信小程序的学院搞笑大学生竞赛管理系统设计与实现

目录系统架构设计功能模块划分数据库设计关键技术实现评审系统设计测试与部署项目里程碑项目技术支持可定制开发之功能创新亮点源码获取详细视频演示 :文章底部获取博主联系方式!同行可合作系统架构设计 采用SpringBoot作为后端框架,微信小程…...

影响力--题解

题干中给的是,切比雪夫距离的公式 如果使用暴力算法, 需要遍历每个格子A;对每个格子A,都要遍历所有格子B;计算代价。 发现复杂度太高,O((nm)2),n的二次方乘m的二次方 所以这个方法不可行。这时我…...

基于FPGA的数据同步采集处理框架:包含ADC7606芯片数据采集模块、多通道数据处理模块、D...

基于fpga的数据同步采集处理,包含adc7606芯片的数据采集模块,多通道数据处理模块,ddr3缓存模块,使用SRIO通信模块,以及各个模块的仿真文件,提供学习整个框架。 包含单独的ddr3仿真,srio通信协议仿真&#x…...

基于springboot湄潭县乡村茶产品管理系统

一、系统定位与核心目标 湄潭县作为中国著名茶产区,其乡村茶产品管理需解决传统模式中的信息分散、产业链协同低效等问题。基于SpringBoot框架开发的茶产品管理系统,旨在通过数字化手段实现以下目标: 全产业链整合:覆盖茶园种植、…...

AI智能开发代码

import openai# 设置API密钥 openai.api_key = "your_api_key_here"def generate_text(prompt, max_tokens=50):"""使用AI模型生成文本参数:prompt (str): 输入提示文本max_tokens (int): 生成文本的最大长度返回:str: 生成的文本"""t…...

JAVA 国际版多商户团购扫码核销系统源码:支持多语言 + 多商户,可直接商用运营

随着本地生活、跨境团购、连锁门店、海外文旅场景快速发展,传统单商户团购系统已无法满足多商户入驻、多语言切换、扫码秒核销、跨境支付等真实运营需求。为此,一套稳定、成熟、可直接上线的 JAVA 国际版多商户团购扫码核销系统 成为开发者、创业者、企业…...

springboot基于微信小程序的二手书交易系统

基于 SpringBoot 和微信小程序的二手书交易系统是一款专为学生、书友等群体打造的二手书交易平台,借助 SpringBoot 的高效后端处理能力和微信小程序的轻量化特性,实现二手书的发布、浏览、交易、评价等全流程数字化管理,旨在促进闲置书籍的循…...

基差贸易全流程详解:高效点价与自动化下单的最佳实践

引言:破解基差贸易的高效难题——从人工瓶颈到自动化革新 基差贸易,作为现代大宗商品交易中的核心定价模式,彻底改变了企业锁定采购/销售价格的方式。无论是农产品、金属、能源还是化工,基差贸易都成为贸易公司与生产企业应对价格…...

虚拟同步发电机自适应控制(VSG)转动惯量与阻尼系数自适应调整并网仿真研究:角频率变化率对J和...

虚拟同步发电机自适应控制(VSG)转动惯量和阻尼系数自适应控制(并网)仿真 下图附带:参考文献的自适应算法以及仿真结果 仿真结果:J和D能够很好的根据角频率变化率和角频率变化率进行自适应2018a版本以上都可…...

快期指令系统优势全解析:高效合规的期现交易新范式

引言:破解期现指令管理难题,迈向高效合规新时代 在期现交易业务的迅猛发展背景下,指令管理的复杂性与合规要求持续提升。无论是客户直连交易,还是业务经理代操,企业都面临着权限分配繁琐、审批流程冗长、指令执行不透…...

FX5U PLC数据类型详解

本文详细整理了三菱FX5U系列PLC的各类数据类型定义、取值范围、存储方式及位操作方法。---1. Bool(位)类型| 属性 | 说明 ||------|------|| **数据位** | Bool只可能是 **0 / 1** || **数据大小** | 1位 || **X、Y点** | 8进制 |输入/输出区定义- **输入…...

树结构概述:从家谱到文件系统

在计算机科学中,树结构是一种基础且应用广泛的数据结构,它的设计灵感源于现实世界中的“树”——比如我们每个人都熟悉的家谱,又比如电脑中管理文件的文件系统。看似毫不相关的两个场景,背后却共享着树结构的核心逻辑。今天&#…...

comsol sofc固体氧化物燃料电池 单通道非绝热逆流固体氧化物燃料电池模型,包括阴阳极气...

comsol sofc固体氧化物燃料电池 单通道非绝热逆流固体氧化物燃料电池模型,包括阴阳极气体扩散层,电极扩散层尺寸来源于实际电池(极化曲线,性能曲线,气体分布,温度分布)在燃料电池的江湖里&#…...