先来将一下总的设计,然后再结合具体的函数进行详细的分析。
首先,创建一个大约100M的文件作为模拟的硬盘。硬盘的空间总共分为三个部分:超级块区,inode区和磁盘块区。其中超级块区就是一个struct结构,其中保存了inode区和磁盘块区的使用情况。inode区则由1024个inode块组成。一个inode块对应一个目录文件或者普通文件,其中保存了对应文件的文件类型,文件大小,占用的磁盘块的数目,以及所占用的磁盘块块号。然后每次对文件的读写就通过改变相应的inode的内容以及对应的磁盘块的内容就可以了。最后是磁盘块区,每个磁盘块的大小是1KB。总共由1024*80个磁盘块构成,其实磁盘块并不需要特殊的数据结构进行标记,只要能找到对应的位置,然后在要进行读写的时候将其读入内存,进行操作后再放回硬盘即可。
接下来再来说说目录,其实目录就是一个目录文件,其中包含许多目录项,每个目录项由两部分组成:1.下级目录或是文件的文件名,2.该文件对应的i节点。需要注意的是,在创建一个子目录的时候,该子目录的目录文件的内容并不是空的,而是至少包含了两个目录项”.”和”..”。其中,文件名为”.”的目录项表示的是当前目录,文件名为”..”表示的是上层目录。当我们需要进入上层目录时,只要找到”..”目录项对应的inode即可。特别要注意的是,在根目录中”.”和”..”对应的inode号是一样的,这也为我们判断当前目录是否为根目录提供了条件。
然后是普通文件的操作。普通文件的创建其实比目录文件的创建要简单,因为普通文件在开始的时候可以是空的。因此只要读取相应的inode结构并初始化就可以了。至于对文件的编辑,由于自己懒得再写一个编辑器了,因此就机智地调用了vim的接口。首先,另外创建一个buff.txt文件,当要对文件进行编辑的时候,读取该文件的磁盘块的内容,放到buff.txt中,然后调用一个子进程,使用vim对其进行编辑。在编辑完成之后,又将buff.txt文件的内容写回相应的文件中,这样对文件的编辑工作也就搞定了。
最后是文件的删除工作。普通文件的删除工作较为简单,只要读取相应的inode,把它所有的磁盘块释放,最后再释放它本身就可以了。其实所谓的释放,就是把超级块里面的磁盘块位图或是inode位图的相应位置置0就可以了,并没有什么复杂的操作。不过,目录的删除工作相对比较复杂,因为一个目录中通常是包含其他文件的。所以在Linux系统中,当你要删除一个目录的时候,它通常会提醒你该目录不为空,不让你删除。不过,在这个模拟文件系统中,为了简单起见,就不管目录是否为空了,直接将所包含的内容全部删除。这其实是这个系统中最难操作的部分。使用的方法是递归删除,当遇到的是目录时,则进入子目录,若遇到的文件,则将文件进行删除,如果当前目录下已经没有内容了,则返回上层目录。再进行了一个深度遍历之后,整个目录树就被删除了。需要注意的是,需要删除的根目录和它的子目录的操作是有所不同的,根目录不仅要删除自身,还要将其父目录做一个修改,因为包含它的名字和inode的目录项是存放在它的父目录的目录文件中的,而它自身包含的子目录则无需做这一步的修改,因为反正它的父目录也是要被删除的。这样,对文件的删除工作也搞定了,整个系统也就差不多完成了。
具体的代码实现如下:
#include <stdio.h>
#include <string.h>
#include <stdlib.h>
#include <unistd.h>
#define InodeNum 1024//i节点数目
#define BlkNum (80*1024)//磁盘块的数目
#define BlkSize 1024//磁盘块大小为1K
#define BlkPerNode 1024//每个文件包含的最大的磁盘块数目
#define DISK "disk.txt"
#define BUFF "buff.txt"//读写文件时的缓冲文件
#define SuperBeg 0//超级块的起始地址
#define InodeBeg sizeof(SuperBlk)//i节点区启示地址
#define BlockBeg (InodeBeg+InodeNum*sizeof(Inode))//数据区起始地址
#define MaxDirNum (BlkPerNode*(BlkSize/sizeof(Dir)))//每个目录最大的文件数
#define DirPerBlk (BlkSize/sizeof(Dir))//每个磁盘块包含的最大目录项
#define Directory 0
#define File 1
#define CommanNum (sizeof(command)/sizeof(char*))//指令数目
typedef struct{
int inode_map[InodeNum];//i节点位图
int blk_map[BlkNum];//磁盘块位图
int inode_used;//已被使用的i节点数目
int blk_used;//已被使用的磁盘块数目
}SuperBlk;
typedef struct{
int blk_identifier[BlkPerNode];//占用的磁盘块编号
int blk_num;//占用的磁盘块数目
int file_size;//文件的大小
int type;//文件的类型
}Inode;
typedef struct{
char name[30];//目录名
short inode_num;//目录对应的inode
}Dir;
Dir dir_table[MaxDirNum];//将当前目录文件的内容都载入内存
int dir_num;//相应编号的目录项数
int inode_num;//当前目录的inode编号
Inode curr_inode;//当前目录的inode结构
SuperBlk super_blk;//文件系统的超级块
FILE* Disk;
/*指令集合*/
char* command[]={"fmt","quit","mkdir","rmdir","cd","ls","mk","rm","vim"};
char path[40]="monster: root";
int init_fs(void);//初始化文件系统
int close_fs(void);//关闭文件系统
int format_fs(void);//格式化文件系统
int open_dir(int);//打开相应inode对应的目录
int close_dir(int);//保存相应inode的目录
int show_dir(int);//显示目录
int make_file(int,char*,int);//创建新的目录或文件
int del_file(int,char*,int);//删除子目录
int enter_dir(int,char*);//进入子目录
int file_write(char*);//写文件
int file_read(char*);//读文件
int adjust_dir(char*);//删除子目录后,调整原目录,使中间无空隙
int check_name(int,char*);//检查重命名,返回-1表示名字不存在,否则返回相应inode
int type_check(char*);//确定文件的类型
int free_inode(int);//释放相应的inode
int apply_inode();//申请inode,返还相应的inode号,返还-1则INODE用完
int init_dir_inode(int,int);//初始化新建目录的inode
int init_file_inode(int);//初始化新建文件的inode
int free_blk(int);//释放相应的磁盘块
int get_blk(void);//获取磁盘块
void change_path(char*);
int main()
{
char comm[30],name[30];
char *arg[]={"vim",BUFF,NULL};
int i,quit=0,choice,status;
Disk=fopen(DISK,"r+");
init_fs();
while(1){
printf("%s# ",path);
scanf("%s",comm);
choice=-1;
for(i=0;i<CommanNum;++i){
if(strcmp(comm,command[i])==0){
choice=i;
break;
}
}
switch(choice){
/*格式化文件系统*/
case 0: format_fs();
break;
/*退出文件系统*/
case 1: quit=1;
break;
/*创建子目录*/
case 2: scanf("%s",name);
make_file(inode_num,name,Directory);
break;
/*删除子目录*/
case 3: scanf("%s",name);
if(type_check(name)!=Directory){
printf("rmdir: failed to remove '%s': Not a directory\n",name);
break;
}
del_file(inode_num,name,0);
break;
/*进入子目录*/
case 4: scanf("%s",name);
if(type_check(name)!=Directory){
printf("cd: %s: Not a directory\n",name);
break;
}
if(enter_dir(inode_num,name)){
change_path(name);//改变路径前缀
}
break;
/*显示目录内容*/
case 5: show_dir(inode_num);
break;
/*创建文件*/
case 6: scanf("%s",name);
make_file(inode_num,name,File);
break;
/*删除文件*/
case 7: scanf("%s",name);
if(type_check(name)!=File){
printf("rm: cannot remove '%s': Not a file\n",name);
break;
}
del_file(inode_num,name,0);
break;
/*对文件进行编辑*/
case 8: scanf("%s",name);
if(type_check(name)!=File){
printf("vim: cannot edit '%s': Not a file\n",name);
break;
}
file_read(name);//将数据从文件写入BUFF
if(!fork()){
execvp("vim",arg);
}
wait(&status);
file_write(name);//将数据从BUFF写入文件
break;
default:
printf("%s command not found\n",comm);
}
if(quit) break;
}
close_fs();
fclose(Disk);
return 0;
}
int init_fs(void)
{
fseek(Disk,SuperBeg,SEEK_SET);
fread(&super_blk,sizeof(SuperBlk),1,Disk);//读取超级块
inode_num=0;//当前根目录的inode为0
if(!open_dir(inode_num)){
printf("CANT'T OPEN ROOT DIRECTORY\n");
return 0;
}
return 1;
}
int close_fs(void)
{
fseek(Disk,SuperBeg,SEEK_SET);
fwrite(&super_blk,sizeof(SuperBlk),1,Disk);
close_dir(inode_num);
return 1;
}
int format_fs(void)
{
/*格式化inode_map,保留根目录*/
memset(super_blk.inode_map,0,sizeof(super_blk.inode_map));
super_blk.inode_map[0]=1;
super_blk.inode_used=1;
/*格式化blk_map,保留第一个磁盘块给根目录*/
memset(super_blk.blk_map,0,sizeof(super_blk.blk_map));
super_blk.blk_map[0]=1;
super_blk.blk_used=1;
inode_num=0;//将当前目录改为根目录
/*读取根目录的i节点*/
fseek(Disk,InodeBeg,SEEK_SET);
fread(&curr_inode,sizeof(Inode),1,Disk);
// printf("%d\n",curr_inode.file_size/sizeof(Dir));
curr_inode.file_size=2*sizeof(Dir);
curr_inode.blk_num=1;
curr_inode.blk_identifier[0]=0;//第零块磁盘一定是根目录的
/*仅.和..目录项有效*/
dir_num=2;
strcpy(dir_table[0].name,".");
dir_table[0].inode_num=0;
strcpy(dir_table[1].name,"..");
dir_table[1].inode_num=0;
strcpy(path,"monster: root");
return 1;
}
int open_dir(int inode)
{
int i;
int pos=0;
int left;
fseek(Disk,InodeBeg+sizeof(Inode)*inode,SEEK_SET);
/*读出相应的i节点*/
fread(&curr_inode,sizeof(Inode),1,Disk);
// printf("%d\n",curr_inode.file_size);
for(i=0;i<curr_inode.blk_num-1;++i){
fseek(Disk,BlockBeg+BlkSize*curr_inode.blk_identifier[i],SEEK_SET);
fread(dir_table+pos,sizeof(Dir),DirPerBlk,Disk);
pos+=DirPerBlk;
}
/*left为最后一个磁盘块内的目录项数*/
left=curr_inode.file_size/sizeof(Dir)-DirPerBlk*(curr_inode.blk_num-1);
fseek(Disk,BlockBeg+BlkSize*curr_inode.blk_identifier[i],SEEK_SET);
fread(dir_table+pos,sizeof(Dir),left,Disk);
pos+=left;
dir_num=pos;
return 1;
}
int close_dir(int inode)
{
int i,pos=0,left;
/*数据写回磁盘块*/
for(i=0;i<curr_inode.blk_num-1;++i){
fseek(Disk,BlockBeg+BlkSize*curr_inode.blk_identifier[i],SEEK_SET);
fwrite(dir_table+pos,sizeof(Dir),DirPerBlk,Disk);
pos+=DirPerBlk;
}
left=dir_num-pos;
// printf("left:%d",left);
fseek(Disk,BlockBeg+BlkSize*curr_inode.blk_identifier[i],SEEK_SET);
fwrite(dir_table+pos,sizeof(Dir),left,Disk);
/*inode写回*/
curr_inode.file_size=dir_num*sizeof(Dir);
fseek(Disk,InodeBeg+inode*sizeof(Inode),SEEK_SET);
fwrite(&curr_inode,sizeof(curr_inode),1,Disk);
return 1;
}
/*创建新的目录项*/
int make_file(int inode,char* name,int type)
{
int new_node;
int blk_need=1;//本目录需要增加磁盘块则blk_need=2
int t;
if(dir_num>MaxDirNum){//超过了目录文件能包含的最大目录项
printf("mkdir: cannot create directory '%s' :Directory full\n",name);
return 0;
}
if(check_name(inode,name)!=-1){//防止重命名
printf("mkdir: cannnot create file '%s' :File exist\n",name);
return 0;
}
if(dir_num/DirPerBlk!=(dir_num+1)/DirPerBlk){//本目录也要增加磁盘块
blk_need=2;
}
// printf("blk_used:%d\n",super_blk.blk_used);
if(super_blk.blk_used+blk_need>BlkNum){
printf("mkdir: cannot create file '%s' :Block used up\n",name);
return 0;
}
if(blk_need==2){//本目录需要增加磁盘块
t=curr_inode.blk_num++;
curr_inode.blk_identifier[t]=get_blk();
}
/*申请inode*/
new_node=apply_inode();
if(new_node==-1){
printf("mkdir: cannot create file '%s' :Inode used up\n",name);
return 0;
}
if(type==Directory){
/*初始化新建目录的inode*/
init_dir_inode(new_node,inode);
}
else if(type==File){
/*初始化新建文件的inode*/
init_file_inode(new_node);
}
strcpy(dir_table[dir_num].name,name);
dir_table[dir_num++].inode_num=new_node;
}
/*显示目录内容*/
int show_dir(int inode)
{
int i,color=32;
for(i=0;i<dir_num;++i){
if(type_check(dir_table[i].name)==Directory){
/*目录显示绿色*/
printf("\033[1;%dm%s\t\033[0m", color,dir_table[i].name);
}
else{
printf("%s\t",dir_table[i].name);
}
if(!((i+1)%4)) printf("\n");//4个一行
}
printf("\n");
return 1;
}
/*申请inode*/
int apply_inode()
{
int i;
if(super_blk.inode_used>=InodeNum){
return -1;//inode节点用完
}
super_blk.inode_used++;
for(i=0;i<InodeNum;++i){
if(!super_blk.inode_map[i]){//找到一个空的i节点
super_blk.inode_map[i]=1;
return i;
}
}
}
int free_inode(int inode)
{
Inode temp;
int i;
fseek(Disk,InodeBeg+sizeof(Inode)*inode,SEEK_SET);
fread(&temp,sizeof(Inode),1,Disk);
for(i=0;i<temp.blk_num;++i){
free_blk(temp.blk_identifier[i]);
}
super_blk.inode_map[inode]=0;
super_blk.inode_used--;
return 1;
}
/*进入子目录*/
int enter_dir(int inode,char* name)
{
int child;
child=check_name(inode,name);
if(child==-1){//该子目录不存在
printf("cd: %s: No such file or directory\n",name);
return 0;
}
/*关闭当前目录,进入下一级目录*/
close_dir(inode);
inode_num=child;
open_dir(child);
return 1;
}
/*递归删除文件夹*/
int del_file(int inode,char* name,int deepth)
{
int child,i,t;
Inode temp;
if(!strcmp(name,".")||!strcmp(name,"..")){
/*不允许删除.和..*/
printf("rmdir: failed to remove '%s': Invalid argument\n",name);
return 0;
}
child=check_name(inode,name);
if(child==-1){//子目录不存在
printf("rmdir: failed to remove '%s': No such file or directory\n",name);
}
/*读取当前子目录的Inode结构*/
fseek(Disk,InodeBeg+sizeof(Inode)*child,SEEK_SET);
fread(&temp,sizeof(Inode),1,Disk);
if(temp.type==File){
/*如果是文件则释放相应Inode即可*/
free_inode(child);
/*若是最上层文件,需调整目录*/
if(deepth==0){
adjust_dir(name);
}
return 1;
}
else{
/*否则进入子目录*/
enter_dir(inode,name);
}
for(i=2;i<dir_num;++i){
del_file(child,dir_table[i].name,deepth+1);
}
enter_dir(child,"..");//返回上层目录
free_inode(child);
if(deepth==0){
/*删除自身在目录中的内容*/
if(dir_num/DirPerBlk!=(dir_num-1)/DirPerBlk){
/*有磁盘块可以释放*/
curr_inode.blk_num--;
t=curr_inode.blk_identifier[curr_inode.blk_num];
free_blk(t);//释放相应的磁盘块
}
adjust_dir(name);//因为可能在非末尾处删除,因此要移动dir_table的内容
}/*非初始目录直接释放Inode*/
return 1;
}
int adjust_dir(char* name)
{
int pos;
for(pos=0;pos<dir_num;++pos){
/*先找到被删除的目录的位置*/
if(strcmp(dir_table[pos].name,name)==0)
break;
}
for(pos++;pos<dir_num;++pos){
/*pos之后的元素都往前移动一位*/
dir_table[pos-1]=dir_table[pos];
}
dir_num--;
return 1;
}
/*初始化新建目录的inode*/
int init_dir_inode(int child,int father)
{
Inode temp;
Dir dot[2];
int blk_pos;
fseek(Disk,InodeBeg+sizeof(Inode)*child,SEEK_SET);
fread(&temp,sizeof(Inode),1,Disk);
blk_pos=get_blk();//获取新磁盘块的编号
temp.blk_num=1;
temp.blk_identifier[0]=blk_pos;
temp.type=Directory;
temp.file_size=2*sizeof(Dir);
/*将初始化完毕的Inode结构写回*/
fseek(Disk,InodeBeg+sizeof(Inode)*child,SEEK_SET);
fwrite(&temp,sizeof(Inode),1,Disk);
strcpy(dot[0].name,".");//指向目录本身
dot[0].inode_num=child;
strcpy(dot[1].name,"..");
dot[1].inode_num=father;
/*将新目录的数据写进数据块*/
fseek(Disk,BlockBeg+BlkSize*blk_pos,SEEK_SET);
fwrite(dot,sizeof(Dir),2,Disk);
return 1;
}
/*初始化新建文件的indoe*/
int init_file_inode(int inode)
{
Inode temp;
/*读取相应的Inode*/
fseek(Disk,InodeBeg+sizeof(Inode)*inode,SEEK_SET);
fread(&temp,sizeof(Inode),1,Disk);
temp.blk_num=0;
temp.type=File;
temp.file_size=0;
/*将已经初始化的Inode写回*/
fseek(Disk,InodeBeg+sizeof(Inode)*inode,SEEK_SET);
fwrite(&temp,sizeof(Inode),1,Disk);
return 1;
}
/*申请未被使用的磁盘块*/
int get_blk()
{
int i;
super_blk.blk_used++;
for(i=0;i<BlkNum;++i){//找到未被使用的块
if(!super_blk.blk_map[i]){
super_blk.blk_map[i]=1;
return i;
}
}
return -1;//没有多余的磁盘块
}
/*释放磁盘块*/
int free_blk(int blk_pos)
{
super_blk.blk_used--;
super_blk.blk_map[blk_pos]=0;
}
/*检查重命名*/
int check_name(int inode,char* name)
{
int i;
for(i=0;i<dir_num;++i){
/*存在重命名*/
if(strcmp(name,dir_table[i].name)==0){
return dir_table[i].inode_num;
}
}
return -1;
}
void change_path(char *name)
{
int pos;
if(strcmp(name,".")==0){//进入本目录则路径不变
return ;
}
else if(strcmp(name,"..")==0){//进入上层目录,将最后一个'/'后的内容去掉
pos=strlen(path)-1;
for(;pos>=0;--pos) {
if(path[pos]=='/') {
path[pos]='\0';
break;
}
}
}
else {//否则在路径末尾添加子目录
strcat(path,"/");
strcat(path,name);
}
return ;
}
int type_check(char* name)
{
int i,inode;
Inode temp;
for(i=0;i<dir_num;++i){
if(strcmp(name,dir_table[i].name)==0){
inode=dir_table[i].inode_num;
fseek(Disk,InodeBeg+sizeof(Inode)*inode,SEEK_SET);
fread(&temp,sizeof(Inode),1,Disk);
return temp.type;
}
}
return -1;//该文件或目录不存在
}
/*读文件函数*/
int file_read(char* name)
{
int inode,i,blk_num;
Inode temp;
FILE* fp=fopen(BUFF,"w+");
char buff[BlkSize];
//printf("read\n");
inode=check_name(inode_num,name);
fseek(Disk,InodeBeg+sizeof(Inode)*inode,SEEK_SET);
fread(&temp,sizeof(temp),1,Disk);
if(temp.blk_num==0){//如果源文件没有内容,则直接退出
fclose(fp);
return 1;
}
printf("read\n");
for(i=0;i<temp.blk_num-1;++i){
blk_num=temp.blk_identifier[i];
/*读出文件包含的磁盘块*/
fseek(Disk,BlockBeg+BlkSize*blk_num,SEEK_SET);
fread(buff,sizeof(char),BlkSize,Disk);
/*写入BUFF*/
fwrite(buff,sizeof(char),BlkSize,fp);
free_blk(blk_num);//直接将该磁盘块释放
temp.file_size-=BlkSize;
}
/*最后一块磁盘块可能未满*/
blk_num=temp.blk_identifier[i];
fseek(Disk,BlockBeg+BlkSize*blk_num,SEEK_SET);
fread(buff,sizeof(char),temp.file_size,Disk);
fwrite(buff,sizeof(char),temp.file_size,fp);
free_blk(blk_num);
/*修改inode信息*/
temp.file_size=0;
temp.blk_num=0;
/*将修改后的Inode写回*/
fseek(Disk,InodeBeg+sizeof(Inode)*inode,SEEK_SET);
fwrite(&temp,sizeof(Inode),1,Disk);
fclose(fp);
return 1;
}
/*写文件函数*/
int file_write(char* name)
{
int inode,i;
int num,blk_num;
FILE* fp=fopen(BUFF,"r");
Inode temp;
char buff[BlkSize];
inode=check_name(inode_num,name);
fseek(Disk,InodeBeg+sizeof(Inode)*inode,SEEK_SET);
fread(&temp,sizeof(Inode),1,Disk);
while(num=fread(buff,sizeof(char),BlkSize,fp)){
printf("num:%d\n",num);
if((blk_num=get_blk())==-1){
printf("error: block has been used up\n");
break;
}
/*改变Inode结构的相应状态*/
temp.blk_identifier[temp.blk_num++]=blk_num;
temp.file_size+=num;
/*将数据写回磁盘块*/
fseek(Disk,BlockBeg+BlkSize*blk_num,SEEK_SET);
fwrite(buff,sizeof(char),num,Disk);
}
/*将修改后的Inode写回*/
fseek(Disk,InodeBeg+sizeof(Inode)*inode,SEEK_SET);
fwrite(&temp,sizeof(Inode),1,Disk);
fclose(fp);
return 1;
}