文件系统
块(block)设备
- 机械硬盘
在一个传统的机械硬盘中, 里面有很多个磁盘, 磁盘内有多个轨道, 探头一次读取一块数据. 单字节读取是几乎不可能的.
- 固态硬盘
固态硬盘中有个闪存(FLASH), 依然是一次读取多位数据, 读取一块.
块的大小跟具体硬件有关, 差异很大. 文件系统对存储块做了固定大小的抽象, 一般是512B或者4K.
基于inode的文件系统
文件就是有名字的字符串. 使用一些存储块来存储. 比如一个文件有4G, 块大小是4K的话, 就需要1M个块了. 修改文件数据就是修改里面的块.
这1M个块需要索引, 需要建立一个表来进行索引. 但是如果所有块的这么做的话, 这个表会相当的大, 跟内存管理的分段式一样. 因此文件管理系统也采用内存管理的分页式方法, 使用多级页表来索引这些存储块.
inode
对于每一个文件, 我们都维护一个 inode , 这个inode里有一个指针数组, 每一个指针要么指向一个数据块, 要么指向另一个指针数组. 因此这些指针也被分成直接指针, 间接指针, 二级间接指针等. 一般一个inode有12个直接指针, 3个间接指针和1个二级指针
inode最大索引数据大小
一般情况下, 非直接指针指向的索引块跟存储块一个大小, 也就是4K. 而一个指针是8个字节. 那么一个索引块总共有512个指针, 假设指针都指向数据块, 那么一个索引块可以索引2M数据.
那么一个直接指针可以索引4K数据, 一个间接指针可以索引2M数据, 一个二级指针可以索引
数据了.
当然这个比例是可调的.
不管这个inode有没有完全使用, 它总是占256字节, 即使有些指针并没有指向数据. 因此可能会出现大小跟占用空间不同的情况.
元数据
一个inode除了这16个指针占128字节, 还有128字节用来存放元数据. 也就是说一个inode总共256字节.
元数据指示这个inode的文件类型, 文件大小, 文件权限, 拥有者, 创建, 修改, 访问时间等.
文件名与目录文件
一个文件对应一个inode, 用inode编号来找到文件. 对于操作系统来说文件名不重要, 它只需要inode编号; 需要文件名的是我们. 所以inode的元数据里没有文件名, 文件名存在目录里.
目录本身是一个比较特殊的文件, 目录的文件名当然也存在目录里了. 有一个根目录/.
目录指向的数据块存储目录内的文件对应inode.
在目录里查找文件只需要逐个遍历文件名就可以了, 如果想要删除文件, 把对应的inode号置0, 置0后如果连续两个都为0, 那就合并.
存储布局
整套文件系统会在硬盘格式化时创建存储布局, 因此在插入原本用于其它文件系统的u盘或者sd卡时, 我们可能需要进行格式化. 不过格式化后数据也无了呢.
基于inode的文件系统存储布局是这样的, 它在整块硬盘上统一分配inode节点信息.
基于inode的文件系统的存储布局, 大概先弄一个超级块, 然后用一个块放 块分配信息 , 标识哪些块被使用; 还有 inode分配信息 , inode节点表 . 然后就是数据块了.
超级块
超级块(super block)作为文件系统的元数据, 存储基本信息. 比如
魔法数字(magic number), 识别文件系统类型;
inode节点数量, 决定最多能存多少文件;
超级块还用来管理数据空间大小
inode分配信息
这是一个位图, 1表示对应的节点使用了, 0表示没使用.
inode分配信息要占inode节点数量/8位.
inode节点表
相当于一个数组, 存储inode.
块分配信息
也是用位图来标识对应的节点是否使用. 1表示使用.
块分配信息要占数据块数量/8位.
代价
对于一个硬盘, 基于inode的文件系统注定是要浪费一部分空间的, 要用来存储块分配信息, inode分配信息和inode节点表等.
不过这样灵活高效, 牺牲这点东西是值得的.
针对不同大小的硬盘, 每次格式化这些东西都会布局好.
基于表的文件系统
FAT
windows从1985年一直用到现在.
FAT的存储布局大概是, 有一个引导记录, FAT1和FAT2(备份), 跟文件夹和数据区.
引导记录区跟超级块类似.
数据基本块的单位是簇, 与inode中的块相同.
FAT是簇序号的数组, 表示下一个簇. 文件由簇的链表形式组织.
文件的链表组织形式
目录项里存有文件名和起始簇号. 读取一个文件时, 会根据起始簇号, 找到对应的数据簇. 这个数据簇在FAT表有一个对应的表项, 存储着下一个簇号. 文件系统拿到这个簇号, 只要不是FFFF就读这个簇号对应的数据簇, 一直这样下去.
优缺点
实现简单, 应用在多种设备中
链表容易断, 因此又创建了一模一样的FAT2当备份
需要遍历链表, 文件越大, 读写越慢
FAT32中, 文件大小元数据用32位整数表示, 因此文件最大4G
exFAT
U盘常用格式, 与FAT32并不兼容.
使用位图加快空间分配
Unicode保存长文件名
目录查找文件时使用哈希比对
允许4GB以上的文件(文件大小用8字节)
使用校验码保证元数据完整
NTFS
New Technology File System
是现代windows系统中广泛使用的文件系统, 核心结构主文件表Master File Table, MFT
使用文件系统
在命令行
在Linux中有两个叫硬链接和软链接的东西
硬链接
硬链接就是让一个inode有多个文件名. 每个文件名对应的都是同一个inode. 通过ln创建
ln original_file hard_link_file
inode会维护一个计数器, 叫链接计数/引用计数.
当硬链接被创建时, 链接计数++; 当硬链接被删除时, 链接计数--;
当链接计数为0时, inode被删除, 文件数据块被释放.
因此删除其中一个硬链接并不会删除对应的inode, 而是删除所有的硬链接才会删除对应的inode. 访问速度快.
软链接/符号链接
软链接是一个特殊的文件, 文件内容是另一个文件/目录的路径. 类似快捷方式.
软链接有自己的inode, 跟硬链接不一样. 当访问软链接时, 会进行路径解析, 实际访问的是该路径的文件.
如果目标路径变了或者目标路径对应的文件被删了, 那软链接也就失效了.
在代码
基本操作
代码就略了.
内存映射
- 页缓存和脏页
为了提高读写效率, 操作系统会设置一个文件缓存. 每一次read/write不会都去读写硬盘, 数据也没有直接写到硬盘里, 而是都在内存里执行.
使用fsync可以立刻同步内存和硬盘的文件, 强制将缓存内容写入硬盘中.
当程序写入时, 会将对应的页设置为脏页, 表示被修改了. 写回后设置成干净页.
读取文件时, 可以将文件一口气读到内存里. mmap可将文件映射到虚拟内存空间中.
调用mmap时会分配虚拟地址, 标记此段虚拟地址与该文件的inode编号.
访问mmap返回的虚拟地址时, 会触发缺页异常.
操作系统接收到这个异常, 会通过虚拟地址找到对应的inode, 然后读取数据到内存里.
这样可以减少系统调用次数, 减少数据copy, 访问的局限性更好, 如果是随机访问, 不用频繁lseek.
可以使用madvice为内核提供访问提示, 提高性能.
文件系统的高级功能
文件复制
正常的文件复制我们需要
打开文件A,
创建并打开文件B,
从文件A读出数据到buffer
将buffer中的数据写入B
重复3, 4直到文件A被读完
基于内存映射的文件复制
打开文件A
获取A的大小x
创建并打开文件B
改变B的大小为x
将A和B分别mmap到内存空间
memcpy
基于文件系统的文件复制, 只将文件A的关键元数据复制过去, 其他部分通过写时拷贝机制完成.
快照
同样使用写时拷贝.
对于基于inode表的文件系统, 将inode表拷贝一份作为快照保存, 标记已用数据区为写时拷贝.
对于树状结构的文件系统
将树根拷贝一份作为快照保存, 树根以下节点标记为写时拷贝
虚拟文件系统
每种文件系统都有类似的东西: 超级块, 根目录, 树状结构.
Linux VFS(Virtual File System)
Linux的VFS可以统一挂载其它文件系统. 它会定义一系列接口, 这些接口由具体的文件系统来实现.
比如需要读取某个inode的文件, VFS会先找到该inode所属的文件系统, 然后调用该文件系统的读取接口.
WSL就是通过这个来挂载c盘和d盘什么的.
用户态文件系统
为什么需要?
可以快速试验文件系统新设计, 大量第三方库可以调用, 方便调试, 并且不用担心把内核弄崩溃, 更快地实现新功能
FUSE用户态文件系统框架
基本流程:
FUSE文件系统向FUSE驱动注册(挂载)
应用程序发起文件请求
根据挂载点, VFS将请求转发给FUSE驱动
FUSE去掉通过中断, 共享内存等方式将请求发给FUSE文件系统
FUSE文件系统处理请求
FUSE文件系统将请求结果发给FUSE驱动
FUSE驱动通过VFS返回结果给应用程序