该篇blog只是存储系列科普文章中的第二篇,所有文章请参考:
博客所有文章
在工程架构领域里,存储是一个非常重要的方向,这个方向从底至上,我分成了如下几个层次来介绍:
在存储知识栈上提到文件系统, 大家首先就关心他是如何管理好硬件层提供的磁盘空间的。
本文开篇就先描述典型的几种文件系统管理磁盘的技术方案: 连续分配, 链式分配, 索引分配。
顾名思义, 就是创建文件的时候, 给分配一组连续的块。在单独拿出一块地方存储各个文件的meta信息, meta信息也很简单, 包含文件名, 起始位置和长度即可, 这个存放meta信息的地方也叫做FAT(文档分配表)。
如下图:
该方案的优点:
缺点也很明显:
为了克服连续分配的问题, 又进化出来了链式分配方案。
链式分配即将文件块像链表一样管理起来, 每个块中放一个指针, 指针指向下一个文件块所在的位置。这样在FAT中存储也很简单, 只需要存储文件名, 起始块号和结束块号(都可以不存)。
如下图:
该方案的优点:
缺点:
这是一个折衷方案, 也是现在大部分文件系统采用的方案, 他综合了连续分配和链式分配的好处。
该方案会在FAT中保存所有文件块的位置, 各文件系统都有一套自己对应的细节分配策略, 会保证一个文件尽量连续的同时, 又避免出现大量的磁盘碎片。
如下图:
该方案的优点是:
缺点:
一个ext文件系统分成多个块组, 每个块组的结构基本相同, 如下:
解释如下:
2~3号扇区: 超级块, 超级块包含各种meta信息:
组描述符表:
位于超级块后面的一个块, 注意在超级块之前只占用了4个扇区, 如果一个块大小超过4个扇区的话, 这里就会有空的扇区。
组描述符表中包含了所有块组的描述信息, 每个块组占据32个字节(差不多是8个整数的大小)。如果格式化使用默认参数, 那么组描述符表不会超过一块块组。
组描述符和超级块在每个块组中都有一个备份, 但是激活了 稀疏超级块
特征的情况除外。
稀疏超级块
即不是在所有块组中都存储超级块和组描述符的备份, 默认该策略被激活, 其策略类似当块组号是3,5,7的幂的块组才存副本。
块位图表
每个块对应一个bit, 只包含了本块组的信息。而文件系统创建的时候, 默认每个块组包含的块数跟每个块包含的bit数是一样的, 这种情况下每个块位图表正好等于一个块的大小。
而该位图表的块的起始位置会在组描述符表中指定, 大小则为块组中包含块数个bit。
inode位图块
跟块位图表类似, 通常情况他也只占用一个块。通常一个块组中的inode节点数会小于block数量, 所以其不会超过一个块大小。
inode节点表
每个inode都占用128位的一个描述信息, 起始位置在组描述符中表示, 大小为inode节点数*128。
数据区
这就是真正存储数据的区域。
块大小为4096的时候, 各部分和扇区对应关系如下图:
块大小为2048的时候, 各部分和扇区对应关系如下图:
超级块总是位于文件系统的第1024~2048字节处。
超级块中包含的信息如下:
偏移(16进制) | 字节数 | 含义&解释 |
---|---|---|
00~03 | 4 | 文件系统中总inode节点数 |
04~07 | 4 | 文件系统中总块数 |
08~0B | 4 | 为文件系统预保留的块数 |
0C~0F | 4 | 空闲块数 |
10~13 | 4 | 空间inode节点数 |
14~17 | 4 | 0号块组起始块号 |
18~1B | 4 | 块大小(1024左移位数) |
1C~1F | 4 | 片段大小, 跟块大小一模一样 |
20~23 | 4 | 每个块组中包含的块数量 |
24~27 | 4 | 每个块组中包含的片段数量, 跟包含的快数量一致 |
28~2B | 4 | 每个块组中包含的inode节点数量 |
2C~2F | 4 | 文件系统最后挂载时间 |
30~33 | 4 | 文件系统最后写入时间 |
34~35 | 2 | 当前挂载数 |
36~37 | 2 | 最大挂载数 |
38~39 | 2 | 文件系统签名标识 53EF |
3A~3B | 2 | 文件系统状态, 正常, 错误, 恢复了孤立的inode节点 |
3C~3D | 2 | 错误处理方式, 继续, 以只读方式挂载 |
3E~3F | 2 | 辅版本号 |
40~43 | 4 | 最后进行一致性检查的时间 |
44~47 | 4 | 一致性检查的间隔时间 |
48~4B | 4 | 创建本文件系统的操作系统 |
4C~4F | 4 | 主版本号, 只有该值为1的时候, 偏移54之后的扩展超级块的一些动态属性值才是有意义的 |
50~51 | 2 | 默认为UID的保留块 |
52~53 | 2 | 默认为GID的保留块 |
54~57 | 2 | 第一个非保留的inode节点号, 即用户可以使用的第一个inode节点号 |
58~59 | 2 | 每个inode节点的大小字节数 |
5A~5B | 2 | 本超级块所在的块组号 |
5C~5F | 4 | 兼容标识特征 |
60~63 | 4 | 非兼容标识特征 |
64~67 | 4 | 只读兼容特征标识 |
68~77 | 16 | 文件系统ID号 |
78~87 | 16 | 卷名 |
88~C7 | 64 | 最后的挂载路径 |
C8~CB | 4 | 位图使用的运算法则 |
CC~CC | 1 | 文件再分配的块数 |
CD~CD | 1 | 目录再分配的块数 |
CE~CF | 2 | 未使用 |
D0~DF | 16 | 日志ID |
E0~E3 | 4 | 日志inode节点 |
E4~E7 | 4 | 日志设备 |
E8~EB | 4 | 孤立的inode节点表 |
EC~3FF | 788 | 未使用 |
每个块组描述符占用32个字节, 其数据结构如下:
偏移(16进制) | 字节数 | 含义&解释 |
---|---|---|
00~03 | 4 | 块位图起始地址(块号) |
04~07 | 4 | inode节点位图起始地址(块号) |
08~0B | 4 | inode节点表起始地址(块号) |
0C~0D | 2 | 块组中空闲块数 |
0E~0F | 2 | 块组中空闲inode节点数 |
10~11 | 2 | 块组中的目录数 |
12~1F | - | 未使用 |
每个inode会被分配给一个目录或者一个文件, 每个inode包含了128个字节, 里面包含了这个文件的各种meta信息。
在所有inode中, 1~10号会用作保留给内核使用, 在这些保留节点中, 2号节点用于存储根目录信息, 1号表示坏块, 8号表示日志文件信息。
第一个用户可见的都是从11号inode开始, 11号节点一般用作 lost+found
目录, 当检查程序发现一个inode节点已经被分配, 但是没有文件名指向他的时候, 就会把他添加到 lost+found
目录中并赋予一个新的文件名。
inode通过三级指针的方式来找到文件数据最终存储的数据块, 见下图:
这种方式能保证小文件的读取效率的同时, 也支持大文件的读取。
在ext文件系统中, 每个目录也对应一个inode节点, 该inode节点对应的数据块里面会存储该目录下所有文件/目录的信息。
每个文件/目录对应的信息就叫 目录项
, 目录项包含的内容也很简单, 主要就是文件名和指向该文件名的inode指针, 详细信息如下:
偏移(16进制) | 字节数 | 含义&解释 |
---|---|---|
00~03 | 4 | inode节点号 |
04~05 | 2 | 本目录项的长度字节数 |
06~06 | 1 | 名字长度字节数 |
07~07 | 1 | 文件类型 |
08~ | 不定长度 | 名字的ascii码 |
当文件删除的时候, 不会真正删除文件, 会将该文件所属目录对应的目录项给删除, 同时将对应数据块在快位图表中标记为0。
首先判断应该在哪个块组中分配
如果是为文件创建节点
如果父目录所在组没有空闲的节点或者空闲的块了, 就到别的块组中为该文件分配节点, 找寻别的块组的算法如下
如果是为目录创建节点
会将其分配到可用空间较多的块组中, 分配算法如下:
当一个数据块分配给某文件之后
我们创建一个 /xuanku/file.txt
, 该文件大小为10000字节, 文件块大小为4096, 那么下面来看一下过程:
xuanku
的目录项, 读到该inode节点为4724 xuanku
的目录内容位于17216号块 xuanku
目录项内容, 将 file.txt
相关信息更新到该块的目录项当中 file.txt
对应的目录项当中, 然后写各种时间, 记录日志 下面再演示一下将该文件删除的过程:
xuanku
的目录项, 读到该inode节点为4724 xuanku
的目录内容位于17216号块 xuanku
目录项内容, 读到 file.txt
的inode节点为4850 file.txt
的目录项分配, 修改系列 xuanku
目录对应的目录项信息