FAT12文件系统C语言模拟实现及原理讲解

这篇具有很好参考价值的文章主要介绍了FAT12文件系统C语言模拟实现及原理讲解。希望对大家有所帮助。如果存在错误或未考虑完全的地方,请大家不吝赐教,您也可以点击"举报违法"按钮提交疑问。

FAT12-File-System

一、概述

作者:yhy
时间:2022.8.22
项目地址:https://github.com/HuiyuanYan/FAT12-File-System.git
项目简介:用C语言实现的简易FAT12文件系统,并写了简单的shell用于交互。
项目目的:通过简单的编码了解FAT12文件系统的构成和运行原理,并用C语言模拟文件系统的运行,同时强化C语言编码能力。
运行及编码环境:vscode + WSL:Ububntu-20.04

二、项目构成

.
├── picture #说明文档用到的图片
├── README.md 
└── src
    ├── .gitignore
    ├── FAT12.c #FAT12功能函数实现源文件
    ├── FAT12.h #FAT12结构及相关声明
    ├── Makefile #用于构建本项目所需的Makefile
    ├── doc1.txt #用于测试copy命令所需的文本
    ├── main.c #程序入口,包括初始化系统、创建根目录、进入shell等
    ├── shell.c #简易shell实现源文件
    └── shell.h #shell声明

三、项目运行展示

shell的实现只是为了能够与FAT12文件系统实现交互以证明其正确性,所以只进行了简易的实现。shell中所给的文件路径也必须是本文件夹中的文件名

通过命令:

make

生成可执行文件并运行。

这里展示自制shell的部分命令运行效果,以说明该文件系统能够正常运行。
支持的命令及用法可以通过进入shell用help查询或者直接参阅源码。

1. ls命令

ls命令只支持下面几种用法:

c语言文件系统模拟,操作系统,数据结构,C/C++,c语言,bash,linux,学习,数据结构

2. cat命令

cat命令只实现了输出文件内容到标准输出中。

c语言文件系统模拟,操作系统,数据结构,C/C++,c语言,bash,linux,学习,数据结构

3. copy命令

copy命令可以把位于本机(我这里是Windows)的文件复制到正在运行的FAT12文件系统中当前目录文件里去。

示例命令:

copy doc1.txt NJU1.md #把doc1.txt复制到NJU1.md中
cat NJU1.md

运行效果:
c语言文件系统模拟,操作系统,数据结构,C/C++,c语言,bash,linux,学习,数据结构

其中会提示No such file or directory.是因为文件系统没有在当前目录下找到NJU1.md,会去新创建该文件并执行复制过程。

4. load和save命令

为了能够保存/导出disk方便下次使用,魔改实现了这两个命令:

save xxx.bin #将disk以二进制文件形式保存到本机文件中
load xxx.bin #加载本机文件到目前运行的disk中

运行效果:
c语言文件系统模拟,操作系统,数据结构,C/C++,c语言,bash,linux,学习,数据结构

5.其它命令

可以进入shell后通过help查看支持的命令(非常有限且实现简单,但已经能够说明问题了)。

四、实现原理

下面主要介绍并说明FAT12文件系统实现的原理,部分代码会在文档中予以展示,完整代码请参照上面的项目地址。

4.1 FAT12系统原理

这部分网上已经有很多内容讲述,下面的内容主要参照搜集网上的资料和个人理解。

4.1.1 FAT12文件系统

FAT(File Allocation Table)是计算机文件系统,DOS时代就开始使用的文件系统(File System),FAT12(FAT项长度为12比特)文件系统是最古老的版本。

  • FAT的命名由来是由于它把文件表项(Entry)存储在数据卷开头的表中。

  • 为了使系统能够正确定位,FAT表和根目录项都必须存储在一个固定的位置。

  • FAT文件系统的数据都通过 (cluster) 这一基本单位来组织。

4.1.2 簇

什么是簇?

