1在内存中开辟一个虚拟磁盘空间作为文件存储分区,在其上实现一个简单的基于多级目录的单用户单任务系统中的文件系统。在推出该文件系统的使用时,应将虚拟磁盘上的内容以一个文件的方式保存到磁盘上,一遍下次可以将它恢复到内存的虚拟磁盘中
2文件物理结构可采用显式链接或其他结构
3空闲磁盘空间的管理可选择FAT表、位示图或其他办法
4文件目录结构采用多级目录结构。为简单起见,可以不使用索引结点,每个目录项应包含文件名、物理地址、长度等信息,还可以通过目录项实现对文件的读和写的保护
4.实验功能讲解
2关键代码实现
//文件控制块
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;
//文件分配表
typedef struct FAT
{
unsigned short id;
} fat;
当打开一个文件时,必须将文件的目录项中的所有内容全部复制到内存中,同时还要记录有关文件操作的动态信息,如读写指针的值等。在本实例中实现的是一个用于单用户单任务系统的文件系统,为简单起见,我们把用户文件描述符表和内存FCB表合在一起,称为用户打开文件表,表项数目为10,即一个用户最多可同时打开10个文件。然后用一个数组来描述,则数组下标即某个打开文件的描述符。另外,我们在用户打开文件表中还设置了一个字段“char dir[80]”,用来记录每个打开文件所在的目录名,以方便用户打开不同目录下具有相同文件名的不同文件。
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;
在引导块中主要存放逻辑磁盘的相关描述信息,比如磁盘块大小、磁盘块数量、文件分配表、根目录区、数据区在磁盘上的起始位置等。如果是引导盘,还要存放操作系统的引导信息。本实例是在内存的虚拟磁盘中创建一个文件系统,因此所包含的内容比较少,只有磁盘块大小、磁盘块数量、数据区开始位置、根目录文件开始位置等。
//引导块BLOCK
typedef struct BLOCK {
char magic_number[8];
char information[200];
unsigned short root;
unsigned char* startblock;
} block0;
功能实现
格式化磁盘
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);
}
创建目录
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];
char* fname = strtok(dirname, ".");
char* exname = strtok(NULL, ".");
if (exname != NULL) {
printf("you can not use extension\n");
return;
}
// 读取父目录信息
openfilelist[currfd].count = 0;
int filelen = do_read(currfd, openfilelist[currfd].length, text);
fcb* fcbptr = (fcb*)text;
// 查找是否重名
for (i = 0; i < (int)(filelen / sizeof(fcb)); i++) {
if (strcmp(dirname, fcbptr[i].filename) == 0 && fcbptr->attribute == 0) {
printf("dir has existed\n");
return;
}
}
// 申请一个打开目录表项
int fd = get_free_openfilelist();
if (fd == -1) {
printf("openfilelist is full\n");
return;
}
// 申请一个磁盘块
unsigned short int block_num = get_free_block();
if (block_num == END) {
printf("blocks are full\n");
openfilelist[fd].topenfile = 0;
return;
}
// 更新 fat 表
fat* fat1 = (fat*)(myvhard + BLOCKSIZE);
fat* fat2 = (fat*)(myvhard + BLOCKSIZE * 3);
fat1[block_num].id = END;
fat2[block_num].id = END;
// 在父目录中找一个空的 fcb,分配给该目录 ??未考虑父目录满的情况??
for (i = 0; i < (int)(filelen / sizeof(fcb)); i++) {
if (fcbptr[i].free == 0) {
break;
}
}
openfilelist[currfd].count = i * sizeof(fcb);
openfilelist[currfd].fcbstate = 1;
// 初始化该 fcb
fcb* fcbtmp = (fcb*)malloc(sizeof(fcb));
fcbtmp->attribute = 0;
time_t rawtime = time(NULL);
struct tm* time = localtime(&rawtime);
fcbtmp->date = (time->tm_year - 100) * 512 + (time->tm_mon + 1) * 32 + (time->tm_mday);
fcbtmp->time = (time->tm_hour) * 2048 + (time->tm_min) * 32 + (time->tm_sec) / 2;
strcpy(fcbtmp->filename, dirname);
strcpy(fcbtmp->exname, "di");
fcbtmp->first = block_num;
fcbtmp->length = 2 * sizeof(fcb);
fcbtmp->free = 1;
do_write(currfd, (char*)fcbtmp, sizeof(fcb), 2);
// 设置打开文件表项
openfilelist[fd].attribute = 0;
openfilelist[fd].count = 0;
openfilelist[fd].date = fcbtmp->date;
openfilelist[fd].time = fcbtmp->time;
openfilelist[fd].dirno = openfilelist[currfd].first;
openfilelist[fd].diroff = i;
strcpy(openfilelist[fd].exname, "di");
strcpy(openfilelist[fd].filename, dirname);
openfilelist[fd].fcbstate = 0;
openfilelist[fd].first = fcbtmp->first;
openfilelist[fd].free = fcbtmp->free;
openfilelist[fd].length = fcbtmp->length;
openfilelist[fd].topenfile = 1;
strcat(strcat(strcpy(openfilelist[fd].dir, (char*)(openfilelist[currfd].dir)), dirname), "/");
// 设置 . 和 .. 目录
fcbtmp->attribute = 0;
fcbtmp->date = fcbtmp->date;
fcbtmp->time = fcbtmp->time;
strcpy(fcbtmp->filename, ".");
strcpy(fcbtmp->exname, "di");
fcbtmp->first = block_num;
fcbtmp->length = 2 * sizeof(fcb);
do_write(fd, (char*)fcbtmp, sizeof(fcb), 2);
fcb* fcbtmp2 = (fcb*)malloc(sizeof(fcb));
memcpy(fcbtmp2, fcbtmp, sizeof(fcb));
strcpy(fcbtmp2->filename, "..");
fcbtmp2->first = openfilelist[currfd].first;
fcbtmp2->length = openfilelist[currfd].length;
fcbtmp2->date = openfilelist[currfd].date;
fcbtmp2->time = openfilelist[currfd].time;
do_write(fd, (char*)fcbtmp2, sizeof(fcb), 2);
// 关闭该目录的打开文件表项,close 会修改父目录中对应该目录的 fcb 信息
/**
* 这里注意,一个目录存在 2 个 fcb 信息,一个为该目录下的 . 目录文件,一个为父目录下的 fcb。
* 因此,这俩个fcb均需要修改,前一个 fcb 由各个函数自己完成,后一个 fcb 修改由 close 完成。
* 所以这里,需要打开文件表,再关闭文件表,实际上更新了后一个 fcb 信息。
*/
my_close(fd);
free(fcbtmp);
free(fcbtmp2);
// 修改父目录 fcb
fcbptr = (fcb*)text;
fcbptr->length = openfilelist[currfd].length;
openfilelist[currfd].count = 0;
do_write(currfd, (char*)fcbptr, sizeof(fcb), 2);
openfilelist[currfd].fcbstate = 1;
}
进入目录
void my_cd(char* dirname)
{
int i = 0;
int tag = -1;
int fd;
if (openfilelist[currfd].attribute == 1) {
// if not a dir
printf("you are in a data file, you could use 'close' to exit this file\n");
return;
}
char* buf = (char*)malloc(10000);
openfilelist[currfd].count = 0;
do_read(currfd, openfilelist[currfd].length, buf);
fcb* fcbptr = (fcb*)buf;
// 查找目标 fcb
for (i = 0; i < (int)(openfilelist[currfd].length / sizeof(fcb)); i++, fcbptr++) {
if (strcmp(fcbptr->filename, dirname) == 0 && fcbptr->attribute == 0) {
tag = 1;
break;
}
}
if (tag != 1) {
printf("my_cd: no such dir\n");
return;
} else {
// . 和 .. 检查
if (strcmp(fcbptr->filename, ".") == 0) {
return;
} else if (strcmp(fcbptr->filename, "..") == 0) {
if (currfd == 0) {
// root
return;
} else {
currfd = my_close(currfd);
return;
}
} else {
// 其他目录
fd = get_free_openfilelist();
if (fd == -1) {
return;
}
openfilelist[fd].attribute = fcbptr->attribute;
openfilelist[fd].count = 0;
openfilelist[fd].date = fcbptr->date;
openfilelist[fd].time = fcbptr->time;
strcpy(openfilelist[fd].filename, fcbptr->filename);
strcpy(openfilelist[fd].exname, fcbptr->exname);
openfilelist[fd].first = fcbptr->first;
openfilelist[fd].free = fcbptr->free;
openfilelist[fd].fcbstate = 0;
openfilelist[fd].length = fcbptr->length;
strcat(strcat(strcpy(openfilelist[fd].dir, (char*)(openfilelist[currfd].dir)), dirname), "/");
openfilelist[fd].topenfile = 1;
openfilelist[fd].dirno = openfilelist[currfd].first;
openfilelist[fd].diroff = i;
currfd = fd;
}
}
}
删除目录
void my_rmdir(char* dirname)
{
int i, tag = 0;
char buf[MAX_TEXT_SIZE];
// 排除 . 和 .. 目录
if (strcmp(dirname, ".") == 0 || strcmp(dirname, "..") == 0) {
printf("can not remove . and .. special dir\n");
return;
}
openfilelist[currfd].count = 0;
do_read(currfd, openfilelist[currfd].length, buf);
// 查找要删除的目录
fcb* fcbptr = (fcb*)buf;
for (i = 0; i < (int)(openfilelist[currfd].length / sizeof(fcb)); i++, fcbptr++) {
if (fcbptr->free == 0)
continue;
if (strcmp(fcbptr->filename, dirname) == 0 && fcbptr->attribute == 0) {
tag = 1;
break;
}
}
if (tag != 1) {
printf("no such dir\n");
return;
}
// 无法删除非空目录
if (fcbptr->length > 2 * sizeof(fcb)) {
printf("can not remove a non empty dir\n");
return;
}
// 更新 fat 表
int block_num = fcbptr->first;
fat* fat1 = (fat*)(myvhard + BLOCKSIZE);
int nxt_num = 0;
while (1) {
nxt_num = fat1[block_num].id;
fat1[block_num].id = FREE;
if (nxt_num != END) {
block_num = nxt_num;
} else {
break;
}
}
fat1 = (fat*)(myvhard + BLOCKSIZE);
fat* fat2 = (fat*)(myvhard + BLOCKSIZE * 3);
memcpy(fat2, fat1, BLOCKSIZE * 2);
// 更新 fcb
fcbptr->date = 0;
fcbptr->time = 0;
fcbptr->exname[0] = '\0';
fcbptr->filename[0] = '\0';
fcbptr->first = 0;
fcbptr->free = 0;
fcbptr->length = 0;
openfilelist[currfd].count = i * sizeof(fcb);
do_write(currfd, (char*)fcbptr, sizeof(fcb), 2);
// 删除目录需要相应考虑可能删除 fcb,也就是修改父目录 length
// 这里需要注意:因为删除中间的 fcb,目录有效长度不变,即 length 不变
// 因此需要考虑特殊情况,即删除最后一个 fcb 时,极有可能之前的 fcb 都是空的,这是需要
// 循环删除 fcb (以下代码完成),可能需要回收 block 修改 fat 表等过程(do_write 完成)
int lognum = i;
if ((lognum + 1) * sizeof(fcb) == openfilelist[currfd].length) {
openfilelist[currfd].length -= sizeof(fcb);
lognum--;
fcbptr = (fcb *)buf + lognum;
while (fcbptr->free == 0) {
fcbptr--;
openfilelist[currfd].length -= sizeof(fcb);
}
}
// 更新父目录 fcb
fcbptr = (fcb*)buf;
fcbptr->length = openfilelist[currfd].length;
openfilelist[currfd].count = 0;
do_write(currfd, (char*)fcbptr, sizeof(fcb), 2);
openfilelist[currfd].fcbstate = 1;
}
列出当前目录的文件
void my_ls()
{
// 判断是否是目录
if (openfilelist[currfd].attribute == 1) {
printf("data file\n");
return;
}
char buf[MAX_TEXT_SIZE];
int i;
// 读取当前目录文件信息(一个个fcb), 载入内存
openfilelist[currfd].count = 0;
do_read(currfd, openfilelist[currfd].length, buf);
// 遍历当前目录 fcb
fcb* fcbptr = (fcb*)buf;
for (i = 0; i < (int)(openfilelist[currfd].length / sizeof(fcb)); i++, fcbptr++) {
if (fcbptr->free == 1) {
if (fcbptr->attribute == 0) {
printf(" %-8s\t%d/%d/%d %d:%d\n" ,
fcbptr->filename,
(fcbptr->date >> 9) + 2000,
(fcbptr->date >> 5) & 0x000f,
(fcbptr->date) & 0x001f,
(fcbptr->time >> 11),
(fcbptr->time >> 5) & 0x003f);
} else {
printf("<---> %-8s\t%d/%d/%d %d:%d\t%d\n",
fcbptr->filename,
(fcbptr->date >> 9) + 2000,
(fcbptr->date >> 5) & 0x000f,
(fcbptr->date) & 0x001f,
(fcbptr->time >> 11),
(fcbptr->time >> 5) & 0x003f,
fcbptr->length);
}
}
}
}
新建文件
int my_create(char* filename)
{
// 非法判断
if (strcmp(filename, "") == 0) {
printf("please input filename\n");
return -1;
}
if (openfilelist[currfd].attribute == 1) {
printf("you are in data file now\n");
return -1;
}
openfilelist[currfd].count = 0;
char buf[MAX_TEXT_SIZE];
do_read(currfd, openfilelist[currfd].length, buf);
int i;
fcb* fcbptr = (fcb*)buf;
// 检查重名
for (i = 0; i < (int)(openfilelist[currfd].length / sizeof(fcb)); i++, fcbptr++) {
if (fcbptr->free == 0) {
continue;
}
if (strcmp(fcbptr->filename, filename) == 0 && fcbptr->attribute == 1) {
printf("the same filename error\n");
return -1;
}
}
// 申请空 fcb;
fcbptr = (fcb*)buf;
for (i = 0; i < (int)(openfilelist[currfd].length / sizeof(fcb)); i++, fcbptr++) {
if (fcbptr->free == 0)
break;
}
// 申请磁盘块并更新 fat 表
int block_num = get_free_block();
if (block_num == -1) {
return -1;
}
fat* fat1 = (fat*)(myvhard + BLOCKSIZE);
fat* fat2 = (fat*)(myvhard + BLOCKSIZE * 3);
fat1[block_num].id = END;
memcpy(fat2, fat1, BLOCKSIZE * 2);
// 修改 fcb 信息
strcpy(fcbptr->filename, filename);
time_t rawtime = time(NULL);
struct tm* time = localtime(&rawtime);
fcbptr->date = (time->tm_year - 100) * 512 + (time->tm_mon + 1) * 32 + (time->tm_mday);
fcbptr->time = (time->tm_hour) * 2048 + (time->tm_min) * 32 + (time->tm_sec) / 2;
fcbptr->first = block_num;
fcbptr->free = 1;
fcbptr->attribute = 1;
fcbptr->length = 0;
openfilelist[currfd].count = i * sizeof(fcb);
do_write(currfd, (char*)fcbptr, sizeof(fcb), 2);
// 修改父目录 fcb
fcbptr = (fcb*)buf;
fcbptr->length = openfilelist[currfd].length;
openfilelist[currfd].count = 0;
do_write(currfd, (char*)fcbptr, sizeof(fcb), 2);
openfilelist[currfd].fcbstate = 1;
}
删除文件
void my_rm(char* filename)
{
char buf[MAX_TEXT_SIZE];
openfilelist[currfd].count = 0;
do_read(currfd, openfilelist[currfd].length, buf);
int i, flag = 0;
fcb* fcbptr = (fcb*)buf;
// 查询
for (i = 0; i < (int)(openfilelist[currfd].length / sizeof(fcb)); i++, fcbptr++) {
if (strcmp(fcbptr->filename, filename) == 0 && fcbptr->attribute == 1) {
flag = 1;
break;
}
}
if (flag != 1) {
printf("no such file\n");
return;
}
// 更新 fat 表
int block_num = fcbptr->first;
fat* fat1 = (fat*)(myvhard + BLOCKSIZE);
int nxt_num = 0;
while (1) {
nxt_num = fat1[block_num].id;
fat1[block_num].id = FREE;
if (nxt_num != END)
block_num = nxt_num;
else
break;
}
fat1 = (fat*)(myvhard + BLOCKSIZE);
fat* fat2 = (fat*)(myvhard + BLOCKSIZE * 3);
memcpy(fat2, fat1, BLOCKSIZE * 2);
// 清空 fcb
fcbptr->date = 0;
fcbptr->time = 0;
fcbptr->exname[0] = '\0';
fcbptr->filename[0] = '\0';
fcbptr->first = 0;
fcbptr->free = 0;
fcbptr->length = 0;
openfilelist[currfd].count = i * sizeof(fcb);
do_write(currfd, (char*)fcbptr, sizeof(fcb), 2);
//
int lognum = i;
if ((lognum + 1) * sizeof(fcb) == openfilelist[currfd].length) {
openfilelist[currfd].length -= sizeof(fcb);
lognum--;
fcbptr = (fcb *)buf + lognum;
while (fcbptr->free == 0) {
fcbptr--;
openfilelist[currfd].length -= sizeof(fcb);
}
}
// 修改父目录 . 目录文件的 fcb
fcbptr = (fcb*)buf;
fcbptr->length = openfilelist[currfd].length;
openfilelist[currfd].count = 0;
do_write(currfd, (char*)fcbptr, sizeof(fcb), 2);
openfilelist[currfd].fcbstate = 1;
}
打开该目录的文件
int my_open(char* filename)
{
char buf[MAX_TEXT_SIZE];
openfilelist[currfd].count = 0;
do_read(currfd, openfilelist[currfd].length, buf);
int i, flag = 0;
fcb* fcbptr = (fcb*)buf;
// 重名检查
for (i = 0; i < (int)(openfilelist[currfd].length / sizeof(fcb)); i++, fcbptr++) {
if (strcmp(fcbptr->filename, filename) == 0 && fcbptr->attribute == 1) {
flag = 1;
break;
}
}
if (flag != 1) {
printf("no such file\n");
return -1;
}
// 申请新的打开目录项并初始化该目录项
int fd = get_free_openfilelist();
if (fd == -1) {
printf("my_open: full openfilelist\n");
return -1;
}
openfilelist[fd].attribute = 1;
openfilelist[fd].count = 0;
openfilelist[fd].date = fcbptr->date;
openfilelist[fd].time = fcbptr->time;
openfilelist[fd].length = fcbptr->length;
openfilelist[fd].first = fcbptr->first;
openfilelist[fd].free = 1;
strcpy(openfilelist[fd].filename, fcbptr->filename);
strcat(strcpy(openfilelist[fd].dir, (char*)(openfilelist[currfd].dir)), filename);
openfilelist[fd].dirno = openfilelist[currfd].first;
openfilelist[fd].diroff = i;
openfilelist[fd].topenfile = 1;
openfilelist[fd].fcbstate = 0;
currfd = fd;
return 1;
}
关闭文件
int my_close(int fd)
{
if (fd > MAXOPENFILE || fd < 0) {
printf("my_close: fd error\n");
return -1;
}
int i;
char buf[MAX_TEXT_SIZE];
int father_fd = -1;
fcb* fcbptr;
for (i = 0; i < MAXOPENFILE; i++) {
if (openfilelist[i].first == openfilelist[fd].dirno) {
father_fd = i;
break;
}
}
if (father_fd == -1) {
printf("my_close: no father dir\n");
return -1;
}
if (openfilelist[fd].fcbstate == 1) {
do_read(father_fd, openfilelist[father_fd].length, buf);
// update fcb
fcbptr = (fcb*)(buf + sizeof(fcb) * openfilelist[fd].diroff);
strcpy(fcbptr->exname, openfilelist[fd].exname);
strcpy(fcbptr->filename, openfilelist[fd].filename);
fcbptr->first = openfilelist[fd].first;
fcbptr->free = openfilelist[fd].free;
fcbptr->length = openfilelist[fd].length;
fcbptr->time = openfilelist[fd].time;
fcbptr->date = openfilelist[fd].date;
fcbptr->attribute = openfilelist[fd].attribute;
openfilelist[father_fd].count = openfilelist[fd].diroff * sizeof(fcb);
do_write(father_fd, (char*)fcbptr, sizeof(fcb), 2);
}
// 释放打开文件表
memset(&openfilelist[fd], 0, sizeof(useropen));
currfd = father_fd;
return father_fd;
}
写文件
int my_write(int fd)
{
if (fd < 0 || fd >= MAXOPENFILE) {
printf("my_write: no such file\n");
return -1;
}
int wstyle;
while (1) {
// 1: 截断写,清空全部内容,从头开始写
// 2. 覆盖写,从文件指针处开始写
// 3. 追加写,字面意思
printf("1:Truncation 2:Coverage 3:Addition\n");
scanf("%d", &wstyle);
if (wstyle > 3 || wstyle < 1) {
printf("input error\n");
} else {
break;
}
}
char text[MAX_TEXT_SIZE] = "\0";
char texttmp[MAX_TEXT_SIZE] = "\0";
printf("please input data, line feed + $$ to end file\n");
getchar();
while (gets(texttmp)) {
if (strcmp(texttmp, "$$") == 0) {
break;
}
texttmp[strlen(texttmp)] = '\n';
strcat(text, texttmp);
}
text[strlen(text)] = '\0';
do_write(fd, text, strlen(text) + 1, wstyle);
openfilelist[fd].fcbstate = 1;
return 1;
}