模拟文件系统

       先来将一下总的设计,然后再结合具体的函数进行详细的分析。

       首先,创建一个大约100M的文件作为模拟的硬盘。硬盘的空间总共分为三个部分:超级块区,inode区和磁盘块区。其中超级块区就是一个struct结构,其中保存了inode区和磁盘块区的使用情况。inode区则由1024inode块组成。一个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;
}


  • 14
    点赞
  • 67
    收藏
    觉得还不错? 一键收藏
  • 6
    评论
评论 6
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值