FAT表是由一个个组成(簇的数量取决于数据区的大小),每个簇是一个固定比特长度的数。对于FAT12来说,簇的长度为12位,它表示当前簇指向的下一个簇。可以把“簇”的组织形式当成一个链表:

  • 如果2号簇储存的数字为3,那么说明2号簇指向3号簇。3号簇的12位数字储存的是5的话,那么说明3号簇指向5号簇。链表的空指针NULL(结尾标志),使用0xFFF表示。
    c语言文件系统模拟,操作系统,数据结构,C/C++,c语言,bash,linux,学习,数据结构

如此,分散在各个数据区块的数据便可以通过簇有效地组织起来。

FAT表的0号簇和1号簇不能使用,他们储存的是坏簇标记0xFF0和结尾标志0xFFF。

在FAT12系统中,由于每个簇的大小是12bit,而计算机操作数据又是以字节(8bit)为单位进行的,所以在实际编码和操作过程中,我们可以每次存取两个簇:

typedef struct FAT2Entry //FAT表项,以两个簇为基本操作单位
{
    //两个簇
    int firstEntry : 12;
    int secondEntry : 12;
} __attribute__((packed)) FAT2Entry; //取消字节对齐

那么对于自定义FAT表的第i项,它实际上包含两个簇:i*2i*2+1。假设要操作第k个簇,那么可以根据k的奇偶判断它所在FAT表项中的位置以及操作哪个变量。

4.1.3 FAT12文件系统结构

FAT12系统的组成包括:

  • MBR引导区
  • FAT1、FAT2表
  • 根目录区
  • 数据区
MBR引导区

引导扇区是Windows操作系统下特有的,包含操作系统引导的作用。对于文件系统而言,这个扇区的作用其实与Linux文件系统的超级块很像,其中包含着对文件系统整体的描述信息。

它占一个扇区(512字节)且最后两个字节是0x550xAA

名称 开始字节 长度 存储数据
BS_jmpBOOT 0 3 跳转指令
BS_OEMName 3 8 厂商名
BPB_BytePerSec 11 2 每扇区字节数,512
BPB_SecPerClus 13 1 每簇扇区数,1
BPB_ResvdSecCnt 14 2 MBR占用扇区数,1
BPB_NumFATs 16 1 共有多少FAT表,2
BPB_RootEntCnt 17 2 根目录区文件最大数,224
BPB_TotSec16 19 2 扇区总数,2880
BPB_Media 21 1 介质描述符,0xF0
BPB_FATSz16 22 2 一个FAT表所占扇区数
BPB_SecPerTrk 24 2 每磁道扇区数
BPB_NumHeads 26 2 磁头数
BPB_HiddSec 28 4 隐藏扇区数,0
BPB_ToSec32 32 4 如果BPB_TotSec16=0,则这里给出扇区数
BS_DrvNum 36 1 INT 13H的驱动器号
BS_Reserved1 37 1 保留位
BS_BootSig 38 1 扩展引导标记
BS_VollD 39 4 卷序列号
BS_VolLab 43 11 卷标
BS_FileSysType 54 8 文件系统类型,‘FAT12’
汇编代码 64 448 引导记录里面的汇编代码
结束标志 510 2 两个字节,分别为0x55,0xAA

相应的我们可以定义它的结构体:

