# 基于C语言的简单文件系统的实现
# 1 题目介绍
通过具体的文件存储空间的管理、文件物理结构、目录结构和文件操作的实现,加深对文件系统内部的数据结构、功能以及实现过程的理解。
## 1.1 要求
- 在内存中开辟一个虚拟磁盘空间作为文件存储分区,在其上实现一个简单的基于多级目录的单用户单任务系统中的文件系统。在推出该文件系统的使用时,应将虚拟磁盘上的内容以一个文件的方式保存到磁盘上,一遍下次可以将它恢复到内存的虚拟磁盘中
- 文件物理结构可采用显式链接或其他结构
- 空闲磁盘空间的管理可选择FAT表、位示图或其他办法
- 文件目录结构采用多级目录结构。为简单起见,可以不使用索引结点,每个目录项应包含文件名、物理地址、长度等信息,还可以通过目录项实现对文件的读和写的保护
- 需要提供一以下操作命令
```c++
my_format
my_mkdir
my_rmdir
my_ls
my_cd
my_create
my_open
my_close
my_write
my_read
my_rm
my_exitsys
```
# 2 实验思路
## 2.1 程序设计思想
- 在该虚拟文件系统启动时,申请一块内存作为磁盘空间
- 将这块内存空间进行格式化,本系统仿照的是FAT16文件系统,其结构如下
| 1块 | 2块 | 2块 | 995块 |
| :--: | :--: | :--: | :--: |
| 引导块 | FAT1 | FAT2 | 数据区 |
- 格式化时,主要包括引导块,FAT1,FAT2,的一些初始化工作。例如设置文件魔数,文件系统的信息,FAT1,FAT2的信息等等
- 根据用户输入的命令,调用对应的my\_函数
**程序流程图**如下:
![](http://www.write-bug.com/myres/static/uploads/2021/10/19/d819f56a3d15abdff78d8c198381c2b7.writebug)
## 2.2 关键数据结构设计
### 2.2.1 文件控制块
```c++
typedef struct FCB
{
char filename[8]; // 文件名
char exname[3]; // 文件扩展名
unsigned char attribute; // 文件属性字段(目录or文件)
unsigned short time; // 创建时间
unsigned short date; // 创建日期
unsigned short first; // 起始盘块号
unsigned short length; // 文件长度
char free; // 表示目录项是否为空
} fcb;
```
### 2.2.2 文件分配表
```c++
typedef struct FAT
{
unsigned short id;
} fat;
```
### 2.2.3 用户打开文件表
当打开一个文件时,必须将文件的目录项中的所有内容全部复制到内存中,同时还要记录有关文件操作的动态信息,如读写指针的值等。在本实例中实现的是一个用于单用户单任务系统的文件系统,为简单起见,我们把用户文件描述符表和内存FCB表合在一起,称为用户打开文件表,表项数目为10,即一个用户最多可同时打开10个文件。然后用一个数组来描述,则数组下标即某个打开文件的描述符。另外,我们在用户打开文件表中还设置了一个字段“char dir[80]”,用来记录每个打开文件所在的目录名,以方便用户打开不同目录下具有相同文件名的不同文件。
```c++
typedef struct USEROPEN
{
char filename[8];
char exname[3];
unsigned char attribute;
unsigned short time;
unsigned short date;
unsigned short first;
unsigned short length;
char free;
int dirno; // 父目录文件起始盘块号
int diroff; // 该文件对应的 fcb 在父目录中的逻辑序号
char dir[MAXOPENFILE][80]; // 全路径信息
int count;
char fcbstate; // 是否修改 1是 0否
char topenfile; // 0: 空 openfile
} useropen;
```
### 2.2.4 引导块BLOCK0
在引导块中主要存放逻辑磁盘的相关描述信息,比如磁盘块大小、磁盘块数量、文件分配表、根目录区、数据区在磁盘上的起始位置等。如果是引导盘,还要存放操作系统的引导信息。本实例是在内存的虚拟磁盘中创建一个文件系统,因此所包含的内容比较少,只有磁盘块大小、磁盘块数量、数据区开始位置、根目录文件开始位置等。
```c++
typedef struct BLOCK {
char magic_number[8];
char information[200];
unsigned short root;
unsigned char* startblock;
} block0;
```
## 2.3 全局变量定义
指向虚拟磁盘的起始地址
```c++
unsigned char* myvhard;
```
用户打开文件表数组
```c++
useropen openfilelist[MAXOPENFILE];
```
记录虚拟磁盘上的数据区开始位置
```c++
unsigned char* startp;
```
## 2.4 虚拟磁盘空间布局
由于真正的磁盘操作需要涉及到设备的驱动程序,所以本实例是在内存中申请一块空间作为虚拟磁盘使用,我们的文件系统就建立在这个虚拟磁盘上。虚拟磁盘一共划分成1000个磁盘块,每个块1024个字节,其布局格式是模仿FAT文件系统设计的,其中引导块占一个盘块,两张FAT各占2个盘块,剩下的空间全部是数据区,在对虚拟磁盘进行格式化的时候,将把数据区第1块(即虚拟磁盘的第6块)分配给根目录文件,如下图所示:
![](http://www.write-bug.com/myres/static/uploads/2021/10/19/b4abd0335216085105730cb894e9e47a.writebug)
# 3 核心代码与实验结果及实验结果分析
## 3.1 核心代码与实验结果
**格式化磁盘**
```c++
void my_format()
{
/**
* 初始化前五个磁盘块
* 设定第六个磁盘块为根目录磁盘块
* 初始化 root 目录: 创建 . 和 .. 目录
* 写入 FILENAME 文件 (写入磁盘空间)
*/
block0* boot = (block0*)myvhard;
strcpy(boot->magic_number, "10101010");
strcpy(boot->information, "fat file system");
boot->root = 5;
boot->startblock = myvhard + BLOCKSIZE * 5;
fat* fat1 = (fat*)(myvhard + BLOCKSIZE);
fat* fat2 = (fat*)(myvhard + BLOCKSIZE * 3);
int i;
for (i = 0; i < 6; i++) {
fat1[i].id = END;
fat2[i].id = END;
}
for (i = 6; i < 1000; i++) {
fat1[i].id = FREE;
fat2[i].id = FREE;
}
// 5th block is root
fcb* root = (fcb*)(myvhard + BLOCKSIZE * 5);
strcpy(root->filename, ".");
strcpy(root->exname, "di");
root->attribute = 0; // dir file
time_t rawTime = time(NULL);
struct tm* time = localtime(&rawTime);
// 5 6 5 bits
root->time = time->tm_hour * 2048 + time->tm_min * 32 + time->tm_sec / 2;
// 7 4 5 bits; year from 2000
root->date = (time->tm_year - 100) * 512 + (time->tm_mon + 1) * 32 + (time->tm_mday);
root->first = 5;
root->free = 1;
root->length = 2 * sizeof(fcb);
fcb* root2 = root + 1;
memcpy(root2, root, sizeof(fcb));
strcpy(root2->filename, "..");
for (i = 2; i < (int)(BLOCKSIZE / sizeof(fcb)); i++) {
root2++;
strcpy(root2->filename, "");
root2->free = 0;
}
FILE* fp = fopen(FILENAME, "w");
fwrite(myvhard, SIZE, 1, fp);
fclose(fp);
}
```
**创建目录**
- 调用do_read读入当前目录文件到内存,检查新建文件目录是否重名
- 分配一个空闲的打开文件表项
- 分配一个空闲的盘块
- 在当前目录中问新建目录寻找一个空闲的目录项
- 设置FCB,文件的属性信息
- 创建特殊的两个目录项‘.’,‘..’
- 返回
```c++
void my_mkdir(char* dirname)
{
/**
* 当前目录:当前打开目录项表示的目录
* 该目录:以下指创建的目录
* 父目录:指该目录的父目录
* 如:
* 我现在在 root 目录下, 输入命令 mkdir a/b/bb
* 表示 在 root 目录下的 a 目录下的 b 目录中创建 bb 目录
* 这时,父目录指 b,该目录指 bb,当前目录指 root
* 以下都用这个表达,简单情况下,当前目录和父目录是一个目录
* 来不及了,先讨论简单情况,即 mkdir bb
*/
int i = 0;
char text[MAX_TEXT_SIZE