linux EXT文件系统 认识磁盘谈文件系统之前我们先来认识一下磁盘这个就是我们目前家用电脑上最常见的机械硬盘他的优点是存储容量大价格便宜我们来看看他的内部构造我们所有的数据都是保存在盘片上的那么是怎么存储数据的 在磁盘上面会有很多的磁道所有文件、系统数据都写在磁道上单个磁道空间太大会再切分成扇形小块叫扇区最小读写单位一组上下对齐、同编号的磁道合起来叫柱面柱面就是磁盘的正面和反面合起来的称为柱面)扇区是磁盘存储数据的基本单位大小为512字节是一个块设备。那么我们现在知道了数据是存在扇区上面的我们知道文件内容属性在磁盘里面归根结底就是占用多少个扇区的问题那么我们怎么去定位我们存放在磁盘上的数据的呢如何定位⼀个扇区呢1.可以先定位磁头header2.确定磁头要访问哪⼀个柱⾯(磁道)cylinder3.定位⼀个扇区(sector)这就是 CHS地址定位扇区是从磁盘读出和写⼊信息的最⼩单位通常⼤⼩为512字节。磁头head数每个盘⽚⼀般有上下两⾯分别对应1个磁头共2个磁头磁道track数磁道是从盘⽚外圈往内圈编号0磁道1磁道...靠近主轴的同⼼圆⽤于停靠磁头不存储数据柱⾯cylinder数磁道构成柱⾯数量上等同于磁道个数扇区sector数每个磁道都被切分成很多扇形区域每道的扇区数量相同圆盘platter数就是盘⽚的数量磁盘容量磁头数 × 磁道(柱⾯)数 × 每道扇区数 × 每扇区字节数上面我们说了磁盘的物理结构下面我们来说说磁盘的逻辑结构磁盘的逻辑结构我们想一下磁盘的磁道就相当于一组一组的同心圆构成如果我们把里面的同心圆全部拿出来把他放直形成线性结构与就像下面这样那么磁盘本质上虽然是硬质的但是逻辑上我们可以把磁盘想象成为卷在⼀起的磁带那么磁盘的逻 辑存储结构我们也可以类似于下面这样这样每⼀个扇区就有了⼀个线性地址(其实就是数组下标)这种地址叫做LBALBA就是一个逻辑地址磁盘访问都是用这个地址。这个只是我们方便理解但是在现实过程中磁盘的的真实运动过程是这样的我们要知道一个细节磁盘上面的磁头是共同进退的就像下面这张图一样我们把磁道展开是一维数组而把柱面展开就是一个二维数组我们看看下面的图片那么整个磁盘都展开这不就是一个三维数组吗但是在计算机世界里面我们无法表示除开一维数组以外的所有维度的数组这时候我们就需要把三维数组通过算法抽象成一位数组所以寻址⼀个扇区先找到哪⼀个柱⾯(Cylinder) ,在确定柱⾯内哪⼀个磁道(其实就是磁头位置 Head)在确定扇区Sector所以就有了 CHS。我们把它抽象成一个一维数组每⼀个扇区都有⼀个下标我们叫做LBA(Logical Block Address)地址,其实就是线性地址。所以怎么计算得到这个LBA地址呢OS只需要使⽤LBA就可以了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//: 表⽰除取整有了上面的对于硬件的理解之后我们来从linux系统角度来看看文件引入文件系统引⼊块概念其实硬盘是典型的“块”设备操作系统读取硬盘数据的时候其实是不会⼀个个扇区地读取这样效率太低⽽是⼀次性连续读取多个扇区即⼀次性读取⼀个”块”block。硬盘的每个分区是被划分为⼀个个的”块”。⼀个”块”的⼤⼩是由格式化的时候确定的并且不可以更改最常⻅的是4KB即连续⼋个扇区组成⼀个 ”块”。”块”是⽂件存取的最⼩单位。我们来看看我们linux下的一个文件我们可以看到这一个块的大小是4096字节也就是4kb磁盘就是⼀个三维数组我们把它看待成为⼀个⼀维数组数组下标就是LBA每个元素都是扇区每个扇区都有LBA那么8个扇区⼀个块每⼀个块的地址我们也能算出来。知道LBA块号 LBA/8知道块号LAB块号*8 n. (n是块内第⼏个扇区引入分区的概念在我们日常生活中我们的磁盘会被划分成多个分区就拿windows来说我们一般会划分成c盘d盘那个c,d就是分区。分区从实质上说就是对硬盘的⼀种格式化。也是一种分治思想但是Linux的设备都是以⽂件形式存在那是怎么分区的呢柱⾯是分区的最⼩单位我们可以利⽤参考柱⾯号码的⽅式来进⾏分区其本质就是设置每个区的起 始柱⾯和结束柱⾯号码。 此时我们可以将硬盘上的柱⾯分区进⾏平铺将其想象成⼀个⼤的平 ⾯如下图所⽰但是光用柱面来划分又太麻烦了linux会以块为单位划分分区就像下面的图片一样这样我们就可以对磁盘进行分区了引入inode概念我们知道 文件内容加属性我们可以用ls -l看到文件的属性除了可以看到文件名字还可以看到文件的属性其中的属性包括ls -l读取存储在磁盘上的⽂件信息然后显⽰出来到这我们要思考⼀个问题⽂件数据都储存在”块”中那么很显然我们还必须找到⼀个地⽅储存⽂件的元信息属性信息⽐如⽂件的创建者、⽂件的创建⽇期、⽂件的⼤⼩等等。这种储存⽂件 元信息的区域就叫做inode中⽂译名为”索引节点”。我们用ls -li就可以看到文件的inode每⼀个⽂件都有对应的inode⾥⾯包含了与该⽂件有关的⼀些信息。为了能解释清楚inode我们需 要是深⼊了解⼀下⽂件系统。在了解之前我们需要注意几点⽂件名属性并未纳⼊到inode数据结构内部inode的⼤⼩⼀般是128字节或者256我们后⾯统⼀128字节任何⽂件的内容⼤⼩可以不同但是属性⼤⼩⼀定是相同的深入理解文件系统有了上面的理解之后我们需要深入了解文件系统需要知道在linux操作系统中文件到底在硬盘上面是如何存放的ext2文件系统我们知道linux系统中文件是以块为单位存放的我们的分区就是以块为单位记录一个起始地址和结束地址就代表一个分区但一个分区一般是200g或者300g或者更大这样也不好管理在Linux系统中还会把分区继续细化变成以组为单位ext2⽂件系统将整个分区划分成若⼲个同样⼤⼩的块组 (Block Group)如下图所⽰。只要能管理⼀个 分区就能管理所有分区也就能管理所有磁盘⽂件。如下图所示这个图片我们会详细说明首先宏观的大体看一下首先从上至下第一个是如下图所示第二层第三层第四层data block数据区存放⽂件内容也就是⼀个⼀个的Block。根据不同的⽂件类型有以下⼏种情况普通⽂件⽂件的数据存储在数据块中。Block 号按照分区划分不可跨分区datablock大小为4kbinode table我们知道linux中任何文件都要有自己的属性集合而这些属性集合的大小是一样的而inode_table就是保存这些属性的首先我们来说说这个 inode table 这里面是存放文件的各种属性包括权限类型等而这个是一个结构体一个数据块是4kb 4096/128 等于 32 也就是说一个数据块可以保存32个inode 也就是说这里可以存放32个文件的属性有了inode_table我们就能知道文件的属性了但是在磁盘中有千千万万个这种文件的属性那我们怎么可以快速知道哪些inode被使用哪些没有呢inode_bitmap用一个位图我们就能快速知道了在inode_bitmap中每个bit表⽰⼀个inode是否空闲可⽤。假设有10万个inode编号我们需要快速定位我们就需要10万多个比特位 2的17次方大于10万转换为10进制就是131072位图初始化内容一共131072 个比特全部填 0然后从一开始编号这个就是我们的位图计算过程计算机最小可寻址单元字节 Byte 8bit所以我们用unsigned char buf[16384]字节数组承载。buf 数组里每一个 char 存放连续 8 个 bit。8*16384131072这里换算下来就是16Kb,我们就需要4个块data_block)来存储查找空闲 inode 流程遍历 inode_bitmap寻找第一个值为 0 的 bit算出 bit 下标 → 映射得到 inode 编号将该 bit 置 1标记已占用释放 inode将对应 bit 重新置 0有了inode_bitmap能够快速帮我们定位inode编号我们同时也需要知道哪些data_block被使用了这时候data_block也有自己的bit_mapBlock BitmapBlock Bitmap中记录着Data Block中哪个数据块已经被占⽤哪个数据块没有被占⽤我们这时候想一个问题一个分区会被细化为多个组inode编号和data_block编号只能在同一个组中使用吗答案是不是的inode和数据块是跨组编号的但是inode和数据块不能跨分区所有在同一个分区内部inode和块编号都是唯一的。有了以上的了解文件系统还需要一个部分来描述整个关于inode data_block的整体使用情况所以就有了GDTGDT块描述表块组描述符表描述块组属性信息整个分区分成多个块组就对应有多少个块组描述符。每个块组描述符存储⼀个块组 的描述信息如在这个块组中从哪⾥开始是inode Table从哪⾥开始是DataBlocks空闲的inode和数据块还有多少个等等。块组描述符在每个块组的开头都有⼀份拷⻉。这个就相当于对整个块的一个总览Super Block超级块是是存放⽂件系统本⾝的结构信息描述整个分区的⽂件系统信息。记录的信息主要有bolck 和 inode的 总量未使⽤的block和inode的数量⼀个block和inode的⼤⼩最近⼀次挂载的时间最近⼀次写 ⼊数据的时间最近⼀次检验磁盘的时间等其他⽂件系统的相关信息。Super Block的信息被破坏可 以说整个⽂件系统结构就被破坏了 我们再回到上面的图片看一个细节超级快既然是对真个硬盘分区的一个描述那么为什么每一个组前面都要有一个超级块呢答案是备份在我们日常生活中有时候我们的windows电脑会意外关机再开机的时候就会看到他在转圈圈一般要转个几分钟这个就是磁盘的分区损坏了在进行恢复超级块在每个块组的开头都有⼀份拷⻉第⼀个块组必须有后⾯的块组可以没有。 为了保证⽂件系统在磁盘部分扇区出现物理问题的情况下还能正常⼯作就必须保证⽂件系统的super block信 息在这种情况下也能正常访问。所以⼀个⽂件系统的super block会在多个block group中进⾏备份 这些super block区域的数据保持⼀致。有了以上的对于文件系统的了解我们来谈谈下面的问题硬盘格式化的本质我们平时复制东西一般我们在复制大文件的时候会复制很久但是如果是删除大文件一下子就删除完毕了这个删除是真的像复制那样进行挨个挨个删除吗答案是不是的我们知道文件里存放的东西本质就是由inode 和data_block组成而inode 就是一个文件在磁盘中的编号我们删除东西本质就是把inode编号设置成未使用再把inode_bitmap里面的对应的比特位设置成0还有修改块位图就行了哪些块属于一个文件呢再inode_table里面有一个数组int data_block[]就可以记录哪些块被使用了剩下的inode对应的data_bolck里面的东西本质就是乱码不关心而硬盘格式化就是重置super_bolck GDT inode inode_bitmap bolci_bitmap 就可以了如果需要完整格式化data_block也需要重置速度就会慢一些。一句话总结本质就是修改文件系统的管理信息linux只要知道了文件的inode 就可以定位文件了。如何看待目录我们访问⽂件都是⽤的⽂件名没⽤过inode号啊磁盘上只要对应的文件和属性也没有这个目录的概念目录的本质也是文件也是有属性加文件内容但是目录里面的内容是存放对应的文件名和inode对应的映射关系这其实也是数据那我们平时查找文件时都需要提供路径为什么下面用ls 时就不需要文件路径了呢我们查找文件本质就是进程在查找文件而进程有对应的cwd当前所在的路径这里的路径操作系统提供了我们就不需要再提供了我们用的文件名访问文件本质就是路径文件名先打开对应的路径读取里面文件的内容得到文件名和inode的映射关系进行文件的查找linux查找任何文件都必须要从根目录往下面找才能找到对应的文件我们再想一个问题我要查找一个·文件都必须要从根目录往下面找是不是太慢了对于这种情况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 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结构包括普通⽂件。这样所有被打开的⽂件就可以在内存中形成整个树形结构注意在叶子节点上也会存在对应的struct dentry结构为什么通过struct dentry就可以找到文件对应的inode!!)整个树形节点也同时会⾪属于LRU(Least Recently Used最近最少使⽤)结构中进⾏节点淘汰整个树形节点也同时会⾪属于Hash⽅便快速查找更重要的是这个树形结构整体构成了Linux的路径缓存结构打开访问任何⽂件都在先在这棵树下根据路径进⾏查找找到就返回属性inode和内容没找到就从磁盘加载路径添加dentry结构缓存新路径我们已经能够根据inode号在指定分区找⽂件了也已经能根据⽬录⽂件内容找指定的inode了在 指定的分区内。可是inode不是不能跨分区吗Linux不是可以有多个分区吗我怎么知道我在哪⼀个分区挂载分区我们开做一个实验上面的图片我们制作了一个大磁盘块相当于一个分区格式化写入文件系统建立空目录然后查看可使用的分区将指定分区挂载到指定目录我们刚才做了创建磁盘格式化分区然后再挂载到指定目录我们的分区一定要和某一个目录进行关联进入这个目录相当于进入这个分区这个操作叫挂载所以再次回到我们刚才的问题操作系统是如何知道我们的哪个文件是在哪个分区的答案是在我们的文件路径中某一个文件一定是一个挂载点对应一个分区所以我们能够找到对应的分区下的对应文件在最后我们在讲一个inode 和data_block的映射关系这里用15个块共计60kb就能表示很大的文件了小文件就直接用前面的12个直接块指针大文件就用一级二级三级块指针来表示这样就可以存放大文件如果这个都不满足的话就需要使用分布式存储了这就是linuxext2文件系统