// MBR首部结构
typedef struct MBRHeader
{
    char BS_jmpBOOT[3];
    char BS_OEMName[8];
    // 每个扇区字节数 512
    uint16_t BPB_BytesPerSec;
    // 每簇扇区数 1
    uint8_t BPB_SecPerClus;
    // boot引导占扇区数 1
    uint16_t BPB_ResvdSecCnt;
    //一共有几个FAT表 2
    uint8_t BPB_NumFATs;
    //根目录文件最大数  0xe0 = 224
    uint16_t BPB_RootEntCnt;
    //扇区总数  0xb40 = 2880
    uint16_t BPB_TotSec16;
    //介质描述  0xf0
    uint8_t BPB_Media;
    //每个FAT表占扇区数 9
    uint16_t BPB_FATSz16;
    // 每个磁道占扇区数 0x12
    uint16_t BPB_SecPerTrk;
    // 磁头数   0x2
    uint16_t BPB_NumHeads;
    // 隐藏扇区数 0
    uint32_t BPB_HiddSec;
    // 如果BPB_TotSec16=0,则由这里给出扇区数 0
    uint32_t BPB_TotSec32;
    // INT 13H的驱动号 0
    uint8_t BS_DrvNum;
    //保留,未用    0
    uint8_t BS_Reserved1;
    //扩展引导标记  0x29
    uint8_t BS_BootSig;
    // 卷序列号 0
    uint32_t BS_VollD;
    // 卷标 'yxr620'
    uint8_t BS_VolLab[11];
    // 文件系统类型 'FAT12'
    uint8_t BS_FileSysType[8];
    //引导代码
    char code[448];
    //结束标志
    char end_point[2];

} __attribute__((packed)) MBRHeader;

在用C语言模拟过程中,MBR首部的引导代码等很多内容实际上我们都用不到。

FAT1、FAT2表

这两个表完全相同,FAT2表存在的目的就是为了修复FAT1表。

FAT表有9个扇区一共有3072个簇,12位的二进制数能表示的最大簇号为4096。每个簇都能被访问,需要关心的是不要让簇号越界。

FAT表的0号簇和1号簇不能使用,他们储存的是坏簇标记0xFF0和结尾标志0xFFF。

关于簇和FAT表的结构体定义在上面已经写出。

根目录区

根目录就是系统最初的文件夹。在Windows中用’‘表示,在Linux中用’/'表示。根目录中储存了文件和子目录,两者的真正数据都是储存在数据区中。

FAT12文件目录项的内容:

偏移量 长度 描述
0 8 文件名
8 3 文件扩展名
11 1 文件属性
12 10 保留位
22 2 创建时间
24 2 创建日期
26 2 首簇号
28 4 文件大小

基于此,我们定义文件目录项的结构体:

typedef struct FileDescriptor
{
    uint8_t DIR_Name[8];   //文件名
    uint8_t DIR_Type[3];   //文件类型/扩展名
    uint8_t DIR_Attr;      //文件类型
    uint8_t Reserved[10];  //保留位
    uint16_t WrtTime;      //最后一次写入时间
    uint16_t WrtDate;      //最后一次写入日期
    uint16_t DIR_FstClus;  //此条目对应的开始簇数
    uint32_t DIR_FileSize; //文件大小
} __attribute__((packed)) FileDescriptor;
数据区

数据区是系统存储文件以及目录(除根目录)的地方,由一个个扇区组成。

  • 如果某个扇区属于非目录文件,那么它存储的内容是该文件的内容。
  • 如果某个扇区属于目录文件,那么它所存储的内容是该目录文件的子目录,它们的组织形式和根目录一样。
  • 子目录中一定要包含 .和… 两个目录项。
  • dot目录项储存的首簇号为当前目录的首簇号,dot除了名字,其他信息和当前目录的目录项一样。
  • dotdot目录项储存当前目录父目录的首簇号,如果父目录是根目录那么首簇号为零,这是一种特殊情况。
    c语言文件系统模拟,操作系统,数据结构,C/C++,c语言,bash,linux,学习,数据结构

4.2 代码实现

4.2.1 磁盘的定义及初始化

FAT12文件系统磁盘的格式在上面已经叙述过,据此可以定义disk结构体:

//磁盘定义
typedef struct Disk
{
    MBRHeader MBR;                               // 1个扇区
    FAT2Entry FAT1[CLUS_NUM / 2];                // 9个扇区;
    FAT2Entry FAT2[CLUS_NUM / 2];                // 9个扇区
    FileDescriptor rootDirectory[ROOT_DICT_NUM]; // 14个扇区
    Sector dataSector[DATA_SECTOR_NUM];          // 2880个扇区
} __attribute__((packed)) Disk;

