1.实验目的
深入了解磁盘文件系统的实现。
2.实验预备知识
文件的操作;
文件的逻辑结构和物理结构;
磁盘空间的管理;
磁盘目录结构。
3.实验内容
设计一个简单的文件系统,用文件模拟磁盘,用数组模拟缓冲区,要求实现:
支持多级目录结构,支持文件的绝对读路径;
文件的逻辑结构采用流式结构,物理结构采用链接结构中的显示链接方式;
采用文件分配表;
实现的命令包括建立目录、列目录、删除空目录、建立文件、删除文件、显示文件内容、打开文件、读文件、写文件(追加方式)、关闭文件、改变文件属性。
最后编写主函数对所作工作进程测试。
4.提示与讲解
为了正确地实现文件的存取,文件系统设计了一组与存取文件有关的功能模块,用户可以用"访管指令"调用这些功能模块,以实现文件的存取要求。我们把文件系统设计的这一组功能模块称为 "文件操作",实验就是要模拟实现一些文件操作。文件操作不是独立的,它和文件系统的其他部分密切相关,若要实现文件操作就离不开文件的目录结构、文件的组织结构和磁盘空间的管理。因此,这个实验虽然是文件操作的模拟实现,但是还必须模拟一部分文件的组织结构、目录结构和磁盘空间管理的实现。
⑴ 文件的组织结构
文件的逻辑结构有两种形式:流式文件和记录式文件。实验中只支持流式文件,采用称为显示链接的物理文件结构,把磁盘中每一块的指针部分提出来,组织在一起,形成文件分配表(FAT)。文件分配表的作用不仅如此,其他的作用下面将提到。
磁盘有多少块,文件分配表就有多少项,若某文件的一个磁盘块号为i,则这个文件的下一个磁盘的块号应该记录在文件分配表第i项。例如,某系统文件分配表前几项值如图2.10所示。某个文件的起始盘块号为3,则该文件的磁盘块号依次为3、4、9、12、13。
|-----|----|----|----|---|---|---|---|---|----|----|----|----|----|----|----|----|-----|
| 第几项 | 0 | 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | 9 | 10 | 11 | 12 | 13 | 14 | 15 | |
| 内容 | -1 | -1 | -1 | 4 | 9 | 0 | 7 | 8 | -1 | 12 | 11 | -1 | 13 | -1 | 0 | 0 | ... |
图2.10 某磁盘文件分配表部分内容
⑵ 磁盘空间的管理
首先要模拟一个磁盘。因为是实验,不使用真正的磁盘,所以实验中用一个文件模拟一个小磁盘。假设模拟磁盘有128个物理块,每个物理块大小为64字节。盘块的块号从0编起,0,1,2,3......,127。
将前面所讲的文件分配表放在磁盘的开始处,因为盘块有128块,所以文件分配表有128项,每项占用一个字节。这样文件分配表占用了磁盘的0块和1块,这两块就不能作其他用处。若一个盘块是某个文件的最后一块,填写"-1"表示文件结束。
文件的建立和删除就需要对磁盘的空间进行分配和回收,所以要建立一定的数据表格来记录磁盘的使用情况。用文件分配表的第i项表示第i个盘块的使用情况。磁盘的第0块一定会被系统数据占用,所以任何一个文件的某个盘块快号都不可能是"0",因而还可以用"0"表示磁盘盘块空闲,若这个盘块已经分配出去,即是某个文件的一块,由上面我们知道文件分配表中对应项记录的是文件的下一块的块号或结束标志都不是"0"。这样非"0"值表示盘块已分。象前面图2.10那张文件分配表中,块号为5、14和15的盘块是空闲的,其余是已分配的。在文件分配表中可以用一个超过盘块编号的正整数表示文件结束,在此实验中采用255代替-1表示文件结束。
如果磁盘中某些部分损坏,只要不是系统区(引导扇区、文件分配表或根目录等),不分配那些坏的盘块,磁盘可以继续使用。在文件分配表对应坏盘块的项不能是"0",一般也需要特定的数值表示(这个数值应该是盘块编号以外并且不是结束标志的数值,例如,实验中128~254之间的数值)。假设实验中模拟磁盘的第8、10、11、12、26块已经损坏,不能使用,则在文件分配表的对应项写入"254"表示该盘块损坏不能使用。
由于磁盘分配时,有时不能预定文件的大小,例如建立文件时并不知道文件的大小。因而磁盘的分配有时是一块一块申请的。磁盘空间回收时,整个文件删除时回收很多块,但有时文件修改时可能会删除某些内容,造成归还磁盘块,这时是一块一块回收的。这里给出申请一块磁盘空间的流程图,若分配多块时,循环多次即可。分配一个磁盘块时,不应该从文件分配表第一项查起,因为磁盘中最开始的几块为系统数据区(引导扇区、文件分配表、根目录等占用),所以应该从这之后的可分配数据区开始查询。假定系统区域占用了x个盘块,分配一个磁盘块的流程图如图2.11所示。回收一个磁盘块的流程图很简单,比如回收磁盘块的块号为x,只要找到文件分配表中第x项,将第x项的值改为0即可。
2.11 分配一个磁盘块的流程图
分配一个磁盘块代码:
cpp
//分配磁盘空间 没有修改FAT表
int AssignDisk()
{
for (int i=1;i<128;i++)
{
if (FAT[i] == 0)
return i;
}
}
⑶ 目录结构
文件目录是用于检索文件的,它是文件系统实现按名存取的主要手段。文件目录由若干目录项组成,每一个目录记录一个文件的有关信息。一般地说,目录项应该包括如下内容:
① 有关文件的控制信息。例如,用户名、文件名、文件类型、文件属性。实验模拟个人计算机上的文件操作,这部分内容仅包括文件名、文件类型和属性;
② 有关文件结构的信息。例如,文件的逻辑结构、文件的物理结构、记录个数、文件在存储介质的位置等。实验中,仅仅支持流式文件,不支持记录式文件,所以这部分内容仅仅包括文件在存储介质的位置(分给文件第一块盘块的块号,即起始盘块号)、文件的长度;
③ 有关文件管理的信息。例如,文件的建立日期、文件被修改的日期、文件保留期限和记帐信息等。实验中为了简单起见,这部分内容都不采用。
因此,实验中文件的目录项包括:文件名、文件类型、文件属性、文件的起始盘块号、文件的长度,每个目录项占用8个字节,具体结构如下所示:
文件名:3个字节**(实验中合法文件名仅可以使用字母、数字和除""、"."、"/"和"\\n"以外的字符,第一个字节的值为""时表示该目录为空目录项,文件名和类型名之间用"."分隔,用"/"作为路径名中目录间分隔符)**;
文件类型名:2个字节;
文件属性:1字节;
起始盘块号:1个字节;(同样用的ASCII码,会打乱格式的直接初始化为磁盘损坏)
文件长度:1个字节**(用ASCII码值代表的字符数量,但有的ASCII码会打乱磁盘(txt)的格式,所以写入磁盘时的时候强制加1,并且个别的数量字符不能写)**。
有了文件目录后,当用户要求使用某个文件时,文件系统可以顺序查找目录项,并比较文件名,就可以找到指定文件的目录项,根据目录项中有关内容核对使用权限、并读出文件供用户使用。因此文件目录的组织和管理要便于检索和防止冲突。
在操作系统中目录就有根目录和子目录两种目录。除了文件需要登记形成目录外,还要登记子目录的情况。实验中,根目录固定位置、固定大小(可以登记有限个文件或子目录项),子目录象文件一样,可使用任何一个空闲磁盘块。为了实验简单,实验中根目录占用了一个盘块,子目录的长度没有采用可以任意长的方法,而是采用了定长的方法,每个子目录的长度也是一个盘块,只能放8个目录项。文件和目录的登记项是混在一起的,登记项的结构应该和文件目录一样,每个目录项占用8个字节,结构如下:
目录名:3个字节(实验中合法名仅可以使用字母、数字和除""、"."、"/"、"\\n"、"\\0"以外的字符,第一个字节的值为""时表示该目录为空目录);
未使用2字节(在实验中填写空格);
目录属性:1字节;
起始盘块号:1个字节;
未使用1字节(在实验中填写"0")。
在目录登记项中,系统为目录名后2个字节(对应文件类型名位置)填写空格,目录起始盘块号后1字节(对应文件长度位置)填写0。目录属性和文件属性占用同一个字节,为了区别目录和文件,该字节每一位代表不同的含义(为"1"表示"是",为"0"表示"否"),如图2.12所示,第0位表示文件为只读文件,第1位表示文件为系统文件,第2位表示文件为一般可读、可写的普通文件,第3位表示该登记项不是文件的登记项,而是目录的登记项,其余几位闲置未用。如该字节为8 (8=(00001000)2)表示该目录是一个目录的登记项 ,该字节为3(3=(00000011)2)表示该目录是一个只读系统文件的登记项,该字节为(4=(00000100)2)表示该目录是一个可读可写的普通文件。这一项用的直观上的数字8、3、4进行存储,其他未用,所以在转化时需要加减'0'
第7位 第6位 第5位 第4位 第3位 第2位 第1位 第0位
|-----|-----|-----|-----|------|------|------|------|
| 未使用 | 未使用 | 未使用 | 未使用 | 目录属性 | 普通文件 | 系统文件 | 只读文件 |
图2.12 属性字节各位作用示意图
目录检索的方法常用的是顺序检索,根据绝对路径名查找文件的方法一般如下:先找到根目录的起始盘块,一般根目录位置是固定的,实验中就是模拟磁盘的第2块,将该盘块读出;取出路径名中根目录后的目录名或文件名,和根目录中目录项依次比较,比较完一块,再根据文件分配表找到下一块,再读入比较,直到找到名字一致的目录项或根目录登记项均已查完为止;若没有找到,则查找失败,结束;若查找的是文件,结束;若查找的是目录,从找到的目录项中,取出目录的起始盘块号,读入此盘块,然后用上述相同的查找方法继续查找,直到找到该文件(或目录)或查找失败结束。
实验中,使用绝对路径查找文件(或目录)的流程图如图2.13所示。
查找文件除了绝对路径名外,还可以使用相对路径名。相对路径名是从当前目录出发到指定文件的路径。如果文件(或目录)在当前目录下,使用相对路径名查找速度比较快。和绝对路径的查找方法一样,只是查找的起点是当前目录,不是根目录。实验中只使用了绝对路径名。
图2.13 使用绝对路径查找文件(目录)的流程图
代码:(注:在处理字符串时需要手动加\0结束符,否则后面会有乱码)
cpp
//查找磁盘中的文件 或 目录 找的过程中为从磁盘里找出1块放缓存,从缓存里匹配是不是 该函数不记录在文件登记表里
int FindContentDisk(char name[])
{
/*char *token; //把name拆分为二维数组
char *delimiter = "\\"; //一个\的字符识别不出来 需要加一个
token = strtok(name, delimiter);
while (token != NULL)
{
strcpy(pna[last],token);
token = strtok(NULL, delimiter);
last++;
}
for (int i=0;pna[0][i]!='\0';i++)//第0项为空?没有加\0
printf("%c%d",pna[0][i],i);*/
//上面分隔的方法最后没有加\0运行时会错误
//i记录起始盘块号 初始为根目录所在盘块号 last记录到最后文件名或目录名有多层
int i = 1,last = 0,num=0;
char pna[5][10]; //拆分后分别存到每一层里
for (int j=0;name[j]!='\0';j++)
{
if (name[j]=='\\')
{
pna[last][num] = '\0';
num=0;
last++;
}
else
pna[last][num++] = name[j];
}
pna[last][num] = '\0'; //必须加\0
//标志有没有找到 返回值为flag即缓存区中的第几行 即该块的第几项
int flag = -1;
for (int t=0;t<last+1;t++) //总的循环次数为层数
{
flag=-1;
WriteBuffer2(i); //把第i块写入缓冲2
for (int j=0;j<8;j++) //在缓冲区里找哪一行 如果是文件只有1行
{
char bname[10],type[10]; int attribute;
//sscanf(buffer2[j],"%s %s %d %d",bname,type,attribute,i);
bname[0] = buffer2[j][0]; //前三个字节为名
bname[1] = buffer2[j][1];
bname[2] = buffer2[j][2];
bname[3] = '\0'; //必须加\0
type[0] = '.';
type[1] = buffer2[j][3]; //类型名 或 空格
type[2] = buffer2[j][4];
attribute = buffer2[j][5]-'0'; //属性
i = buffer2[j][6]; //把起始盘块号赋值给i 不需要 -'0'
if(attribute != 8) //目录属性字节为8 文件的目录需要把文件名和类型名合并
{
strcat(bname,type); //将文件名和类型拼接
}
//printf("bname=%s pna = %s",bname,pna[t]);
if (!strcmp(bname,pna[t]))
{
flag = j;
break;
}
}
if (flag==-1)
{
printf("查找的不存在\n");
return -1;
}
}
return flag; //返回找到的在第几行
}
⑷ 文件操作
确定文件组织结构、目录结构和磁盘空间管理的方法后,就可以模拟文件操作的实现。实验中文件操作包括建立文件(create_file)、打开文件(open_file)、关闭文件(close_file)、读文件(read_file)、写文件(write_file)、删除文件(delete_file)、显示文件内容(typefile)和改变文件属性(change),目录命令包括建立目录(md)、显示目录内容(dir)和删除空目录(rd)。在实验中没有程序调用这些指令,为了看到它们的模拟情况,从键盘输入选择指令来模拟用户程序的调用。
首先要建立一个"已打开文件表",用来记录打开或建立文件的相关内容,结构如图2.14所示。
|-------|------|-------|------|------|-----|------|-----|------|
| 文件路径名 | 文件属性 | 起始盘块号 | 文件长度 | 操作类型 | 读指针 || 写指针 ||
| 文件路径名 | 文件属性 | 起始盘块号 | 文件长度 | 操作类型 | 块号 | 块内地址 | 块号 | 块内地址 |
| | | | | | | | | |
| ... | ... | ... | | ... | ... | | ... | |
图2.14 已打开文件表结构
用数组模拟已打开文件表,数据结构定义如下:
#define n 5 //实验中系统允许打开文件的最大数量
typedef struct
{ int dnum; //磁盘盘块号
int bnum; //磁盘盘块内第几个字节
}pointer; //已打开文件表中读、写指针的结构
typedef struct
{char name[20]; //文件绝对路径名
char attribute; //文件的属性,用1个字节表示,所以采用char类型
int number; //文件起始盘块号
int length; //文件长度,文件占用的字节数
int flag; //操作类型,用"0"表示以读操作方式开文件,用"1"表示写操作方式打开文件
pointer read; //读文件的位置,文件打开时dnum为文件起始盘块号,bnum为"0"
pointer write; //写文件的位置,文件刚建立时dnum为文件起始盘块号,bnum为"0",打开文件时dnum和bnum为文件的末尾位置
}OFILE; //已打开文件表项类型定义
struct
{OFILE file[n]; //已打开文件登记表
int length; //已打开文件登记表中登记的文件数量
}openfile; //已打开文件登记表定义
无论上述哪种文件操作都会涉及到已打开文件表,对于已打开文件表主要是查找、删除和插入操作。下面给出这三种操作的流程图,在已打开文件表中查找某文件是否存在的流程图如图2.15,将某文件从已打开文件表中删除的流程图如图2.16,将某个文件登记在已打开文件表的流程图如图2.17。
图2.16 在已打开文件表中查找某文件的流程图
在已打开文件表中查找某文件的流程图 代码:
cpp
//在打开的文件表里找文件 没有返回-1 找到返回第几个
int FindOpenFile(char name[])
{
for (int i=0;i<openfile.length;i++)
{
if (!strcmp(openfile.file[i].name,name))
{
return i;
}
}
return -1;
}
图2.17 将某文件插入已打开文件表的流程图
将某文件插入已打开文件表的流程图 代码:
cpp
//在打开的文件表里插入 成功返回1 失败返回0
int insertFile(char name[],char attribute,int number,int length,int flag,int dnum,int bnum)
{
if(FindOpenFile(name)!=-1)
{
printf("文件已打开");
return 0;
}
if (openfile.length == 5)
{
printf("文件表已满,插入失败\n");
return 0;
}
strcpy(openfile.file[openfile.length].name ,name);
openfile.file[openfile.length].attribute = attribute;
openfile.file[openfile.length].number = number;
openfile.file[openfile.length].length = length;
openfile.file[openfile.length].flag = flag;
if (!flag) //flag为0代表只读
{
openfile.file[openfile.length].read.dnum = dnum;
openfile.file[openfile.length].read.bnum = bnum;
}
else
{
openfile.file[openfile.length].write.dnum = dnum;
openfile.file[openfile.length].write.bnum = bnum;
}
openfile.length++;
return 1;
}
图2.18 将某文件从已打开文件表中删除的流程图
将某文件从已打开文件表中删除的流程图 代码:
cpp
//在打开的文件表里删除 成功返回1 失败返回0 在关闭文件时调用
int DeleteOpenFile(char name[])
{
for (int i=0;i<openfile.length;i++)
{
if (!strcmp(name,openfile.file[i].name))
{
openfile.file[i] = openfile.file[openfile.length-1];
openfile.length--;
return 1;
}
}
return 0;
}
① 建立文件(create_file)
用户要把一个新文件放到存储介质上前,首先调用文件系统的"建立"操作。
"建立文件"的主要工作就是检查文件目录,确认无重名文件后,寻找空闲登记项进行登记;寻找空闲存储块(至少一块)以备存储文件信息或存放索引表,最后应该填写已打开文件表。
实验中需要的参数比较少,只要有文件名、文件属性就可以,create_file(文件名,文件属性)。
实验中,建立文件时给出文件名和文件属性,文件属性如果是只读性质则不能建立;文件建立时根据给定的文件路径名进行查找,如果父目录不存在,建立文件失败;如果存在,查看有无重名文件,如果有,则提示该文件已存在,建立文件失败;如无重名文件,则为该文件建立文件目录,并分配给它一个磁盘块;最后填写目录和已打开文件表。建立文件的流程图如图2.19所示。
图2.19 模拟建立文件的流程图
模拟建立文件的流程图 代码:
cpp
//在磁盘中创建空的文件
void create_file(char name[],char attribute)
{
if ((attribute-'0')%2) //只读文件奇数
{
printf("此文件为只读文件,不能创建\n");
return;
}
if (openfile.length == 5)
{
printf("已打开文件表已满,建立文件失败\n");
return;
}
//分离name pathname为目录路径 type为类型
int last = 0;char pathname[20],dname[10],type[10];
for(int i=0;name[i]!='\0';i++)
{
if (name[i]=='\\') //计数 有多少\就有多少层
last++;
}
int count1=0; //count1为写入了多少层
for (int i=0;name[i]!='\0';)
{
if (count1 == last) //最后一层写入文件名dname和类型type
{
int ii=0;
//pathname[i] = '\0'; 不加也对?????
if(name[i]=='\\')
i++;
while (name[i]!='.')
dname[ii++] = name[i++];
// dname[ii] = '\0';
i++;
ii=0;
while(name[i]!='\0')
type[ii++] = name[i++];
// type[ii] = '\0';
//break;
}
else
{
if(count1<last)
{
pathname[i] = name[i]; //前几层全计入目录路径
i++; //i++不能写在[]里面!!!!!!!!
}
if (name[i] == '\\') //遇到\ count加1
count1++;
}
}
//有有没有对应的目录,并找到最后1层目录的盘块号
int d;
if(!last) //文件在根目录
{
d=1;
}
else
{ //!!!d前不能加int 否则出了循环d乱码
d=FindContentDisk(pathname); //调用函数 在磁盘上找有没有这个目录
if (d==-1)
{
printf("指定的文件目录不存在,建立文件失败\n");
return;
}
d = buffer2[d][6]; //最后1层目录的起始盘块号
}
//找该页目录有没有这个文件和有没有空位
WriteBuffer2(d); //从磁盘上写入缓冲2
int fullflag = -1;
for (int i=0;i<8;i++)
{
char bname[10],two[10]; //记录从磁盘上的文件名bname和属性two
bname[0] = buffer2[i][0]; //前三个字节为名
bname[1] = buffer2[i][1];
bname[2] = buffer2[i][2];
bname[3] = '\0';
two[0] = buffer2[i][3]; //类型名
two[1] = buffer2[i][4];
two[2] = '\0';
if (!strcmp(dname,bname) && !strcmp(two,type)) //如果已经有这个文件了
{
printf("指定文件已存在,建立文件失败\n");
return ;
}
if(fullflag==-1 && bname[0] == '$') //找这一块磁盘的第一个空位
{
fullflag = i; //记录空位位置
}
}
if (fullflag==-1) //如果没有空位
{
printf("该目录没有空位,创建文件失败\n");
return ;
}
//申请磁盘空位
int dd = AssignDisk(); //调用时已修改fat表
if (!dd)
return;
if(!insertFile(name,attribute,dd,0,1,dd,0)) //认为新建的文件为空 新建文件为写的类型
{
//printf("文件表已满,新建文件失败\n");
return;
}
FAT[dd] = 255; //文件表没有满再修改FAT表 255代表文件结束
//填写缓冲2的目录 最后写入磁盘
strcpy(buffer2[fullflag],dname); //文件名
strcat(buffer2[fullflag] , type); //文件类型
strcat(buffer2[fullflag] , &attribute); //文件属性 字符要加地址
buffer2[fullflag][6] = dd; //申请的起始盘块号
buffer2[fullflag][7]=1; //文件长度为0 因为0的ascii码为\0会打乱磁盘格式 用1代替
buffer2[fullflag][8]='\0';
WriteDisk(buffer2,d); //修改目录写入磁盘
//初始化文件
buffer2[0][0] = '#';
strcpy(buffer2[0],"# \0");
for (int i=1;i<8;i++)
strcpy(buffer2[i]," \0");
/*buffer2[0][1] = '\n';buffer2[0][2] = '\0';这种方法不行 必须满8个字符且不包含回车
buffer2[1][0] = '\n';buffer2[1][1] = '\0';
buffer2[2][0] = '\n';buffer2[2][1] = '\0';
buffer2[3][0] = '\n';buffer2[3][1] = '\0';*/
WriteDisk(buffer2,dd); //把新建的文件追加结束符并写入磁盘
printf("新建文件成功\n");
return;
}
② 打开文件(open_file)
用户要求使用一个已经存在的文件时,首先执行"打开文件"操作。
实验中,所需参数有文件名、操作类型(读或写),open_file(文件名,操作类型)。
实验中,打开文件首先要检查该文件是否存在,不存在,打开失败;如果文件存在,还要检查打开方式,确保不能以写方式打开只读文件;最后填写已打开文件表,若文件已经打开则不需要填写已打开文件表。打开文件的流程图如图2.20所示。
图2.20 打开文件的流程图
打开文件的流程图 代码:
cpp
//从磁盘里找到文件并插入到文件表里
void open_file(char name[],char operat)
{
int t = FindContentDisk(name);
if(t==-1)
{
return;
}
int attribute;int length,dnum,number,bnum=0;
//sscanf(buffer2[t],"%s %s %d %d %d",bname,type,attribute,dnum,length); //把起始盘块号赋值给i 字符直接转化为数字 不用减'0'
attribute = buffer2[t][5]-'0'; //属性
dnum = buffer2[t][6];
length = buffer2[t][7]-1; //写的时候加1了 返回需要减1 实际没有用到
if ((attribute)%2 && operat=='w' || !((attribute)%2) && operat == 'r') //只读文件奇数
{
printf("操作不合法,无法打开文件\n");
return;
}
int flag = 0;
number = dnum;
if (operat == 'w')
{
flag = 1;
bnum = length%64; //写的文件bnum指向末尾
while (FAT[dnum] != 255)
{
dnum = FAT[dnum]; //找到末尾磁盘块
}
}
insertFile(name,attribute,number,length,flag,dnum,bnum);
printf("文件已打开\n");
}
③ 读文件(read_file)
用户要求读文件信息时调用文件系统的"读文件"操作。
实验中,读文件的参数只需要文件名和读取长度,read_file(文件名,读取长度)。因为采用的是流式文件结构,所以读的长度用字节表示。
实验中,读文件操作的主要工作是查找已打开文件表中是否存在该文件;如果不存在,不能读;然后检查是否是以读方式打开文件,如果是以写方式打开文件,则不允许读;最后从已打开文件表中读出读指针,从这个位置上读出所需要长度,若所需长度没有读完已经遇到文件结束符,就终止读操作。实验中用"#"表示文件结束。读文件的流程图如图2.21所示。
图2.21 读文件的流程图
读文件的流程图 代码:
cpp
//在文件表里找文件并读 如果多次操作并且没有关闭文件 将会继续读而不是从头读
void read_file(char name[],int length) //length 为读取长度
{
int loc=FindOpenFile(name); //查找打开的文件表
if(loc==-1)
{
printf("文件未打开,无法读文件\n");
return;
}
if (openfile.file[loc].flag)
{
printf("此文件为写,不能读\n");
return ;
}
WriteBuffer1(openfile.file[loc].read.dnum); //将这个文件的起始盘块号写入缓冲1
int line=openfile.file[loc].read.bnum/8;int t=0;
while(t<length) //当t小于要读的长度时一直循环
{
if (buffer1[line][openfile.file[loc].read.bnum%8]=='#') //如果文件结束强制结束循环
{
printf("读文件结束\n");
return ;
}
else
{
printf("%c",buffer1[line][openfile.file[loc].read.bnum%8]); //没有输出一行后面的回车
t++;
openfile.file[loc].read.bnum++;
if(openfile.file[loc].read.bnum>7 && openfile.file[loc].read.bnum%8==0) //读到第8个字符换行
line++;
if(line == 8) //这一块内容读完 找下一块
{
openfile.file[loc].read.bnum = 0;
openfile.file[loc].read.dnum = FAT[openfile.file[loc].read.dnum];
WriteBuffer1(openfile.file[loc].read.dnum);
line=0;
}
}
}
printf("读文件结束\n");
return ;
}
④ 写文件(write_file)
用户要求存取文件信息时调用文件系统的"写文件"操作。实验中,写文件的参数只需要文件名、存放准备写入磁盘信息的缓冲和写的长度,write_fille(文件名,缓冲,写长度)。因为采用的是流式文件结构,所以写长度用字节表示。
实验中,写文件操作的主要工作是查找已打开文件表中是否存在该文件,如果不存在,不能写;如果存在,还要检查是否以写方式打开文件;如果不是,写方式打开文件, 不能写;最后从已打开文件表中读出写指针,从这个位置上写入缓冲中的数据。
写文件有两种情况,一种情况是建立文件后的写入,这种写比较简单,一边写一边申请空间即可完成;一种情况是文件打开后的写入,这个比较复杂,存在着文件中间修改的问题。实验中,第二种情况只完成了从文件末尾向后追加的功能。写文件的流程图如图2.22所示。
图2.22 写文件的流程图
写文件的流程图 代码:
cpp
//在文件表里找文件并写 函数结束后所有都已经写入磁盘并且追加了文件结束符
void write_file(char name[],char buff[],int length)
{
int loc=FindOpenFile(name);
if(loc==-1)
{
printf("文件未打开,无法写文件\n");
return;
}
if (!openfile.file[loc].flag)
{
printf("此文件为读,不能读\n");
return ;
}
WriteBuffer1(openfile.file[loc].write.dnum);
int line = openfile.file[loc].write.bnum / 8; //计算当前指针在缓冲区的第几行
int t = 0;
while (t<length)
{
buffer1[line][openfile.file[loc].write.bnum % 8] = buff[t++]; //openfile.file[loc].write bnum % 8为在这一行的第几列 一开始bnum指的位置就可以写
openfile.file[loc].write.bnum++; //写完之后在加1
if (openfile.file[loc].write.bnum >7 && !(openfile.file[loc].write.bnum %8)) //当这一行即满8个字节时
{
buffer1[line][8] = '\n'; //每一行加一个回车符
line++;
if(line==8) //当这个磁盘写满后
{ //fseek(fp,((openfile.file[loc].write.dnum)*9+1)*sizeof(char), SEEK_SET); //从该文件的块开始写 一次写1行 即8字节 每一行还有1个回车
//申请新的磁盘空间
int dd = AssignDisk();
if(dd)
{
WriteDisk(buffer1,openfile.file[loc].write.dnum); //把写满的缓冲1写入磁盘
FAT[openfile.file[loc].write.dnum] = dd;
FAT[dd] = 255;
WriteDisk(buffer1,openfile.file[loc].write.dnum);
openfile.file[loc].write.dnum = dd;
WriteBuffer1(openfile.file[loc].write.dnum);
openfile.file[loc].write.bnum = 0;
}
else
{
buffer1[7][7]='#';buffer1[7][8]='\0'; //把最后一个字符强制换为文件结束符
WriteDisk(buffer1,openfile.file[loc].write.dnum); //把写满的缓冲1写入磁盘
openfile.file[loc].length+=t;
printf("磁盘满,不能继续写");
return ;
}
line = 0;
}
}
}
openfile.file[loc].length += length;
buffer1[line][openfile.file[loc].write.bnum % 8] = '#'; //最后追加文件结束符写入磁盘
//printf("line=%d openfile.file[loc].write.bnum=%d\n",line,openfile.file[loc].write.bnum);
if ((openfile.file[loc].write.bnum+1) %8)
{ //如果这一行没有结束,把buffer1后面的清空
int loc = openfile.file[loc].write.bnum+1;
while(loc %8)
{
buffer1[line][(loc) % 8] = ' ';
loc++;
}
buffer1[line][loc] = '\0';
}
//行数加1 把buffer1下面的初始化 因为写入磁盘是一块一块的写入
line++;
while(line<8)
{
strcpy(buffer1[line]," \0");
//buffer1[line][1] = '\0';
line++;
}
WriteDisk(buffer1,openfile.file[loc].write.dnum); //把最后写的写入磁盘
printf("写文件结束\n");
return;
}
⑤ 关闭文件(close_file)
用户对文件读写完毕后需要调用文件系统的"关闭文件"操作。
实验中,关闭文件的参数只需要文件名,close_file(文件名)。
实验中关闭文件,首先要看该文件是否打开,如果没有打开,就不用关闭;如果已经打开,则检查打开方式,如果是写方式打开的,要追加文件结束符,修改目录项;最后从已打开文件表中删除对应项。关闭文件的流程图如图2.23所示。
图2.23 模拟关闭文件的流程
模拟关闭文件的流程图 代码:
注:这里面并没有按照流程图的思路写,而是把追加文件结束符分别写在创建文件与写文件里面,即在调用完那两个函数时,已经把缓冲里面的数据写入了磁盘(.txt)里面,再关闭文件时只需要修改目录里的文件字符个数即长度,而这个长度用的1个字符来表示,并且最后长度加1,因为0的ASCII码为空 ,在磁盘(.txt)里面,这会打乱格式,后面会出错,所以最多存放126个字符
cpp
//关闭文件
void close_file(char name[])
{
int openloc = FindOpenFile(name);
if (openloc==-1)
{
printf("文件未打开,无法关闭\n");
return;
}
if(openfile.file[openloc].flag)//如果操作是写 只需修改目录
{
int pathdnum = FindDisk(name); //找到目录所在盘块号
int d = FindContentDisk(name); //把该目录的盘块存入了缓冲区2 并返回第几行
// printf("\naaaaa{}%d\n",openfile.file[openloc].length+1);
buffer2[d][7] = openfile.file[openloc].length+1; //修改目录里文件长度 ASCII码为0代表\0 会把格式打乱
WriteDisk(buffer2,pathdnum); //把第pathdnum块的第d行写入磁盘
}
if(DeleteOpenFile(name));
//printf("")
printf("关闭文件成功\n");
return;
}
⑥ 删除文件(delete_file)
用户认为文件没有必要保存时需要调用文件系统的"删除文件"操作。实验中,删除文件时参数只要文件名,delete_file(文件名)。
实验中,删除文件操作的主要工作是检查文件是否存在;不存在,操作失败;如存在,查找该文件是否打开,如果打开不能删除;如果没有打开,则删除文件目录项并归还文件所占磁盘空间。删除文件的流程图如图2.24所示。
图2.24 删除文件的流程图
删除文件的流程图 代码:
cpp
//删除文件
void delete_file(char name[])
{
int t = FindContentDisk(name);
if(t ==-1)
{
printf("文件不存在,无法删除文件\n");
return;
}
int fileloc = FindOpenFile(name);
if (fileloc != -1)
{
printf("文件正在使用,不能删除\n");
return;
}
int lastdnum=buffer2[t][6];
//修改目录需要找到这个目录的盘块号 并修改目录
int pathdnum = FindDisk(name);
WriteBuffer2(pathdnum) ; //把该目录的盘块存入了缓冲区2
strcpy(buffer2[t],"$ \0"); //修改目录
WriteDisk(buffer2,pathdnum); //把第pathdnum块 写入磁盘
//把文件初始化
t = lastdnum;
while(t!=255)
{
/*buffer2[0][0]='\n';buffer2[1][0]='\n';buffer2[2][0]='\n';buffer2[3][0]='\n';
buffer2[0][1]='\0';buffer2[1][1]='\0';buffer2[2][1]='\0';buffer2[3][1]='\0';*/
for (int i=0;i<8;i++)
strcpy(buffer2[i]," \0");
WriteDisk(buffer2,t);
t = FAT[t];
FAT[lastdnum] = 0; //修改FAT表
lastdnum = t;
}
printf("删除文件结束\n");
return;
}
⑦显示文件内容(typefile)
显示文件内容首先要找到该文件的目录登记项,如果文件不存在,指令执行失败;如果存在,查看文件是否打开,打开则不能显示文件内容;若没有打开,从目录中取出文件的起始盘块号,一块一块显示文件内容。显示文件内容流程如图2.25。
图2.25 显示文件的流程图
显示文件的流程图 代码:
cpp
//显示文件
void typefile(char name[])
{
int t = FindContentDisk(name);
if(t ==-1)
{
printf("文件不存在,无法显示文件\n");
return;
}
int fileloc = FindOpenFile(name);
if (fileloc != -1)
{
printf("文件正在使用,不能显示\n");
return;
}
int dnum = buffer2[t][6];
while(dnum != 255)
{
WriteBuffer2(dnum);
for (int i=0;i<8;i++)
{
for (int j=0;j<8;j++)
if (buffer2[i][j] == '#')
{
printf("\n文件显示结束\n");
return;
}
else
printf("%c",buffer2[i][j]); //不能显示空格
}
dnum = FAT[dnum];
}
}
⑧改变文件属性(change)
改变文件属性,首先查找该文件,如果不存在,结束;如果存在,检查文件是否打开,打开不能改变属性;没有打开,根据要求改变目录项中属性值。
实验中,首先要系统初始化,包括建立文件c模拟磁盘、初始化磁盘FAT、初始化根目录为空目录项;然后,可以选择一项功能执行。
改变文件属性 代码:
cpp
//改变文件属性
void chage(char name[],char attribute)
{
int t = FindContentDisk(name);
if(t ==-1)
{
printf("文件不存在,无法改变文件属性\n");
return;
}
int fileloc = FindOpenFile(name);
if (fileloc != -1)
{
printf("文件正在使用,不能改变文件属性\n");
return;
}
int dnum = FindDisk(name);
WriteBuffer2(dnum);
buffer2[t][5] = attribute;
WriteDisk(buffer2,dnum);
printf("修改属性完成\n");
return;
}
目录的操作命令:
① 建立目录(md)
建立目录首先要找到建立目录的位置(父目录),然后查找该目录是否存在,如果父目录不存在,不能建立;如果存在,查找是否存在同名目录,存在,不能建立;不存在,则查找一个空目录项,为该目录申请一个盘块,并填写目录内容。
建立目录流程图如图2.26所示。
图2.26 建立目录的流程图
建立目录的流程图 代码:
cpp
//在磁盘中创建空的目录
void create_content(char name[])
{
//分离name pathname为目录路径
int last = 0;char pathname[20],dname[10];
printf("%s\n",name);
for(int i=0;name[i]!='\0';i++)
{
if (name[i]=='\\') //计数 有多少\就有多少层
last++;
}
int count1=0; //count1为写入了多少层
for (int i=0;name[i]!='\0';i++)
{
if (count1 == last) //最后一层写入目录名dname
{
int ii=0;
while (name[i]!='\0')
{
dname[ii++] = name[i++];
}
break; //必须要有break否则还会循环?????
}
else //必须有else
{
if (name[i] == '\\') //遇到\count加1
count1++;
if(count1<last)
pathname[i] = name[i]; //前几层全计入目录路径
}
}
if (strlen(dname)>3)
{
printf("文件名超过3个字符,新建目录失败\n");
return;
}
int d;
if (last==0)
{
d=1;
}
else
{
//调用函数 在磁盘上找有没有这个目录
d=FindContentDisk(pathname);
if (d == -1)
{
printf("指定的文件目录不存在,建立目录失败\n");
return;
}
//返回的d为这个目录所在的父目录的磁盘块的项 需要找到这一项的起始磁盘块
d = buffer2[d][6];
}
WriteBuffer2(d); //从磁盘上写入缓冲2
int fullflag = -1;
for (int i=0;i<8;i++) //找该页目录有没有这个目录和有没有空位
{
char bname[10]; //记录从磁盘上的目录名bname
bname[0] = buffer2[i][0]; //前三个字节为名
bname[1] = buffer2[i][1];
bname[2] = buffer2[i][2];
bname[3] = '\0'; //必须加\0
if (!strcmp(dname,bname)) //如果已经有了
{
printf("指定目录已存在,建立文件失败\n");
return ;
}
if(fullflag==-1 && bname[0] == '$') //找这一块磁盘的第一个空位
{
fullflag = i; //记录空位位置
}
}
if (fullflag==-1) //如果没有空位
{
printf("无目录项,建立失败\n");
return ;
}
int dd = AssignDisk(); //申请磁盘空位
if (!dd)
{
printf("磁盘没有空间,新建目录失败\n");
return;
}
FAT[dd] = 255;
//新建目录初始化为未用
FILE *fp = fopen("磁盘.txt","r+w"); //填写磁盘 初始化目录
char line[300];int count = 0;
while(fgets(line,1000,fp)) //让fp指向要初始化的行
{
count++;
if (count == dd*8) //1块有8行
{
break;
}
}
fseek(fp,0*sizeof(char), SEEK_CUR );
for (int i=0;i<8;i++)
fprintf(fp,"$ \n");
fclose(fp);
//填写缓冲2的目录信息 最后写入磁盘
strcpy(buffer2[fullflag],dname); //目录名
buffer2[fullflag][3] = ' '; //目录未使用 2字节
buffer2[fullflag][4] =' ';
buffer2[fullflag][5] = '8'; //文件属性
buffer2[fullflag][6] = dd; //申请的起始盘块号 超过两位数表示的是子符 但还可以转化
buffer2[fullflag][7] ='0'; //不为空为1 未使用为空为0
buffer2[fullflag][8] ='\0';
WriteDisk(buffer2,d); //修改这个目录的目录
FAT[d] = 255; //255代表文件结束
printf("创建目录成功\n");
}
② 显示目录内容(dir)
显示目录内容首先要找到该目录,如果目录不存在,指令执行失败;如果存在,一项一项显示目录内容。显示目录内容的流程图如图2.27所示。
图2.27 显示目录内容的流程图
显示目录内容 代码:
③删除空目录(rd)
删除空目录首先要找到该目录,如果目录不存在,指令执行失败;如果存在,但是根目录或非空目录,,显示不能删除,操作失败;若是非空子目录,则删除其目录项并回收对应空间。删除空目录的过程和删除文件的过程相似,流程可参考文件的删除过程。
实验中定义了两个数组buffer1和buffer2模拟缓冲。
实验中首先系统初始化,包括建立文件c模拟磁盘、初始化磁盘FAT和根目录初始为空目录项,然后选择各个命令进行测试。
5.课外题
在上述基础上,将磁盘文件系统改进为支持多级树型目录,支持相对路径,子目录可以任意长的文件系统。再添加文件的拷贝、移动指令和非空目录的删除指令。
代码解释:
在关闭文件 删除文件 改变文件属性 删除空目录时调用了int FindDisk(char name[]);函数,返回这个文件的目录所在的盘块号 修改目录需要调用这个函数
FindDisk函数代码:
cpp
//返回这个文件的目录所在的盘块号 修改目录需要调用这个函数 关闭文件 删除文件 改变文件属性 删除空目录时调用
int FindDisk(char name[])
{
int last = 0;char pathname[20]={"-"};//,lastpath[15]={"-"};
for(int i=0;name[i]!='\0';i++)
{
if (name[i]=='\\') //计数 有多少\就有多少层
last++;
}
int count1=0; int i=0; //count1为写入了多少层
for (;last>count1 && name[i]!='\0';i++)
{
if (name[i] == '\\') //遇到\ count加1
count1++;
if(count1<last)
pathname[i] = name[i]; //前几层全计入目录路径
}
//pathname[i+1] = '\0';没有也对
int pathdnum;
if (last == 0) //如果文件在根目录 修改根目录
return 1;
else
pathdnum= FindContentDisk(pathname); //返回的 pathnum为buffer2这一块第几行 为下一层目录
pathdnum = buffer2[pathdnum][6]; //第7个字节为该目录的起始盘号
return pathdnum;
}
表示缓冲区的数据写入第i块磁盘的函数为void WriteDisk(char buffer[8][8],int d),对磁盘文件进行读操作时,需要磁盘的一个盘块读入主存后才能进行处理,对磁盘文件进行写操作时,要写满缓冲后才写入磁盘。所以模拟文件操作时,不能将整个模拟磁盘的内容同时读入主存,应该当需要模拟磁盘的某个盘块内容时,从对应文件中读出;修改后需要写回模拟磁盘。实验中就是用这种方法模拟磁盘的输入输出。
WriteDisk函数代码:
这里面先解释一下我对磁盘(.txt)的划分,一共有128块,一块有8行,1行有8个字节,在读取和写入时都是取8行,所以不能有多余的换行符来把格式打乱,这样后面的操作都会乱,所以写入时需要在前7行末尾加上\n,最后1行不需要,而且缓冲里面不能有回车符,在初始化时已将所有回车符打入,这样方便后续操作哦
fprintf函数在写入文件时,会把指针的位置覆盖,并且会把回车当成一个字符,所以初始化磁盘时也必须有8个字符和末尾分回车符
cpp
//表示缓冲区的数据写入第i块磁盘
void WriteDisk(char buffer[8][8],int d)
{
FILE *fp = fopen("磁盘.txt","r+"); //填写磁盘
char line[300];int count = 0;
while(fgets(line,300,fp))
{
count++;
if (count == d*8) //1块有8行
break;
}
fseek(fp,0*sizeof(char), SEEK_CUR ); //必须要有
for (int i=0;i<7;i++)
{
for (int j=0;j<8;j++)
fprintf(fp,"%c",buffer[i][j]);
fprintf(fp,"\n");
//fwrite(buffer2[i],sizeof(buffer2),1,fp);
}
fprintf(fp,"%s",buffer[7]);
fclose(fp);
}
WriteBuffer2函数:
从磁盘上读入缓冲2函数 void WriteBuffer2(int i) ;其中参数i为第几块磁盘,在读取时需要用fgets使读指针下移动到对用的行,fgets会读取回车符并读入到line数组里面,而gets不会把末尾的回车符读入数组里面,所以用的gets
cpp
//从磁盘上读入缓冲2
void WriteBuffer2(int i)
{
FILE *fp = fopen("磁盘.txt","r"); //打开磁盘
char line[500]; //没用的空间 用来循环找到第i块
int count = 0;
while(fgets(line,500,fp))
{
count++;
if (count == i*8) //1块有8行
{
break;
}
}
for (int ii=0;ii<8;ii++) //找到后把每一行赋值给缓冲区buffer2
{
fgets(buffer2[ii],100,fp);
//fscanf(fp,"%s",buffer2[ii]);
}
fclose(fp);
}
磁盘(.txt)的举例说明
aaa、bbb、为目录名 8 为目录属性 后面的为盘块号的ascii码 最后0没有用
abcaa为在aaa目录下的文件名和类型4为写的属性 后面为文件起始盘块号的ascii码,最后为文件字符个数+1的ascii码再后面为回车
下面的bbb为目录aaa下的目录
磁盘(.txt)的举例说明
下面这副图为1个文件写入数据后的图片,每一行有8个字符
所有代码:
cpp
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#define n 5
int FAT[128];
char buffer2[8][9];
char buffer1[8][9];
typedef struct
{
int dnum;
int bnum;
}pointer;
typedef struct
{
char name[20];
char attribute;
int number;
int length;
int flag;
pointer read;
pointer write;
}OFILE;
struct
{
OFILE file[n];
int length;
}openfile; //文件登记表
//openfile.length = 0;报错
//分配磁盘空间 没有修改FAT表
int AssignDisk()
{
for (int i=1;i<128;i++)
{
if (FAT[i] == 0)
return i;
}
}
//表示缓冲区的数据写入第i块磁盘
void WriteDisk(char buffer[8][9],int d)
{
FILE *fp = fopen("磁盘.txt","r+"); //填写磁盘
char line[300];int count = 0;
while(fgets(line,300,fp))
{
count++;
if (count == d*8) //1块有8行
break;
}
fseek(fp,0*sizeof(char), SEEK_CUR ); //必须要有
for (int i=0;i<7;i++)
{
for (int j=0;j<8;j++)
fprintf(fp,"%c",buffer[i][j]);
fprintf(fp,"\n");
//fwrite(buffer2[i],sizeof(buffer2),1,fp);
}
fprintf(fp,"%s",buffer[7]);
fclose(fp);
}
//从磁盘上读入缓冲2
void WriteBuffer2(int i)
{
FILE *fp = fopen("磁盘.txt","r"); //打开磁盘
char line[500]; //没用的空间 用来循环找到第i块
int count = 0;
while(fgets(line,500,fp))
{
count++;
if (count == i*8) //1块有8行
{
break;
}
}
for (int ii=0;ii<8;ii++) //找到后把每一行赋值给缓冲区buffer2
{
fgets(buffer2[ii],100,fp);
//fscanf(fp,"%s",buffer2[ii]);
}
fclose(fp);
}
//从磁盘上读入缓冲1
void WriteBuffer1(int i)
{
FILE *fp = fopen("磁盘.txt","r"); //打开磁盘
char line[1000]; //没用的空间 用来循环找到第i块
int count = 0;
while(fgets(line,1000,fp))
{
count++;
if (count == i*8) //1块有8行
{
break;
}
}
for (int ii=0;ii<8;ii++) //找到后把每一行赋值给缓冲区buffer2
fgets(buffer1[ii],100,fp);
fclose(fp);
}
//查找磁盘中的文件 或 目录 找的过程中为从磁盘里找出1块放缓存,从缓存里匹配是不是 该函数不记录在文件登记表里
int FindContentDisk(char name[])
{
/*char *token; //把name拆分为二维数组
char *delimiter = "\\"; //一个\的字符识别不出来 需要加一个
token = strtok(name, delimiter);
while (token != NULL)
{
strcpy(pna[last],token);
token = strtok(NULL, delimiter);
last++;
}
for (int i=0;pna[0][i]!='\0';i++)//第0项为空?没有加\0
printf("%c%d",pna[0][i],i);*/
//i记录起始盘块号 初始为根目录所在盘块号 last记录到最后文件名或目录名有多层
int i = 1,last = 0,num=0;
char pna[5][10]; //拆分后分别存到每一层里
for (int j=0;name[j]!='\0';j++)
{
if (name[j]=='\\')
{
pna[last][num] = '\0';
num=0;
last++;
}
else
pna[last][num++] = name[j];
}
pna[last][num] = '\0'; //必须加\0
//标志有没有找到 返回值为flag即缓存区中的第几行 即该块的第几项
int flag = -1;
for (int t=0;t<last+1;t++) //总的循环次数为层数
{
flag=-1;
WriteBuffer2(i); //把第i块写入缓冲2
for (int j=0;j<8;j++) //在缓冲区里找哪一行 如果是文件只有1行
{
char bname[10],type[10]; int attribute;
//sscanf(buffer2[j],"%s %s %d %d",bname,type,attribute,i);
bname[0] = buffer2[j][0]; //前三个字节为名
bname[1] = buffer2[j][1];
bname[2] = buffer2[j][2];
bname[3] = '\0'; //必须加\0
type[0] = '.';
type[1] = buffer2[j][3]; //类型名 或 空格
type[2] = buffer2[j][4];
attribute = buffer2[j][5]-'0'; //属性
i = buffer2[j][6]; //把起始盘块号赋值给i 不需要 -'0'
if(attribute != 8) //目录属性字节为8 文件的目录需要把文件名和类型名合并
{
strcat(bname,type); //将文件名和类型拼接
}
//printf("bname=%s pna = %s",bname,pna[t]);
if (!strcmp(bname,pna[t]))
{
flag = j;
break;
}
}
if (flag==-1)
{
printf("查找的不存在\n");
return -1;
}
}
return flag; //返回找到的在第几行
}
//在打开的文件表里找文件 没有返回-1 找到返回第几个
int FindOpenFile(char name[])
{
for (int i=0;i<openfile.length;i++)
{
if (!strcmp(openfile.file[i].name,name))
{
return i;
}
}
return -1;
}
//在打开的文件表里插入 成功返回1 失败返回0
int insertFile(char name[],char attribute,int number,int length,int flag,int dnum,int bnum)
{
if(FindOpenFile(name)!=-1)
{
printf("文件已打开");
return 0;
}
if (openfile.length == 5)
{
printf("文件表已满,插入失败\n");
return 0;
}
strcpy(openfile.file[openfile.length].name ,name);
openfile.file[openfile.length].attribute = attribute;
openfile.file[openfile.length].number = number;
openfile.file[openfile.length].length = length;
openfile.file[openfile.length].flag = flag;
if (!flag) //flag为0代表只读
{
openfile.file[openfile.length].read.dnum = dnum;
openfile.file[openfile.length].read.bnum = bnum;
}
else
{
openfile.file[openfile.length].write.dnum = dnum;
openfile.file[openfile.length].write.bnum = bnum;
}
openfile.length++;
return 1;
}
//在打开的文件表里删除 成功返回1 失败返回0 在关闭文件时调用
int DeleteOpenFile(char name[])
{
for (int i=0;i<openfile.length;i++)
{
if (!strcmp(name,openfile.file[i].name))
{
openfile.file[i] = openfile.file[openfile.length-1];
openfile.length--;
return 1;
}
}
return 0;
}
//在磁盘中创建空的文件
void create_file(char name[],char attribute)
{
if ((attribute-'0')%2) //只读文件奇数
{
printf("此文件为只读文件,不能创建\n");
return;
}
if (openfile.length == 5)
{
printf("已打开文件表已满,建立文件失败\n");
return;
}
//分离name pathname为目录路径 type为类型
int last = 0;char pathname[20],dname[10],type[10];
for(int i=0;name[i]!='\0';i++)
{
if (name[i]=='\\') //计数 有多少\就有多少层
last++;
}
int count1=0; //count1为写入了多少层
for (int i=0;name[i]!='\0';)
{
if (count1 == last) //最后一层写入文件名dname和类型type
{
int ii=0;
//pathname[i] = '\0'; 不加也对?????
if(name[i]=='\\')
i++;
while (name[i]!='.')
dname[ii++] = name[i++];
// dname[ii] = '\0';
i++;
ii=0;
while(name[i]!='\0')
type[ii++] = name[i++];
// type[ii] = '\0';
//break;
}
else
{
if(count1<last)
{
pathname[i] = name[i]; //前几层全计入目录路径
i++; //i++不能写在[]里面!!!!!!!!
}
if (name[i] == '\\') //遇到\ count加1
count1++;
}
}
if(strlen(dname)!=3 || strlen(type)!=2)
{
printf("输入的文件名或类型格式不对\n");
return;
}
//有有没有对应的目录,并找到最后1层目录的盘块号
int d;
if(!last) //文件在根目录
{
d=1;
}
else
{ //!!!d前不能加int 否则出了循环d乱码
d=FindContentDisk(pathname); //调用函数 在磁盘上找有没有这个目录
if (d==-1)
{
printf("指定的文件目录不存在,建立文件失败\n");
return;
}
d = buffer2[d][6]; //最后1层目录的起始盘块号
}
//找该页目录有没有这个文件和有没有空位
WriteBuffer2(d); //从磁盘上写入缓冲2
int fullflag = -1;
for (int i=0;i<8;i++)
{
char bname[10],two[10]; //记录从磁盘上的文件名bname和属性two
bname[0] = buffer2[i][0]; //前三个字节为名
bname[1] = buffer2[i][1];
bname[2] = buffer2[i][2];
bname[3] = '\0';
two[0] = buffer2[i][3]; //类型名
two[1] = buffer2[i][4];
two[2] = '\0';
if (!strcmp(dname,bname) && !strcmp(two,type)) //如果已经有这个文件了
{
printf("指定文件已存在,建立文件失败\n");
return ;
}
if(fullflag==-1 && bname[0] == '$') //找这一块磁盘的第一个空位
{
fullflag = i; //记录空位位置
}
}
if (fullflag==-1) //如果没有空位
{
printf("该目录没有空位,创建文件失败\n");
return ;
}
//申请磁盘空位
int dd = AssignDisk(); //调用时已修改fat表
if (!dd)
return;
if(!insertFile(name,attribute,dd,0,1,dd,0)) //认为新建的文件为空 新建文件为写的类型
{
//printf("文件表已满,新建文件失败\n");
return;
}
FAT[dd] = 255; //文件表没有满再修改FAT表 255代表文件结束
//填写缓冲2的目录 最后写入磁盘
strcpy(buffer2[fullflag],dname); //文件名
strcat(buffer2[fullflag] , type); //文件类型
strcat(buffer2[fullflag] , &attribute); //文件属性 字符要加地址
buffer2[fullflag][6] = dd; //申请的起始盘块号
buffer2[fullflag][7]=1; //文件长度为0 因为0的ascii码为\0会打乱磁盘格式 用1代替
buffer2[fullflag][8]='\0';
WriteDisk(buffer2,d); //修改目录写入磁盘
//初始化文件
buffer2[0][0] = '#';
strcpy(buffer2[0],"# \0");
for (int i=1;i<8;i++)
strcpy(buffer2[i]," \0");
/*buffer2[0][1] = '\n';buffer2[0][2] = '\0';
buffer2[1][0] = '\n';buffer2[1][1] = '\0';
buffer2[2][0] = '\n';buffer2[2][1] = '\0';*/
WriteDisk(buffer2,dd); //把新建的文件追加结束符并写入磁盘
printf("新建文件成功\n");
return;
}
//在磁盘中创建空的目录
void create_content(char name[])
{
//分离name pathname为目录路径
int last = 0;char pathname[20],dname[10];
printf("%s\n",name);
for(int i=0;name[i]!='\0';i++)
{
if (name[i]=='\\') //计数 有多少\就有多少层
last++;
}
int count1=0; //count1为写入了多少层
for (int i=0;name[i]!='\0';i++)
{
if (count1 == last) //最后一层写入目录名dname
{
int ii=0;
while (name[i]!='\0')
{
dname[ii++] = name[i++];
}
break; //必须要有break否则还会循环?????
}
else //必须有else
{
if (name[i] == '\\') //遇到\count加1
count1++;
if(count1<last)
pathname[i] = name[i]; //前几层全计入目录路径
}
}
if (strlen(dname)>3)
{
printf("文件名超过3个字符,新建目录失败\n");
return;
}
int d;
if (last==0)
{
d=1;
}
else
{
//调用函数 在磁盘上找有没有这个目录
d=FindContentDisk(pathname);
if (d == -1)
{
printf("指定的文件目录不存在,建立目录失败\n");
return;
}
//返回的d为这个目录所在的父目录的磁盘块的项 需要找到这一项的起始磁盘块
d = buffer2[d][6];
}
WriteBuffer2(d); //从磁盘上写入缓冲2
int fullflag = -1;
for (int i=0;i<8;i++) //找该页目录有没有这个目录和有没有空位
{
char bname[10]; //记录从磁盘上的目录名bname
bname[0] = buffer2[i][0]; //前三个字节为名
bname[1] = buffer2[i][1];
bname[2] = buffer2[i][2];
bname[3] = '\0'; //必须加\0
if (!strcmp(dname,bname)) //如果已经有了
{
printf("指定目录已存在,建立文件失败\n");
return ;
}
if(fullflag==-1 && bname[0] == '$') //找这一块磁盘的第一个空位
{
fullflag = i; //记录空位位置
}
}
if (fullflag==-1) //如果没有空位
{
printf("无目录项,建立失败\n");
return ;
}
int dd = AssignDisk(); //申请磁盘空位
if (!dd)
{
printf("磁盘没有空间,新建目录失败\n");
return;
}
FAT[dd] = 255;
//新建目录初始化为未用
FILE *fp = fopen("磁盘.txt","r+w"); //填写磁盘 初始化目录
char line[300];int count = 0;
while(fgets(line,1000,fp)) //让fp指向要初始化的行
{
count++;
if (count == dd*8) //1块有8行
{
break;
}
}
fseek(fp,0*sizeof(char), SEEK_CUR );
for (int i=0;i<8;i++)
fprintf(fp,"$ \n");
fclose(fp);
//填写缓冲2的目录信息 最后写入磁盘
strcpy(buffer2[fullflag],dname); //目录名
buffer2[fullflag][3] = ' '; //目录未使用 2字节
buffer2[fullflag][4] =' ';
buffer2[fullflag][5] = '8'; //文件属性
buffer2[fullflag][6] = dd; //申请的起始盘块号 超过两位数表示的是子符 但还可以转化
buffer2[fullflag][7] ='0'; //不为空为1 未使用为空为0
buffer2[fullflag][8] ='\0';
WriteDisk(buffer2,d); //修改这个目录的目录
FAT[d] = 255; //255代表文件结束
printf("创建目录成功\n");
}
//从磁盘里找到文件并插入到文件表里
void open_file(char name[],char operat)
{
int t = FindContentDisk(name);
if(t==-1)
{
return;
}
int attribute;int length,dnum,number,bnum=0;
//sscanf(buffer2[t],"%s %s %d %d %d",bname,type,attribute,dnum,length); //把起始盘块号赋值给i 字符直接转化为数字 不用减'0'
attribute = buffer2[t][5]-'0'; //属性
dnum = buffer2[t][6];
length = buffer2[t][7]-1; //写的时候加1了 返回需要减1 实际没有用到
if ((attribute)%2 && operat=='w' || !((attribute)%2) && operat == 'r') //只读文件奇数
{
printf("操作不合法,无法打开文件\n");
return;
}
int flag = 0;
number = dnum;
if (operat == 'w')
{
flag = 1;
bnum = length%64; //写的文件bnum指向末尾
while (FAT[dnum] != 255)
{
dnum = FAT[dnum]; //找到末尾磁盘块
}
}
insertFile(name,attribute,number,length,flag,dnum,bnum);
printf("文件已打开\n");
}
//在文件表里找文件并读 如果多次操作并且没有关闭文件 将会继续读而不是从头读
void read_file(char name[],int length) //length 为读取长度
{
int loc=FindOpenFile(name); //查找打开的文件表
if(loc==-1)
{
printf("文件未打开,无法读文件\n");
return;
}
if (openfile.file[loc].flag)
{
printf("此文件为写,不能读\n");
return ;
}
WriteBuffer1(openfile.file[loc].read.dnum); //将这个文件的起始盘块号写入缓冲1
int line=openfile.file[loc].read.bnum/8;int t=0;
while(t<length) //当t小于要读的长度时一直循环
{
if (buffer1[line][openfile.file[loc].read.bnum%8]=='#') //如果文件结束强制结束循环
{
printf("读文件结束\n");
return ;
}
else
{
printf("%c",buffer1[line][openfile.file[loc].read.bnum%8]); //没有输出一行后面的回车
t++;
openfile.file[loc].read.bnum++;
if(openfile.file[loc].read.bnum>7 && openfile.file[loc].read.bnum%8==0) //读到第8个字符换行
line++;
if(line == 8) //这一块内容读完 找下一块
{
openfile.file[loc].read.bnum = 0;
openfile.file[loc].read.dnum = FAT[openfile.file[loc].read.dnum];
WriteBuffer1(openfile.file[loc].read.dnum);
line=0;
}
}
}
printf("读文件结束\n");
return ;
}
//在文件表里找文件并写 函数结束后所有都已经写入磁盘并且追加了文件结束符
void write_file(char name[],char buff[],int length)
{
int loc=FindOpenFile(name);
if(loc==-1)
{
printf("文件未打开,无法写文件\n");
return;
}
if (!openfile.file[loc].flag)
{
printf("此文件为读,不能读\n");
return ;
}
WriteBuffer1(openfile.file[loc].write.dnum);
int line = openfile.file[loc].write.bnum / 8; //计算当前指针在缓冲区的第几行
int t = 0;
while (t<length)
{
buffer1[line][openfile.file[loc].write.bnum % 8] = buff[t++]; //openfile.file[loc].write bnum % 8为在这一行的第几列 一开始bnum指的位置就可以写
openfile.file[loc].write.bnum++; //写完之后在加1
if (openfile.file[loc].write.bnum >7 && !(openfile.file[loc].write.bnum %8)) //当这一行即满8个字节时
{
buffer1[line][8] = '\n'; //每一行加一个回车符
line++;
if(line==8) //当这个磁盘写满后
{ //fseek(fp,((openfile.file[loc].write.dnum)*9+1)*sizeof(char), SEEK_SET); //从该文件的块开始写 一次写1行 即8字节 每一行还有1个回车
//申请新的磁盘空间
int dd = AssignDisk();
if(dd)
{
WriteDisk(buffer1,openfile.file[loc].write.dnum); //把写满的缓冲1写入磁盘
FAT[openfile.file[loc].write.dnum] = dd;
FAT[dd] = 255;
WriteDisk(buffer1,openfile.file[loc].write.dnum);
openfile.file[loc].write.dnum = dd;
WriteBuffer1(openfile.file[loc].write.dnum);
openfile.file[loc].write.bnum = 0;
}
else
{
buffer1[7][7]='#';buffer1[7][8]='\0'; //把最后一个字符强制换为文件结束符
WriteDisk(buffer1,openfile.file[loc].write.dnum); //把写满的缓冲1写入磁盘
openfile.file[loc].length+=t;
printf("磁盘满,不能继续写");
return ;
}
line = 0;
}
}
}
openfile.file[loc].length += length;
buffer1[line][openfile.file[loc].write.bnum % 8] = '#'; //最后追加文件结束符写入磁盘
//printf("line=%d openfile.file[loc].write.bnum=%d\n",line,openfile.file[loc].write.bnum);
if ((openfile.file[loc].write.bnum+1) %8)
{ //如果这一行没有结束,把buffer1后面的清空
int loc = openfile.file[loc].write.bnum+1;
while(loc %8)
{
buffer1[line][(loc) % 8] = ' ';
loc++;
}
buffer1[line][loc] = '\0';
}
//行数加1 把buffer1下面的初始化 因为写入磁盘是一块一块的写入
line++;
while(line<8)
{
strcpy(buffer1[line]," \0");
//buffer1[line][1] = '\0';
line++;
}
WriteDisk(buffer1,openfile.file[loc].write.dnum); //把最后写的写入磁盘
printf("写文件结束\n");
return;
}
//返回这个文件的目录所在的盘块号 修改目录需要调用这个函数 关闭文件 删除文件 改变文件属性 删除空目录时调用
int FindDisk(char name[])
{
int last = 0;char pathname[20]={"-"};
for(int i=0;name[i]!='\0';i++)
{
if (name[i]=='\\') //计数 有多少\就有多少层
last++;
}
int count1=0; int i=0; //count1为写入了多少层
for (;last>count1 && name[i]!='\0';i++)
{
if (name[i] == '\\') //遇到\ count加1
count1++;
if(count1<last)
pathname[i] = name[i]; //前几层全计入目录路径
}
pathname[i+1] = '\0';//没有也对???
int pathdnum;
if (last == 0) //如果文件在根目录 修改根目录
return 1;
else
pathdnum= FindContentDisk(pathname); //返回的 pathnum为buffer2这一块第几行 为下一层目录
pathdnum = buffer2[pathdnum][6]; //第7个字节为该目录的起始盘号
return pathdnum;
}
//关闭文件
void close_file(char name[])
{
int openloc = FindOpenFile(name);
if (openloc==-1)
{
printf("文件未打开,无法关闭\n");
return;
}
if(openfile.file[openloc].flag)//如果操作是写 只需修改目录
{
int pathdnum = FindDisk(name); //找到目录所在盘块号
int d = FindContentDisk(name); //把该目录的盘块存入了缓冲区2 并返回第几行
// printf("\nlength+1=%d\n",openfile.file[openloc].length+1);
buffer2[d][7] = openfile.file[openloc].length+1; //修改目录里文件长度 ASCII码为0代表\0 会把格式打乱
WriteDisk(buffer2,pathdnum); //把第pathdnum块的第d行写入磁盘
}
if(DeleteOpenFile(name));
printf("关闭文件成功\n");
return;
}
//删除文件
void delete_file(char name[])
{
int t = FindContentDisk(name);
if(t ==-1)
{
printf("文件不存在,无法删除文件\n");
return;
}
int fileloc = FindOpenFile(name);
if (fileloc != -1)
{
printf("文件正在使用,不能删除\n");
return;
}
int lastdnum=buffer2[t][6];
//修改目录需要找到这个目录的盘块号 并修改目录
int pathdnum = FindDisk(name);
WriteBuffer2(pathdnum) ; //把该目录的盘块存入了缓冲区2
strcpy(buffer2[t],"$ \0"); //修改目录
WriteDisk(buffer2,pathdnum); //把第pathdnum块 写入磁盘
//把文件初始化
t = lastdnum;
while(t!=255)
{
/*buffer2[0][0]='\n';buffer2[1][0]='\n';buffer2[2][0]='\n';buffer2[3][0]='\n';
buffer2[0][1]='\0';buffer2[1][1]='\0';buffer2[2][1]='\0';buffer2[3][1]='\0';*/
for (int i=0;i<8;i++)
strcpy(buffer2[i]," \0");
WriteDisk(buffer2,t);
t = FAT[t];
FAT[lastdnum] = 0; //修改FAT表
lastdnum = t;
}
printf("删除文件结束\n");
return;
}
//显示文件
void typefile(char name[])
{
int t = FindContentDisk(name);
if(t ==-1)
{
printf("文件不存在,无法显示文件\n");
return;
}
int fileloc = FindOpenFile(name);
if (fileloc != -1)
{
printf("文件正在使用,不能显示\n");
return;
}
int dnum = buffer2[t][6];
while(dnum != 255)
{
WriteBuffer2(dnum);
for (int i=0;i<8;i++)
{
for (int j=0;j<8;j++)
if (buffer2[i][j] == '#')
{
printf("\n文件显示结束\n");
return;
}
else
printf("%c",buffer2[i][j]); //不能显示空格
}
dnum = FAT[dnum];
}
}
//改变文件属性
void chage(char name[],char attribute)
{
int t = FindContentDisk(name);
if(t ==-1)
{
printf("文件不存在,无法改变文件属性\n");
return;
}
int fileloc = FindOpenFile(name);
if (fileloc != -1)
{
printf("文件正在使用,不能改变文件属性\n");
return;
}
int dnum = FindDisk(name);
WriteBuffer2(dnum);
buffer2[t][5] = attribute;
WriteDisk(buffer2,dnum);
printf("修改属性完成\n");
return;
}
//显示目录内容
void dir(char name[])
{
int t = FindContentDisk(name);
if(t ==-1)
{
printf("文件不存在,无法显示文件\n");
return;
}
int dnum = buffer2[t][6];
WriteBuffer2(dnum);
for (int i=0;i<8;i++)
{
if ( buffer2[i][0] == '$')
{
printf("null\n");
continue;
}
else
{
if(buffer2[i][5]=='8')
printf("目录名:%c%c%c属性值:%c 磁盘块:%d\n",
buffer2[i][0],buffer2[i][1],buffer2[i][2],
buffer2[i][5],buffer2[i][6]);
else
printf("文件名:%c%c%c,类型:%c%c,属性值:%c,起始盘块号:%d,长度:%d\n",
buffer2[i][0],buffer2[i][1],buffer2[i][2],
buffer2[i][3],buffer2[i][4],
buffer2[i][5],buffer2[i][6],buffer2[i][7] );
}
}
}
//删除空目录 因为输入的时候省略了根目录 所以不用判断是否为根目录
void rd(char name[])
{
int t = FindContentDisk(name);
if(t ==-1)
{
printf("目录不存在,无法删除\n");
return;
}
int dnum = buffer2[t][6];
WriteBuffer1(dnum); //把该目录的盘块存入了缓冲区2
for (int i=0;i<8;i++)
{
if ( buffer1[i][0] != '$')
{
printf("此目录非空\n");
return;
}
}
for (int i=0;i<8;i++)
strcpy(buffer1[i]," \0");
FAT[dnum] = 0;
WriteDisk(buffer1,dnum); //把第pathdnum块 写入磁盘
//把存放该目录的目录初始化
int lastdnum = FindDisk(name); //调用了writebuffer2
WriteBuffer2(lastdnum);
strcpy(buffer2[t],"$ \0"); //修改目录
WriteDisk(buffer2,lastdnum);
printf("删除目录结束\n");
return;
}
int main()
{
FILE *fp;
fp = fopen("磁盘.txt","w");
fprintf(fp,"%d %d",-1,-1); //把FAT表存到了磁盘的第一行 前两项为-1
for (int i=2;i<128;i++)
{//磁盘块位置和文件长度用ASCII码表示的 这几个ascii码在txt文本里会打乱格式 故显示磁盘损坏
if (i==8 || i==10||i==11||i==12||i==26)
fprintf(fp," %d",254); //254代表磁盘块损坏不能用
else
fprintf(fp," %d",0); //后面初始化为未用
}
for (int i=0;i<8;i++) //第一块还剩7行 输入8个回车
fprintf(fp,"\n");
for (int i=0;i<8;i++) //根目录初始化为未用
//!!!!!!回车符前面必须8个字符!!!!
fprintf(fp,"$ \n");
for (int i=0;i<8*126;i++) //保证格式统一 初始化剩下的126个磁盘块
fprintf(fp," \n");
fclose(fp); //关闭后在打开才能读
fp = fopen("磁盘.txt","r"); //把磁盘第一行放入FAT表中
for (int i=0;i<128;i++)
{
fscanf(fp,"%d",&FAT[i]);
}
fclose(fp); //关闭文件
openfile.length=0;
int operation;
do{
char name[20],attribute; //name[200],dname乱码? 因为上面create_content没有break;
printf("\n请选择数字\n1.创建空的文件\t2.创建空的目录\n3.写文件\t4.关闭文件\n5.显示文件\t6.改变文件属性\n");
printf("7.打开文件\t8.读打开的文件\n9.删除文件\t10.显示目录\n");
printf("11.删除目录\t12.显示已打开的文件绝对路径名\n");
scanf("%d",&operation);
switch(operation)
{
case 1:
printf("请输入文件绝对路径名(3个字符) 和 文件属性(写:4)写都为4 中间以空格分隔\n");
scanf(" %s %c",name,&attribute);
create_file(name,attribute);
break;
case 2:
printf("请输入目录绝对路径名,每个名字3个字符,只有1个默认在根目录创建 \n");
scanf(" %s",name );
create_content(name);
break;
case 3:
printf("请输入文件绝对路径名(3个字符)按回车结束\n");
scanf(" %s",name);
printf("请输入要写的内容,最多126个字符按回车结束\n");
char buff[126];
getchar();
gets(buff);
int length=strlen(buff);
if (strlen(buff)==7 || strlen(buff)==9||strlen(buff)==10||strlen(buff)==25)
{//文件关闭时会把字符个数加1存 8 10 11 会回车打乱格式 26也不行
printf("不能输入[7 9 10 25]个字符,请见谅");
continue;
}
write_file(name,buff,length);
break;
case 4:
printf("请输入需要关闭文件的绝对路径名\n");
scanf(" %s",name);
close_file(name);
break;
case 5:
printf("请输入需要显示文件的绝对路径名\n");
scanf(" %s",name);
typefile(name);
break;
case 6:
printf("请输入文件的绝对路径名 属性(只读为3)中间空格分隔\n");
scanf(" %s %c",name,&attribute); //输入什么文件显示什么
chage(name,attribute);
break;
case 7:
printf("请输入文件绝对路径名 操作(w写r读)\n");
char operat;
scanf(" %s %c",name,&operat);
open_file(name,operat);
break;
case 8:
printf("请输入文件的绝对路径 和 要读的长度\n");
scanf(" %s %d",name,&length);
read_file(name,length);
break;
case 9:
printf("请输入需要删除文件的绝对路径名\n");
scanf(" %s",name);
delete_file(name);
break;
case 10:
printf("请输入需要显示目录的绝对路径名\n");
scanf(" %s",name);
dir(name);
case 11:
printf("请输入需要删除目录的绝对路径名\n");
scanf(" %s",name);
rd(name);
case 12:
for (int i=0;i<openfile.length;i++)
printf("%s\n",openfile.file[i].name);
}
}while(1);
}
(本来以为3天就能完成的,结果解决1个问题又遇到1个问题ጿ ኈ ቼ ዽ ጿ ኈ ቼ ዽ ጿ ኈ ቼ ዽ ጿ ኈ ቼ ዽ ጿ ኈ ቼ,最后的代码可能还有点小的瑕疵,比如写的字符个数虽然在输入的时候限制了,但是却可以1个1个输入,然后再关闭文件时如果正好为对应的字符个数,可能会出问题,还有FAT表并不能存在磁盘的第一行,因为早已超出8个字符,即8个字节,然后这个程序运行结束后再运行,磁盘里的内容都会格式化,还有一些输出的提醒没有太严格区分( ๑❛ᴗ❛๑ ) ( ❛ัᴗ❛ั⁎ )end)