!!!!!
看再多的博客都不如去读一下原始的手册,这里放一个FAT的手册地址,十分感谢南京大学蒋炎岩老师的分享!
http://jyywiki.cn/pages/OS/manuals/MSFAT-spec.pdf
首先我们来看不同的操作系统有哪些文件系统:
Windows阵营:
- FAT12/FAT16/FAT32
- exFAT
- NTFS
Linux阵营:
- ext2/ext3
- NFS
- jffs2
- yaffs
本次,我们重点关注,FAT32,它的代码量比较少,而且十分经典,它一般可以支持到8G的存储空间(甚至可以到64G),很适合现在的小型嵌入式系统,可以说,经典!好用!
簇——文件存储的最小单元
磁盘中最小的扇区大小是512B,我们可以想到,不同大小的磁盘,它的管理办法应该是不一样的,这时引入一个簇的概念,它就是文件系统管理的最小单元,显而易见,簇应该是扇区大小的倍数。
ps:哪怕文件里只有一个字节,那它也会占用最少一个簇的单元
100KB空间如何管理文件
分配方案
目录分配方案
当文件需要删除/增加
FAT32 FAT表
添加FAT表
重点就是这个文件分配表
FAT表格式
FF就是文件的结束簇
FAT表解决增删改问题
重点看这里:
通过文件分配表,可以实现文件的一个链式结构,很方便实现增删改查!合理的利用碎片化的空间。
如何实现一个FAT系统呢,请看下文。
文件系统的实现
其实实现一个文件系统的核心就是:使用bread()和bwrite()来实现文件系统的API (block read and write)
说到底,这就是一个数据结构!!!!
OK,原理差不多了,进入正题!
File Alloct Table
最终采取第二种方案。
为了防止关键数据发生损坏,怎么办? 备份
备份的是 FAT表 容易损坏的也是 FAT表 FAT表里存放的就是next指针,一个FAT就是一个next的数组
coding过程
我的建议是照抄手册,手册的内容就不放了,直接上代码:
Fat32.h
1 |
|
Fat32tree.cpp
1 |
|
FAT32文件存储原理
FAT是什么? File Alloct Table
FAT32 是 Windwos 系统硬盘格式分区的一种。这种格式采用 32 位的文件分配表,使其对磁盘的管理能力大大增强,突破了 FAT16 对配一个分区的容量只有2GB 的限制。虽然目前已被更优异的 NTFS 分区格式所取代。其实说白了就是 FAT表的每一项长度都是 32 位,所以叫做 FAT32。
FAT32文件系统布局(保留扇区常为31)
FAT32 文件系统将逻辑盘的空间划分为三部分,依次是引导区 (BOOT 区)、文件分配表区(FAT 区)、数据区(DATA 区)。引导区和文件分配表区又合称为系统区。
首先是引导扇区,及操作系统DBR,其实就是启动DOS操作系统的地方,这部分其实是微软在开发FAT文件系统的时放在这里的,我们只研究文件系统的话,不需要了解这部分的内容,重点是后面的部分。
DBR 及保留扇区:DBR 的含义是 DOS 引导记录,也称为操作系统引导记录,在DBR 之后往往会有一些保留扇区。
DBR 分为两个部分:
- DOS 引导程:DOS 引导程序的主要任务是当 MBR 将系统控制权交给它时,判断本分区根目录前两个文件是不是操作系统的引导文件(即IO.SYS 和 MSDOS.SYS),如果确定存在,就把它读入内存,并把控制权交给它。
- BPB(BIOS Parameter Block ,BIOS 参数块 BPB 用来描述本 DOS 分区的磁盘信息,它位于 DBR 偏移 0BH 处,共 13 字节。它的记录包括本分区的起始扇区、结束扇区、文件存储格式、硬盘介质描述符、根目录大小、FAT 个数,分配单元的大小等重要参数。
FAT1:FAT 的含义是文件分配表,FAT32 一般有两份 FAT,FAT1 是第一份,也是主 FAT。
FAT2:FAT2 是 FAT32 的第二份文件分配表,也是 FAT1 的备份。
DATA:DATA 也就是数据区,是 FAT32 文件系统的主要区域,其中包含目录区域。
实际文件其实是放在数据区的,数据区中有根目录,可以进行索引。
索引之后,这个文件就放在数据区,那我们怎么知道它具体放在磁盘的哪个位置呢?其实整个数据区的大小,实际物理地址,FAT1|FAT2的物理地址和大小,都是在操作系统DBR中进行读取的,读取整个文件系统的分配表,来交给软件,由此可以定位到根目录的起始地址(注意,此时定位不到具体一个文件在哪里)。
MBR分区策略
MBR:主引导记录。之所以叫“主引导记录” ,是因为它是存在于驱动器开始部分的一个特殊的启动扇区。这个扇区包含了驱动器的分区信息(64 个字节,大小固定,一个分区用 16 个字节记录)和已安装的操作系统的启动加载器(446 字节)和 2 个字节的结束标志,所以这个扇区的大小是 512 个字节。所谓启动加载器,是一小段代码,用于加载驱动器上其他分区上更大的加载器。如果你安装了 Windows,Windows 启动加载器的初始信息就放在这个区域里——如果 MBR 的信息被覆盖导致 Windows 不能启动,你就需要使用 Windows 的 MBR 修复功能来使其恢复正常。如果你安装了 Linux,则位于 MBR 里的通常会是 GRUB 加载器。
MBR 支持最大 2TB 磁盘,它无法处理大于 2TB 容量的磁盘。 MBR 还只支持最多 4 个主分区——如果你想要更多分区,你需要创建所谓“扩展分区” ,并在其中创建逻辑分区。
MBR 下的硬盘分区有三种,主磁盘分区、扩展磁盘分区、逻辑分区。
主分区:也叫引导分区,最多可能创建 4 个,当创建四个主分区时候,就无法再创建扩展分区了,当然也就没有逻辑分区了。主分区是独立的,对应磁盘上的第一个分区,“一般”就是 C 盘。在 Windows 系统把所有的主分区和逻辑分区都叫做“盘”或者“驱动器”,并且把所有的可存储介质都显示为操作系统的“盘”。因此,从“盘”的概念上无法区分主分区和逻辑分区。并且盘符可以在操作系统中修改,这就是要加上“一般”二字的原因。
扩展分区:除了主分区外,剩余的磁盘空间就是扩展分区了,扩展分区可以没有,最多 1 个。严格地讲它不是一个实际意义的分区,它仅仅是一个指向下一个分区的指针,这种指针结构将形成一个单向链表。这样在主引导扇区中除了主分区外,仅需要存储一个被称为扩展分区的分区数据,通过这个扩展分区的数据可以找到下一个分区(实际上也就是下一个逻辑磁盘)的起始位置,以此起始位置类推可以找到所有的分区。无论系统中建立多少个逻辑磁盘,在主引导扇区中通过一个扩展分区的参数就可以逐个找到每一个逻辑磁盘。
逻辑分区:在扩展分区上面,可以创建多个逻辑分区。逻辑分区相当于一块存储截止,和操作系统还有别的逻辑分区、主分区没有什么关系,是“独立的”。
BPB 及保留扇区
BPB(BIOS Parameter Block)是 FAT 文件系统中第一个重要的数据结构,它位于该 FAT 分区的第一个扇区,同时也属于 FAT 文件系统基本区域的保留区。这个扇区又叫做“启动扇区” 、 “保留扇区”、“0 扇区”,众多的叫法都说明一个相同的问题:该扇区是 FAT 分区的第一个扇区。
FAT 数据结构
文件系统分配磁盘空间按簇来分配的。因此,文件占用磁盘空间时,基本单位不是字节而是簇,即使某个文件只有一个字节,操作系统也会给他分配一个最小单元——即一个簇。
为了可以将磁盘空间有序地分配给相应的文件,而读取文件的时候又可以从相应的地址读出文件,我们把数据区空间分成 BPB_BytsPerSec * BPB_SecPerClus 字节长的簇来管理, FAT 表项的大小与 FAT 类型有关,FAT12的 表项为 12-bit,FAT16 为 16-bit,而 FAT32 则为 32-bit。对于大文件,需要分配多个簇。同一个文件的数据并不一定完整地存放在磁盘中一个连续的区域内,而往往会分成若干段,像链子一样存放。这种存储方式称为文件的链式存储。为了实现文件的链式存储,文件系统必须准确地记录哪些簇已经被文件占用,还必须为每个已经占用的簇指明存储后继内容的下一个簇的簇号,对文件的最后一簇,则要指明本簇无后继簇。这些都是由 FAT 表来保存的,FAT 表的对应表项中记录着它所代表的簇的有关信息:诸如是否空,是否是坏簇,是否已经是某个文件的尾簇等。
FAT32 文件表是由一个个表项组成的一张表,其中每一个表项由一个 32 位的二进制组成,其值对应了相应簇的使用情况,如 2 号表项对应了 2 号簇的使用情况,3 号表项对应了 3 号簇的使用情况,依此类推。(但是第 0 和第 1 项例外,下面会有说明)。每个表项对应数值的含义如表下图 所示:
首先 FAT 表一般来说有两张,另一张用于备份。两张表是前后紧挨在一起的,只要计算出了 FAT1 表的偏移之后加上 FAT 表的大小就可以得到 FAT2 表的偏移。FAT1 表的偏移地址计算公式如下 :
1 | FAT1 表偏移 = 保留扇区数 * 每扇区字节数 |
同理:
1 | FAT2 表的偏移 = FAT1+FAT 表的大小 = (保留扇区数 + FAT 表扇区数) * 每扇区字节数 |
若读取了第一张 FAT 表起始部分的内容,如图所示:
FAT 目录和普通的文件并没有什么不一样的地方,只是多了一个表示它是目录的属性,另外就是目录所链接的内容是一个 32 字节的目录项。除此之外,目录和文件没什么区别。 FAT 表是根据簇数和文件对应的。第一个存放数据的簇是簇 2。簇 2 的第一个扇区(磁盘的数据区)根据 BPB 来计算,首先计算根目录所占的扇区数:
1 | RootDirSectors = ((BPBRootEntCnt ∗32) + (BPBBytsPerSec–1))/BPBBytsPerSec |
因为 FAT32 的 BPB_RootEntCnt 为 0, 所以对于 FAT32 卷RootDirSectors 的值也一定是 0。上式中的 32 是每个目录项所占的字节数。计算结果四舍五入。如何计算总的数据区簇数呢?
1 | DataSec = BPB_TotSec32 – (BPB_RsvdSecCnt + (BPB_NumFATs * BPB_FATSz) + RootDirSectors) |
包含 EOC 标记的簇属于当前文件并且是当前文件的最后一个簇。Microsoft的操作系统设置 EOC 标记时,FAT32 使用 0x0FFFFFFF,但有一些运行于Microsoft 系统的工具并不使用这个值。还有一个特殊的标记就是“坏簇(BATCLUSTER)”标记,任何包含“坏簇”标记的簇都不应该被列到剩余簇的范畴内,这个“坏簇”标记对 FAT12 是 0x0FF7, FAT16 是 0xFFF7, FAT32 是 0x0FFFFFF7。另外,这些坏簇看起来也像是丢失的簇——们似乎已经分配出去,因为它们的值不为 0,但同时它们又不属于任何文件。磁盘修复程序一定要认出这些被标记坏簇标记的“丢失簇”,并且不去修改它们的内容。
FAT 表中剩余簇的列表就是卷中所有内容为 0 的簇的列表。这些数据必须尽早取得并记录下来以表示剩余的簇是已经被使用的。这个列表并没有存储在卷中的任何一个地方,他必须在系统挂上(mount)该卷时由 FAT 扫描程序获得内容为 0 的簇的列表。在 FAT 卷起始部分的两个保留扇区到底是做什么用的呢?
第一个保留簇 FAT[0],它的低位 8-bit 为 BPB_Media,剩余的位用 1 填充,比如:BPB_Media 的内容为 0xF8,那么 FAT32 的内容为 0x0FFFFFF8。
第二个保留簇 FAT[1],在格式化的时候被填充 EOC 标记。FAT16 和 FAT32 此域的高 2-bit 可以被用于标记磁盘是否为“脏”,剩余的位均用 1 填充。
FAT 表的结束扇区不一定就是 FAT 表的最后一个扇区,FAT 表的结尾扇区位于簇号为 CountofClusters + 1 的簇中。这个扇区未必在 FAT 表的最后面。 FAT 程序不应该尝试着去访问 CountofClusters +1 以后的簇. FAT 格式化程序应该把这个簇号后面的所有簇用 0 填充。BPB_FATSz32 的值会比它实际需要的大,也就是说,在 FAT 表 中可能有部分扇区没有被使用。因此,FAT 表的结束扇区都是由CountofClusters + 1 来 计算得到,而不是使用 BPB_FATSz16/32 来计算。FAT 程序不因该尝试去访问这些“额外”的扇区。FAT 格式化程序因该把这些扇区用 0 来填充。
FAT 目录结构
FAT 目录其实就是一个由 32-bytes 的线性表构成的“文件”。
根目录(rootdirectory)是一个特殊的目录,它存在于每一个 FAT 分区中。对于 FAT12/16,根目录存储在磁盘中固定的地方,它紧跟在最后一个 FAT 表后。根目录的扇区数也是固定的,可以根据 BPB_RootEntCnt 计算得出对于 FAT12/16,根目录的扇区号是相对该 FAT 卷第一个扇区(0 扇区)的偏移量。
1 | FirstRootDirSecNum =BPB_RsvdSecCnt + (BPB_NumFATs * BPB_FATSz16) |
FAT32 的根目录由簇链组成,其扇区数不确定,这点和普通的文件相同,根目录的第一个扇区号存储在 BPB_RootClus 中,根目录不同于其他的目录,没有日期和时间戳,也没有目录名,同时根目录里没有 “.” 和 “..”这两个目录项,根目录另一个特殊的地方在于,根目录中有一个设置了 ATTR_VOLUME_ID 位的文件,这个文件在整个 FAT 卷中是唯一的。
当磁盘分区被格式化为 FAT32 文件系统后,FAT 表仅前三项被初始赋值,剩余表项全部为 0。
在文件创建期间,FAT32 文件系统会首先为文件创建目录项结构,并将其保存在目录的数据区内。只有向文件写入数据时,文件系统才为其分配可用簇号;而在创建子目录时,由于子目录始终不空(含有代表当前目录和父目录的两个目录项),所以会为子目录分配簇号。
对于文件和目录的创建方式,Windows 和 linux 略有不同。Linux文件系统再创建目录项时,除非文件名全部为大写字母,否则一律用段目录项加长目录项的方式为文件创建目录项。而 windows 除了具备 Linux 的功能外,还会考虑文件名的长度与大小写,当文件名满足短目录项格式时,如果基础名或拓展名名中含有的字母同为大写或者小写,则可以使用目录项结构中 DIR_NTRes成员变量加以标识,从而省去长目录项。
1 | 00h:扩展名大写,基础名大写 |
短目录项
目录区是由一个个目录项构成,类似于 FAT 表。其中每一个目录项占用 32个字节,可以是代表长文件名目录项、文件目录项、子目录项等。对于短文件名格式的目录项,其参数的含义如下表所示。
长目录项
当创建一个长文件名文件时,系统会自动加上对应的短文件名,其原则如下:
- 1、取长文件名的前 6 个字符加上”~1”形成短文件名,扩展名不变。
- 2、如果已存在这个文件名,则符号”~”后的数字递增,直到 5。
那么系统是如何判断当前目录项是短文件名目录项呢还是长文件名目录项,这里关键是看目录项的第 12 个字节的值,如果为 0x0F 时则系统认为长文件名目录项的参数,如表所示:
Linux 的虚拟文件系统-VFS
Linux 中允许众多不同的文件系统共存,如 ext2, ext3, vfat 等。通过使用同一套文件 I/O 系统 调用即可对 Linux 中的任意文件进行操作而无需考虑其所在的具体文件系统格式;更进一步,对文件的 操作可以跨文件系统而执行。
“一切皆是文件”是 Unix/Linux 的基本哲学之一。不仅普通的文件,目录、字符设备、块设备、 套接字等在 Unix/Linux 中都是以文件被对待;它们虽然类型不同,但是对其提供的却是同一套操作界面。
而虚拟文件系统正是实现上述两点 Linux 特性的关键所在。
虚拟文件系统(Virtual File System, 简称 VFS), 是 Linux 内核中的一个软件层,用于给用户空间的程序提供文件系统接口;同时,它也提供了内核中的一个 抽象功能,允许不同的文件系统共存。系统中所有的文件系统不但依赖 VFS 共存,而且也依靠 VFS 协同工作。
Linux 内核要求文件系统必须是实体,它还必须在持久对象上实现 open()、read() 和 write() 方法,并且这些实体需要有与之关联的名字。从面向对象编程的角度来看,内核将通用文件系统视为一个抽象接口,这三大函数是“虚拟”的,没有默认定义。因此,内核的默认文件系统实现被称为虚拟文件系统。
VFS 内核中与其他的内核模块的协同关系如下:
为了能够支持各种实际文件系统,VFS 定义了所有文件系统都支持的基本的、概念上的接口和数据结构;同时实际文件系统也提供 VFS 所期望的抽象接口和数据结构,将自身的诸如文件、目录等概念在形式上与 VFS 的定义保持一致。
换句话说,一个实际的文件系统想要被 Linux 支持,就必须提供一个符合 VFS 标准的接口,才能与 VFS 协同工作。实际文件系统在统一的接口和数据结构下隐藏了具体的实现细节,所以在 VFS 层和内核的其他部分看来,所有文件系统都是相同的。
VFS 的数据结构
VFS 本身只存在内存中,他需要将硬盘上的文件系统抽象到内存中,这个工作是通过几个重要的设局结构实现的,通过这些数据结构将一个真实的磁盘(或其它设备)文件系统抽象到了内存,通过管理几个对象就可以完成对文件系统的操作。
超级块
超级块对象,挂载点对象及文件系统类型对象间的联系如下:
超级块对象代表已挂载的文件系统本身,存储一个已挂载的文件系统的控制信息,代表一个已安装的文件系统;每次一个实际的文件系统被安装时,内核会从磁盘的特定位置读取一些控制信息来填充内存中的超级块对象。一个挂载实例和一个超级块对象一一对应。 超级块通过其结构中的一个域 s_type 记录它所属的文件系统类型。
dentry
VFS 实现 open、stat、chmod 等类似的文件系统调用,他们传递一个pathname 参数给 VFS。VFS 根据文件路径 pathname 搜索 directory entry cache(dentry cache 或者 dcache)获取对应的 dentry。所以 dcache 是一个高速目录项缓存,用于映射文件路径和 dentry。
dentry 结构用于优化查询性能,只存在于内存中,不实际存储到磁盘。由于内存限制,并不是所有 dentry 都能在缓存命中,当根据 pathname 找不到对应 dentry 时,VFS 调用 lookup 接口向底层文件系统查找获取 inode 信息,以此建立 dentry 和其对应的 inode 结构。
Inode
每个 dentry 通常对应一个 inode 结构用于描述文件、目录等的基本元数据信息。如果底层是磁盘存储,Inode 结构会保存到磁盘。当需要时从磁盘读取到内存中进行缓存。一个 inode 结构可以被多个 dentry 指向,如硬链接。对于网络文件系统(分布式文件系统)Inode 结构需要通过网络协议获取到缓存中。
VFS 通过父目录的 lookup 方法来获取某个文件的 inode 信息,该方法由底层文件系统实现。一旦获取了 inode 信息,open、stat 等无聊的操作直接从缓存里进行,变得很快。
索引节点代表一个文件,保存着文件的参数、信息,一个真实的文件只能由一个索引节点,且目录项和索引节点分别代表了文件的两个部分。
进程与超级块、目录项、索引节点、文件对象间的联系如下:
File
Open 一个文件还需要另外一个数据结构:File。
File 用于表示一个处于Open 状态的文件,同一个文件被 Open 多次对应不同的 File 结构。应用程序打开文件后对应一个句柄,每个 FD 都对应到内核的一个 File 结构,因此 File结构直接存放在进程的 FD 表里,通过 FD 可以快速获取到 File 数据结构。VFS实现用户态文件读写关闭操作时,通过用户态的 FD 来获取对应的 File 结构,然后调用对应的底层文件系统方法。只要有 File 结构正在使用,就增加 dentry的引用计数,保证 dentry 和 inode 结构没有从缓存里删除。
课设的实现步骤(代码详解)
OK,现在我们的先验知识已经足够了,开始我们的课设实现部分,首先先看一下我们要做什么:
同时,老师要求我们要区分内核文件和演示界面,那么我们就先写内核文件的部分。
我这里给出一个整体的对象图:
系统仿造 VFS 构建文件系统的基本结构,并在适当的地方做了简化。该系统底层完全实现了对 FAT32 文件系统的读写等基本操作,并与 Windows 兼容,即可以读写 Windows 系统格式化的 FAT32 文件系统卷,并且读写结果能被Windows 系统所识别。
内核文件的实现 FAT32
内核文件就是一个完整的FAT32系统,同时我们要留出可以调用的接口,以供给界面使用。
还是和前面一样,先照抄文档,把基本的FAT文件系统搭建起来。
根据文档中的内容,我们可以得出要定义哪些东西。
这里我们可以看到整个FAT文件系统,翻译一下就是下面这个样子:
引导扇区与BPB
还是先看文档:
照抄文档,代码如下:
1 | struct FAT32_BootSector |
FAT类型(FAT12、FAT16或FAT32)的确定取决于扇区的数量,对于 FAT32卷,每个 FAT表项的长度为32位
这里扩展一些知识,如何确定FAT的类型:
如何确定FAT的类型
首先,确定根目录多占用的扇区数:
1 | RootDirSectors = ((BPB_RootEntCnt * 32) + (BPB_BytsPerSec – 1)) / BPB_BytsPerSec |
注意在FAT32卷上,BPB_RootEntCnt的值总是0。因此,在FAT32卷上,RootDirSectors总是0。
接下来,确定卷的数据区域的扇区数:
1 | If(BPB_FATSz16 != 0) |
最后,确定扇区的数量为: 计算结构四舍五入
1 | CountofClusters = DataSec / BPB_SecPerClus; |
为了确定FAT类型,使用以下算法:
1 | If(CountofClusters < 4085) |
上述算法确定FAT类型,请注意以下内容:
- FAT12卷不能包含超过4084个簇。
- FAT16卷不能包含少于4085个簇或多于65524个簇。
FAT
文件分配表(FAT)中的每个有效条目表示来自包括根目录区域(当适用时)以及文件和目录数据区域的集群集合的集群的状态。FAT中每个(压缩)条目的大小如下:
- 对于FAT12卷,每个FAT条目长度为12位
- 对于FAT16卷,每个FAT条目长度为16位
- 对于FAT32卷,每个FAT条目长度为32位
如前所述,FAT可以大于描述包括根/数据区域的所有可分配扇区所需的FAT。FAT中的额外条目(位于FAT的尾部/末尾)必须设置为0值。
FAT条目值必须按下表所述写入:
文件系统信息(FSInfo)结构
1 |
|
目录结构
FAT目录是一种特殊的文件类型。该目录充当其他文件和子目录的容器。目录内容(数据)是一系列32字节的目录条目。每个目录条目又通常表示所包含的文件或子目录目录。
下表描述了包括每个目录条目的字段:
Legal文件属性类型定义如下:
文件/目录属性
1 |
1 |
|
文件/目录大小
最大文件大小为0xFFFFFFFF字节。分配给文件的任何簇链必须<= 0x 100000000字节。这样的簇链中的最后一个字节不能是文件的一部分。最大有效目录大小为221字节。
长文件名实现
目录条目中的DIR_Name字段仅允许11个字符的文件/子目录名,由主要部分(最大长度为8个字符)和扩展名(最大长度为3个字符)组成。此字段的内容也称为“短名称”,相应的目录条目也称为短名称目录条目。应用程序和用户通常喜欢为文件/子目录创建更长(更具描述性)的名称。本节介绍如何将此类长文件名存储在介质上。
目标文件或子目录的长文件名被存储在与描述目标文件或子目录的短名称目录条目相关联的一组(一个或多个)附加目录条目中。这组附加目录条目(也称为长名称目录条目)必须紧接在对应的短名称目录条目之前,并且因此在物理上与短名称目录条目连续。
下表描述了长名称目录条目结构:
下表描述了长名称目录条目结构:下面说明了名称目录条目的存储长度:
1 |
|
OK,到这里为止,微软提供的FAT文档中的内容就已经全部写完了,但是这样就可以直接用吗?你的接口呢?你怎么访问磁盘?你的交互逻辑呢?光靠这一堆结构体能干嘛?
所以,为了使这个FAT32可以在电脑上运行,我们有必要把它继续封装成一个虚拟文件系统,这也是上面为什么要介绍虚拟文件系统,那么上面提到了,VFS比不可少的四个结构:超级块、Dentry、Inode、File,那么我们接下来就来定义他们吧。
超级块
1 | //4、超级块中 FAt32 特有数据(私有信息) |
Inode
1 | //5、用于描述FAT32文件或目录的索引节点信息 |
OK,到这里为止,我们整体的FAT32内核就编写完了,但是,我还需要构建一套完整的VFS来搭载它,下面就来构造VFS。
内核文件的实现 VFS
构造一个完整的VFS需要哪些东西呢?
- 磁盘分区表
- 磁盘主引导记录
- 文件系统类型
- 超级块 (代表文件系统本身)
- Dentry(目录项)
- Index_Node(索引结点)
- File
看起来很多,其实一步一步写,思路是很清晰的。
强烈推荐官方的Linux内核文档,其中有官方发布的VFS文档,Documentation/filesystems/vfs.txt
这里我放一个下载官方文档的地址,下面的代码也是基于官方文档开发的。
https://www.kernel.org/doc/html/latest/filesystems/vfs.html
1 | https://www.kernel.org/doc/html/latest/filesystems/vfs.html |
磁盘分区表
1 | struct Disk_Partition_Table_Entry//磁盘分区表 |
磁盘主引导记录
1 | struct Disk_Partition_Table//磁盘主引导记录 |
文件系统类型
官方文档的示例如下:
我们这里做一下精简,只把我们要用到的进行保留:
1 | struct file_system_type |
超级块 (代表文件系统本身)
官网文档的示例如下:
我们这里做一下精简,只把我们要用到的进行保留:
1 | struct super_block//超级块 代表文件系统本身 |
Dentry(目录项)
官网文档的示例如下:
我们这里做一下精简,只把我们要用到的进行保留:
1 | struct dir_entry |
Index_Node(索引结点)
官网文档的示例如下:
我们这里做一下精简,只把我们要用到的进行保留:
1 | struct index_node |
File
官网文档的示例如下:
我们这里做一下精简,只把我们要用到的进行保留:
1 | struct file |
此外,我还需要定义一下函数:
其他功能函数
这部分在官方文档里也有详细的阐述:
1 | unsigned long register_filesystem(struct file_system_type* fs)//注册文件系统//参数:文件系统类型指针 |
1 | unsigned long unregister_filesystem(struct file_system_type* fs)//卸载文件系统 |
1 | //将指定名称的文件系统挂载到当前操作系统中 |
FAT32文件系统API的实现
首先我们来看看有哪些API吧:
1 | //fat32 |
每一个API对应一个操作,那么就下来我们就对操作进行分类,形成不同的操作集:
操作集
超级块操作集
1 | struct super_block_operations FAT32_sb_ops = //超级块操作集 |
iNode节点操作集
1 | struct index_node_operations FAT32_inode_ops = //iNode节点操作集 |
文件操作集
1 | struct file_operations FAT32_file_ops = //文件操作集 |
目录操作集
1 | struct dir_entry_operations FAT32_dentry_ops = //目录操作集 |
OK!接下来就挨个实现功能吧!
磁盘读写
前面也提到了,文件系统的实现过程最基本的来说就是通过bread()和 bwrite()来实现文件系统的各个API,那么下面就先说这两个函数:
磁盘读写调用 Windows 系统 API,通过磁盘物理标识符,直接对物理设备进行读写,通过锁定卷标,实现了对 FAT32 保留扇区及 FAT 表区的写操作。虽然用的是设备无关 I/O 接口,但是为了更好的模拟对磁盘的读写,所以将磁盘访问接口设计为一次读写盘块倍数大小(512B)。
读磁盘的流程图如下:
最基本的两个函数:
1 | DWORD ReadDisk(unsigned char*& out, DWORD start, unsigned long long size); //start 单位:扇区、 size 单位:扇区 调用者不用申请动态内存,但调用者需要释放;//一扇区512字节 |
接下来定义每一个函数:
磁盘初始化函数:
1 | int init_disk(std::wstring device_name) { |
读磁盘函数:
1 | // 读出磁盘扇区 ReadDisk(用于保存读取结果的缓冲区指针,要读取扇区的起始位置,要读取的扇区数量) |
写磁盘函数:
1 | DWORD WriteDisk(unsigned char* inbyte, DWORD start, unsigned long long size) |
退出磁盘函数:
1 | int quit_disk() { |
文件系统挂载
此系统仿照 VFS,先注册 FAT32 类型的文件系统,在挂载时解析磁盘主引导记录与 FAT32 的 BPB 扇区及保留扇区,初始化超级块 root_sb 与根目录索引节点。
文件系统挂载与卸载流程图如下:
首先调用 struct super_block fat32_read_superblock(structDisk_Partition_Table_Entry DPTE, void* buf)函数,进行超级块的初始化**,然后才能进行进行文件系统 FAT32 的挂载,每一个超级块代表一个文件系统,文件挂载时初始化超级块信息,超级块对象代表已挂载的文件系统本身,存储一个已挂载的文件系统的控制信息,代表一个已安装的文件系统;每次一个实际的文件系统被安装时,内核会从磁盘的特定位置读取一些控制信息来填充内存中的超级块对象。
1 | /*实现读取FAT32文件系统的超级块,并初始化根目录的目录项和索引节点。①申请一块内存用于存储超级块,初始化为0 |
文件打开
该系统简化 VFS,未引入并发控制机制。系统维护全局变量:
- fileptr current_file[MAX_OPENING_FILE];
- fileHandle filehand_vector[MAX_OPENING_FILE];
文件打开流程图如下:
首先输入需要打开文件的路径和模式。用上面这两个数组来储存当前打开的文件,以及文件的路径与属性。文件的打开操作,有三种打开模式。Write,Read和 read_write,其中写模式分覆盖写 trunc 和追加写 append 两种。打开文件时,赋予文件相应的句柄。且在读的过程中,通过 path_walk 函数获得此文件目录项,由此目录项初始化文件。 打开成功的话,则返回文件的句柄。
文件关闭
选择需要关闭文件对应的句柄,调用 unsigned long close(int fd)函数,释放出入句柄所对应的文件对象,将此句柄对应的当前打开文件关闭。 成功的话,则返回 0。
文件读写
想通过句柄调用用户级接口,在调用类 VFS 抽象读写接口,再调用 FAT32接口进行读写。读写文件的操作主要依赖于 fat32 系统下定义的函数,用 file结构指向 long FAT32_write(struct file filp, char buf, unsigned longcount, long* position)函数**来完成写操作。
1 | long FAT32_read(struct file* filp, char* buf, unsigned long count, long* position)//buf 为读入缓冲 |
1 | long FAT32_write(struct file* filp, char* buf, unsigned long count, long* position) |
文件 lseek 操作
Lseek 操作是基于读文件操作的。即可以从任意位置开始读取文的内容,而不是向 read 操作那样,从开始读到结尾。每个文件对象中,包含文件读或者写到的当前位置的索引,通过 lseek 操作,可以改变文件当前位置索引的值。通过偏移地址参数和源标记参数,可以获得改变当前文件已读到的位置。Lseek 操作同样的 file 操作下的函数,成功读取的话,则返回开始读偏移的位置。
1 | long FAT32_lseek(struct file* filp, long offset, long origin) |
测试
运行程序后,手动输入,按下回车,展示磁盘分区表信息
挂载分区,显示分区信息
输入 mount,根据提示输入需要挂载的分区号,展示分区信息
读文件
Seek操作
关文件
追加写
覆盖写
长文件名操作
展示文件创建时间和写文件时间
- 本文作者: 李宝璐
- 本文链接: https://libaolu312.github.io/2023/06/12/OS课设FAT32U盘文件系统/
- 版权声明: 本博客所有文章除特别声明外,均采用 MIT 许可协议。转载请注明出处!