//实例化一个磁盘disk
extern Disk disk;

初始化的工作包括下面几项:

  • 初始化MBRHeader:根据参数固定填写即可。
  • 初始化FAT1和FAT2:
    • 将FAT1中第一个簇和第二个簇分别设为0xFF00xFFF,其它设置为0
    • FAT2和FAT1做同样处理
  • 初始化根目录区:清0
  • 初始化数据区:清0

这部分实现参照代码:

InitMBR();
InitFAT();

4.2.2 文件的创建

文件的创建包括根目录、其它文件的创建。

  • 根目录的创建

    //创建一个名为dictName的根目录项
    int CreatRootDict(char *dictName)
    

    根据给定的名字创建根目录,初始大小设置为512。需要完成如下的工作:

    1. 文件描述符的创建:
    //根据给定的参数,填写一个文件描述符
    void MakeFp(FileDescriptor *fp, char dirName[8], char dirType[3], uint8_t dirAttr, uint32_t fileSz)
    

    值得一提的是,FAT12文件系统中文件描述符的WrtTimeWrtDate设置如下:

    WrtDate: 从高到低,前7位为年偏移量year,接下来4位为月偏移量month,最后5位为日偏移量day;得到的最终时间即为1980 + year年,0 + month月,0 + day日。每位表示的意义如下:

    长度(bit) 描述
    7 年偏移量
    4 月偏移量
    5 日偏移量

    WrtTime: 从高到低,前5位为小时的偏移量hour,接下来6位为分钟偏移量minute;最终得到的时间就是:hour小时:minute分钟。每位的意义如下:

    长度(bit) 描述
    5 小时偏移量
    6 分钟偏移量
    5 保留

    有关于时间的设置和读取在函数SetTimeGetTime里,利用了C库time.h,可自行查阅。

    此外,关于磁盘空间的申请也是在该函数中完成的,空间申请函数代码请参阅:

    //申请能容纳sz大小的空间(512的整数倍),成功返回申请到的首簇号,失败返回-1
    int AllocSector(uint32_t sz)
    

    1. 在根目录区找到合适的位置填写描述符,通过遍历来实现。

    2. 创建dot和ddot表项。参见函数:
    //创建"."目录项
    void CreateDotDict(FileDescriptor *dotFp, const FileDescriptor *fp)
    //创建".."目录项
    void CreateDDotDict(FileDescriptor *ddotFp, int fatherClus)
    
    1. 将创建好的两个表项填写至根目录的数据区(已经申请好),参见函数:
    void WriteNewEntry(FileDescriptor *faFp, FileDescriptor *fp, int *newClus)
    
  • 其它文件的创建
    根据给定参数创建新文件:

    int CreateFile(char *fatherPath, char fileName[8], char fileType[3], uint8_t fileAttr, uint32_t fileSz)
    

    其具体步骤与根目录的创建类似,只不过需要先读取父目录即fatherPath的文件描述符,其中重要的一个函数就是:

    int ReadFp(char *path, FileDescriptor *fp)
    

    它可以根据路径读出相应的文件描述符。
    此外还需要注意文件是否已经存在的判断。

4.2.3 文件的读取

实现在函数:

int ReadFile(char *path, char *buf)
{
    FileDescriptor fp;
    if (ReadFp(path, &fp) == -1)
    {
        return -1;
    }
    ReadData(&fp, buf);
    return 0;
}

里。
它的思路更为简单:先根据路径读出相应的文件描述符,然后将数据读取在buf中即可(buf大小适当选取,更保险的做法是先读取文件描述符后再创建相应大小buf(扇区大小整数倍)进行读取,在shell的实现中有体现),最后通过ReadData进行数据读取。

4.2.4 文件的写入

实现在函数:

int WriteFile(char *path, char *buf, int bufSz)
{
    FileDescriptor fp;
    if (ReadFp(path, &fp) == -1)
    {
        return -1;
    }

    WriteData(buf, bufSz, &fp);

    //信息发生改变,回写fp
    WriteFp(path, &fp);
    return 0;
}

里。
基本步骤为:

  • 根据路径读取文件描述符
  • 向文件描述符所指向的数据区写入内容(这里是覆盖写),实现在WriteData里,可以根据buf的大小再申请或归还空间,提供空间利用率。
  • 由于在上面的函数里修改了文件描述符(写入时间,还有可能大小,簇也发生了变化),所以调用WriteFp写回文件描述符实施修改。

4.2.5 文件的删除

实现在函数

// filePath:要删除文件的父目录文件路径
// fileName:要删除的文件名称
int RemoveFile(char *fatherPath, char *fileName)

里。
主要步骤有:

  1. 读取父目录的文件描述符
  2. 根据父目录的文件内容
  3. 在父目录中匹配文件,通过函数MatchDict实现。
  4. 递归删除目标文件下的所有内容(如果是普通文件,直接删除其内容并归还数据区;如果是目录文件,则对于其下所有目录项递归进行删除),实现在函数RemoveFp里。
  5. 根据第3步中匹配的结果,删除目标文件目录项,并写回到父目录内容中。

4.3 简易Shell的实现

实现shell的目的是为了同文件系统实现交互,同时验证其正确性。在shell.hshell.c中给出了相关的定义和实现,并提供接口:ExecShell进行shell的调用。实现的基本思路就是解析用户每次输入的命令,然后根据匹配结果传递至相应的Handle函数进行处理。

五、项目总结

5.1 收获

通过网络资料的搜集和整理加深了对FAT12文件系统的认识,同时在实际编码过程中解决了许多问题,熟悉和了解了C语言的编写和各种用法。

5.2 不足

  • 函数式编程,各个函数耦合度较高,分工不够明确且略混乱。
  • 能力和经验不足,FAT12文件系统的很多实现细节考虑不周且或多或少进行了“魔改”,shell的实现也同样如此(简陋)。

参考

https://www.pc-daily.com/xitong/61493.html
https://zhuanlan.zhihu.com/p/118677232
https://www.file-recovery.com/recovery-understanding-file-system-fat.htm文章来源地址https://www.toymoban.com/news/detail-522055.html

到了这里,关于FAT12文件系统C语言模拟实现及原理讲解的文章就介绍完了。如果您还想了解更多内容,请在右上角搜索TOY模板网以前的文章或继续浏览下面的相关文章,希望大家以后多多支持TOY模板网!

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处: 如若内容造成侵权/违法违规/事实不符,请点击违法举报进行投诉反馈,一经查实,立即删除!

领支付宝红包 赞助服务器费用

相关文章

  • 操作系统进程调度算法的模拟实现(c语言版本)

            前言: 本文旨在分享如何使用c语言对操作系统中的部分进程调度算法进行模拟实现,以及算法描述的讲解, 完整代码放在文章末尾,欢迎大家自行拷贝调用 目录 常见的调度算法 数据结构 先来先服务调度算法 算法模拟思路: 算法模拟:  最短作业优先调度算法

    2024年02月06日
    浏览(55)
  • 操作系统实验一模拟优先级调度算法(C语言实现附带详细注释)

    文章目录 优先级调度算法介绍 两种情况 调度算法分类 优先级分类 实验内容与要求 实验步骤 调度算法总流程图  优先级调度算法流程图  实验代码 实验结果         优先级调度算法既可以用于作业调度,又可以用于进程调度。该算法中的优先级用于描述作业或者进程的

    2024年02月01日
    浏览(53)
  • 实验四 文件系统原理与模拟实现

    代码资源地址 Java实现的混合索引和成组链接法算法资源-CSDN文库 了解操作系统中文件系统的结构和管理过程,掌握经典的算法:混合索引与成组链接法等方法。 编程模拟实现混合索引和成组链接法算法;         1.模拟混合索引的原理;         假设每个盘块16字节

    2024年02月16日
    浏览(37)
  • 文件操作介绍及C语言实现通讯录管理系统3.0最终版(文件操作版本)

    上一篇文章我们学习了动态内存开辟的相关知识点,并用动态内存函数优化了我们的通讯录,但通讯录还有需要改进的地方,比如,正常情况下的通讯录,应该可以一直保存联系人信息,而不是退出就清空了,这就需要我们实实在在的保存下来一个通讯录。 接下来我会给大家

    2023年04月08日
    浏览(59)
  • 简易英文统计和加密系统的设计实现(纯C语言实现,包含文件操作、注释多、易理解)

    ❤️作者主页:微凉秋意 🔥系列专栏:数据结构与课程设计 ✅作者简介:后端领域优质创作者🏆,CSDN内容合伙人🏆,阿里云专家博主🏆

    2024年02月02日
    浏览(44)
  • 操作系统实验:虚拟存储器 (C语言实现) 模拟分页式虚拟存储管理中硬件的地址转换和缺页中断,以及选择页面调度算法处理缺页中断。

    模拟分页式虚拟存储管理中硬件的地址转换和缺页中断,以及选择页面调度算法处理缺 页中断。 模拟分页式存储管理中硬件的地址转换和产生缺页中断。 用先进先出(FIFO)页面调度算法处理缺页中断。 由于是模拟调度算法,所以,不实际启动输出一页和装入一页的程序,

    2024年02月04日
    浏览(65)
  • 硬盘文件系统系列之FAT

    【一、FAT概述】 FAT(File Allocation Table)是一种由微软发明并拥有部分专利的文件系统,供MS-DOS使用,也是所有非NT核心的微软窗口使用的文件系统。FAT是“文件分配表”的意思,顾名思义,就是用来记录文件所在位置的表格,它对于硬盘的使用是非常重要的,假若丢失文件分配

    2024年02月03日
    浏览(35)
  • C语言操作符篇章+系统讲解分析+深入理解操作符+原反补结合的具体应用+根源进行讲解+进制转换+操作环境+实例剖析+上万字+百张图片精细化讲解

    在讲解操作符之前需要讲解一下原反补和进制之间的转换 并且在讲解操作符的时候会重点对难点进行讲解,也就是算数操作符和逻辑操作符 并且会在讲解附带实例 和最后面的代码分析 ————————————————————————————————————————

    2024年02月20日
    浏览(52)
  • 【操作系统实验6】CPU调度程序模拟实现

    加深对操作系统CPU调度以及调度算法的理解 1) 单处理器环境下,针对最短作业优先算法(SJF)和优先级调度算法(Priority),分别模拟实现抢占调度和非抢占调度的调度程序 设计使用三个队列,分别为就绪队列(readyQueue)、运行队列(runningQueue)、等待队列(waitingQueue) 进程状态三种,

    2024年02月06日
    浏览(48)
  • 操作系统原理 —— 文件的逻辑结构(二十三)

    这里说的 逻辑结构 ,就是指在用户看来,文件内部的数据应该是如何组织起来的,而 物理结构 指的是在操作系统看来,文件的数据是如何被存放的。 从 逻辑结构 结构来看,我们可以打开一个记事本,里面的文字内容从用户的角度来看就是无结构的,但是又从 Excel 来看,

    2024年02月08日
    浏览(62)

觉得文章有用就打赏一下文章作者

支付宝扫一扫打赏

博客赞助

微信扫一扫打赏

请作者喝杯咖啡吧~博客赞助

支付宝扫一扫领取红包,优惠每天领

二维码1

领取红包

二维码2

领红包