多级目录设置
前面提到了,当前的文件系统中并没有完成对于多级目录的设置。
为了更好的了解Nachos,这里尝试向当前的文件系统中添加代码以完成多级目录的设置。
总览
对于多级目录来说,与其相关的操作主要有这几个:
- 创建目录
- 删除目录
- 向目录中添加文件
- 从目录中删除文件
- 展示目录及目录中的文件内容
为了便于代码的编写,我定义一个宏CntDirectLevel
代表最多拥有的目录层级
接下来对各自的实现进行设计与分析:
通用函数
在这里创建一个通用函数bool ParseFileName(char* name,char** &dirs)
,并将其存储在单独的目录ParseFN.h
中。
函数功能即判断档期按输入的文件名是否在一个目录下。如果是,返回TRUE,并将各级目录名存储在dirs
中。如果不是,返回FALSE。
具体实现如下:
bool ParseFileName(char* name,char**& dirs)
{
if(name[0]=='.' && name[1]=='/')
{
dirs=new char*[10];
int DirLevInd=0;
for(int i=1;name[i]!='\0';)
{ //check the '/'
if(name[i]=='/')
{
cout<<i<<endl;
i++;
int index=0;
dirs[DirLevInd]=new char[10];
// get the name of directory[DirLevInd-1]
for(;name[i]!='\0' && name[i]!='/';i++)
{
cout<<name[i];
dirs[DirLevInd][index++]=name[i];
}
cout<<"\t";
dirs[DirLevInd][index]='\0';
DirLevInd++;
}
}
dirs[DirLevInd]=nullptr;
// DirLevInd is cnt of FileLevel
return true;
}
else
{
return false;
}
}
创建目录及向目录中添加文件
为了不破坏原本Nachos的参数系统,这里想要通过对添加文件时的文件名进行检查,通过检查来确定是否创建目录。
即:如果新生成/创建的文件的文件名形如./***/findName
那么应该首先检查当前目录下是否存在要生成的目录,如果存在,即进入目录下。如果不存在,就根据前者的名字创建目录,然后再进入目录下。
根据当前Nachos文件系统中创建文件的规则,可以发现它主要是在../lab5/ftest.cc
中的函数Copy
中调用了fileSystem->Create(to,fileLength)
函数完成了对于文件的创建。
为了检查是否需要创建目录,需要在当前的目录中进行检查,来检测目录是否已经存在。
因此这里有一个目录与文件的标识和区分问题,即我们检测目录名时,目录名存在的条件为:同名且类型为目录
根据实验四中得知的目录与文件的相关知识,可以知道目录的目录项中包含三个部分:bool inUse
,int sector
,char* fileName
。注意到inUse
变量的类型为bool,理论只占用一个bit
,但是实际使用中会发现改位置占用了不止一个字节:
根据这个特点,如果我在结构中声明inUse
之后创建一个bool类型的变量direct
,就可以做到基本不修改表目录项的内容且可以保证目录表的大小不会有什么变化,维护了原本Nachos 系统的健壮性。
尝试结果如下:
其中前者代表inUse
,后者代表direct
。
数据结构确定后,就开始修改函数。对于创建目录,所有需要修改的内容如下:
-
directory.h
在类
DirectoryEntry
中添加成员变量bool direct
用来标明当前记录是否为目录文件。在类
Directory
中添加成员函数bool Add(char *name,int newSector,bool isDirect)
;
对类
Directory
中成员函数的修改:-
使用默认形参的方式不会破坏原本的函数调用语法,并且还可以添加新的调用方式
-
int Find(char *name,bool isDirect=false)
-
int FindIndex(char *name,bool isDirect=false)
-
构造函数的修改(置默认的
direct
为FALSE)
-
directory.cc
添加的成员函数
Add
的实现:bool Directory::Add(char* name,int newSector,bool isDirect) { if(isDirect) { // the directory exists if (FindIndex(name,isDirect) != -1) return FALSE; // create a new directory for (int i = 0; i < tableSize; i++) if (!table[i].inUse) { table[i].inUse = TRUE; table[i].direct=isDirect; strncpy(table[i].name, name, FileNameMaxLen); table[i].sector = newSector; return TRUE; } return FALSE; // no space. Fix when we have extensible files. } else { printf("Directory::Add : Unexpected Call of Function!\n"); Abort(); return FALSE; } }
修改函数
FindIndex
结果:
修改函数Find
:
int
Directory::FindIndex(char *name,bool isDirect)
{
for (int i = 0; i < tableSize; i++)
if (table[i].inUse && !strncmp(table[i].name, name, FileNameMaxLen)
&& (table[i].direct == isDirect))
return i;
return -1; // name not in directory
}
构造函数修改:
-
filesys.h
-
添加函数
bool CreateDir(char** dirs,int fileLength)
其中
dirs
是存放着每一层目录名的二维数组fileLength
只代表最后的文件的文件大小。通过这个函数逐层创建目录以及最后创建文件。
-
添加成员函数
Open(char **dirs)
-
添加私有成员函数
Directory* createDir(char* name,Directory* in)
意味着在目录
in
中创建一个名称为name
的目录并返回创建的这个目录。 -
添加私有成员函数
bool createFile(char* name,Directory* in,int fileLength,OpenFile* in_file)
-
-
filesys.cc
-
CreateDir
函数的实现循环对每层目录进行如下操作:
-
调用函数
createDir(dirs[i],pre,preFile)
,代表着在目录pre中创建名称为dirs[i]
的目录,对应的文件通过引用传递存储在preFile
中。为了进行传递,需要先查找当前目录是否存在,如果存在就返回当前目录的指针,但是如果下一级目录不存在,还要用到当前目录的存储位置,因为通过创建目录以后,当前目录发生修改,需要进行写回操作。
-
检查是否创建成功。
到最后一层时应当进行文件的创建。
因此直接调用私有成员函数
createFile(dirs[i],pre,fileLength,preFile)
即向目录
pre
中写入名称为dirs[i]
,大小为fileLength
的文件,然后将更改后的文件写回preFile
。最后代码如下:
bool FileSystem::CreateDir(char** dirs,int fileLength) { int i=0; Directory* pre=new Directory(NumDirEntries); pre->FetchFrom(directoryFile); OpenFile* preFile=directoryFile; for(;dirs[i+1]!=NULL;i++) { pre=createDir(dirs[i],pre,preFile); if(pre==NULL) { printf("CreateDir: Unable to create directory %s\n",dirs[i]); return false; } } // shoule chreate a file return createFile(dirs[i],pre,fileLength,preFile); }
-
-
Open
的实现(实际上是对原有成员函数Open(char* name)
的重载)传入一串目录和最终的文件名,返回最后的文件名对应文件的
OpenFile
类指针OpenFile * FileSystem::Open(char **dirs) { Directory *directory = new Directory(NumDirEntries); OpenFile *openFile = directoryFile; int sector,i; for(i=0;dirs[i+1]!=NULL;i++) { DEBUG('f', "Opening directory %s\n", dirs[i]); directory->FetchFrom(openFile); sector=directory->Find(dirs[i],true); // printf("Find Directory %s Header in %d\n",dirs[i],sector); if(sector<0) return NULL; openFile=NULL; openFile=new OpenFile(sector); // get the openfile of next level directory } DEBUG('f', "Opening File %s\n", dirs[i]); directory->FetchFrom(openFile); sector = directory->Find(dirs[i]); // printf("Find File %s Header in %d\n",dirs[i],sector); if (sector < 0) openFile=NULL; else openFile = new OpenFile(sector); // name was found in directory delete directory; return openFile; // return NULL if not found }
-
私有函数
createDir
的实现首先检查目录
in
中是否已经存在想要创建的目录了。-
如果存在,返回这个目录。
-
如果不存在,创建这个目录,返回目录。
// make directory in the directory `in` // in_file means current dir `in`'s file,when return ,it means the storage file of return Directory Directory* FileSystem::createDir(char* name,Directory* in,OpenFile* &in_file) { BitMap *freeMap; FileHeader *hdr; int sector; bool success; int dirFileH=0; if((dirFileH=in->Find(name,true))!=-1) {// the directory exists in current Direcoty in_file=new OpenFile(dirFileH); Directory* ret; ret=new Directory(NumDirEntries); ret->FetchFrom(in_file); return ret; } else {// we should create a file DEBUG('f',"Creating Directory %s \n",name); Directory* ret; freeMap=new BitMap(NumSectors); freeMap->FetchFrom(freeMapFile); sector=freeMap->Find(); if(sector==-1) ret=NULL; else if(!in->Add(name,sector,true)) ret=NULL; else { hdr=new FileHeader; if(!hdr->Allocate(freeMap,DirectoryFileSize)) ret=NULL; else { success=TRUE; hdr->WriteBack(sector); in->WriteBack(in_file); freeMap->WriteBack(freeMapFile); ret=new Directory(NumDirEntries); in_file=new OpenFile(sector); ret->FetchFrom(in_file); } delete hdr; } delete freeMap; return ret; } }
-
-
私有成员函数
createFile
的实现总体上参考了函数
Create
。也就是先检查文件是否存在,不存在则创建文件,对应修改目录和位图等等。bool FileSystem::createFile(char* name,Directory *in,int fileLength,OpenFile* in_file) { Directory *directory; BitMap *freeMap; FileHeader *hdr; int sector; bool success; DEBUG('f', "Creating file %s, size %d\n", name, fileLength); // make file in the directory `in` directory = in; if (directory->Find(name) != -1) success = FALSE; // file is already in directory else { freeMap = new BitMap(NumSectors); freeMap->FetchFrom(freeMapFile); sector = freeMap->Find(); // find a sector to hold the file header if (sector == -1) success = FALSE; // no free block for file header else if (!directory->Add(name, sector)) success = FALSE; // no space in directory else { hdr = new FileHeader; if (!hdr->Allocate(freeMap, fileLength)) success = FALSE; // no space on disk for data else { success = TRUE; // everthing worked, flush all changes back to disk hdr->WriteBack(sector); directory->WriteBack(in_file); freeMap->WriteBack(freeMapFile); } delete hdr; } delete freeMap; } delete directory; return success; }
-
-
fstest.cc
-
更改函数
void Copy(char* from,char* to)
当通过从Linux向Nachos复制文件时,需要检查Nachos中的文件名是否为目录下的文件。
因此先调用
ParseFileName
函数。如果是普通文件,则正常进行,
如果是目录名,通过调用的函数就得到了多级目录的各自名称。
然后直接调用上面提到的
CreateDir
进行创建:这里只给出关键的部分代码:
char** dirs; if(ParseFileName(to,dirs)) { if(!fileSystem->CreateDir(dirs,fileLength)) { printf("Copy: couldn't create output file %s\n", to); fclose(fp); return; } openFile=fileSystem->Open(dirs); } else //normal files //...
-
删除目录及删除目录中文件
这里的文件删除Nachos系统本身具有的删除非常相似。通过将目录项中的inUse
项设置为False
,同时修改位图,就算删除了文件。
但是这里还包括了对于目录的删除,为了加以区分,这里通过添加新的Nachos指令rd
标识remove directory删除目录文件。
-
删除文件
涉及到的函数修改主要是
filesys.cc
中的Remove
函数要做到删除目录下的文件,就需要找到文件所在的最后一层目录,将最后一层目录的目录项中对应文件的
inUse
设置为FALSE;程序修改如下:
这里只给出关键部分代码,后续代码基本与原
Remove
函数一致char** dirs; if(ParseFileName(name,dirs)) { int i=0; for(i=0;dirs[i+1]!=NULL;i++) { sector=directory->Find(dirs[i],true); if(sector==-1){ delete dirFile; delete directory; return FALSE; } dirFile=new OpenFile(sector); directory->FetchFrom(dirFile); } // current directory is the last level of directory // and dirs[i] is the name of the file name=dirs[i]; }
-
删除目录
首先要明确一点,即删除目录时,目录下的文件也要随之删除。
因此可以采用递归的方式。删除当前文件前,先删除掉当前文件下的所有文件和目录,再删除当前目录。
首先进行查找,找到要删除目录的上一级目录。然后调用函数
dir->Clear()
将该目录下的所有文件和目录清空,然后参考Remove
函数将目录dir
从当前目录中删除。对于命令
-rd
的处理,采用如下方法:-
函数
RemoveDir
的实现:void FileSystem::RemoveDir(char* name) { // printf("Entry\n"); char **dirs; OpenFile *dirFile=directoryFile; Directory *dir=new Directory(NumDirEntries); dir->FetchFrom(dirFile); int i=0; int sector=0; if(!ParseFileName(name,dirs)) { printf("You Should give a directory but not a file name\n"); return ; } for(i=0;dirs[i+1]!=NULL;i++) { sector=dir->Find(dirs[i]); if(sector<0){ printf("Cann't Find Directory %s\n",dirs[i]); return; } dirFile=new OpenFile(sector); dir->FetchFrom(dirFile); } // printf("Ready to CLear!\n"); sector=dir->Find(dirs[i],true); if(sector<0){ printf("RemoveDir: Unable to Find the directory %s\n",dirs[i]); } OpenFile* delFile=new OpenFile(sector); Directory* delDir=new Directory(NumDirEntries); delDir->FetchFrom(delFile); // clear the content of directory dirs[i] delDir->Clear(freeMapFile,delFile,NumDirEntries); FileHeader* fileHdr = new FileHeader; fileHdr->FetchFrom(sector); BitMap* freeMap = new BitMap(NumSectors); freeMap->FetchFrom(freeMapFile); fileHdr->Deallocate(freeMap); // remove data blocks freeMap->Clear(sector); // remove header block if(!dir->Remove(dirs[i],true)) printf("RemoveDir: Unable to Remove directory %s\n",dirs[i]); freeMap->WriteBack(freeMapFile); // flush to disk dir->WriteBack(dirFile); // flush to disk delete dir; delete fileHdr; delete freeMap; delete delDir; delete delFile; delete dirFile; }
-
函数
Clear
的实现:bool Directory::Clear(OpenFile* freeMapFile,OpenFile* curFile,int n) { FileHeader* fileHdr=new FileHeader; BitMap* freeMap=new BitMap(NumSectors); freeMap->FetchFrom(freeMapFile); OpenFile* delFile; Directory* delDir=new Directory(n); for(int i=0;i<n;i++) { if(table[i].inUse) { if(table[i].sector<0) return FALSE; if(table[i].direct) {//delete a directory delFile=new OpenFile(table[i].sector); delDir->FetchFrom(delFile); delDir->Clear(freeMapFile,delFile,n); } fileHdr->FetchFrom(table[i].sector); fileHdr->Deallocate(freeMap); freeMap->Clear(table[i].sector); Remove(table[i].name); } } freeMap->WriteBack(freeMapFile); this->WriteBack(curFile); delete fileHdr; delete freeMap; return true; }
-
展示目录以及目录中的文件内容
这里以Nachos系统中的指令作为区分。
-
首先是对于Nachos相关参数中的**
-p
参数。**调用了
../fstest.cc
中的Print(char *name)
函数,输出名称为name
的文件的内容。通过阅读函数内部可以知道,这里是直接调用了
FileSystem::open(char* name)
用来获取文件的文件句柄。为了满足多层级目录的设置,在这里调用通用函数
ParseFileName
进行判断。- 如果只是单个文件,则直接输出。
- 如果在子目录下,通过多级目录的名称找到文件后再输出。
实现如下:
void Print(char *name) { OpenFile *openFile; int i, amountRead; char *buffer; char** dirs; if(ParseFileName(name,dirs)) { if((openFile=fileSystem->Open(dirs))==NULL){ printf("Print: unable to open file %s\n", name); return; } } else { if ((openFile = fileSystem->Open(name)) == NULL) { printf("Print: unable to open file %s\n", name); return; } } buffer = new char[TransferSize]; while ((amountRead = openFile->Read(buffer, TransferSize)) > 0) for (i = 0; i < amountRead; i++) printf("%c", buffer[i]); delete [] buffer; delete openFile; // close the Nachos file return; }
-
对于参数
-l
,这里对参数设置进行添加,即设置参数-ld
,意为list directory。在处理参数的界面添加以下代码:
else if(!strcmp(*argv,"-ld")) { ASSERT(argc>1); fileSystem->ListDir(*(argv+1)); }
-
对
../lab5/filesys.h
添加函数:void ListDir(char* name)
-
对
../lab5/filesys.cc
函数
ListDir
的实现功能为传入目录名,然后找到最后一个目录名对应的目录,输出目录下的所有文件名和目录名。首先解析文件名,得到一连串的目录名。然后逐层寻找目录中的下一级目录文件。
void FileSystem::ListDir(char* name) { char** dirs; if(ParseFileName(name,dirs)) { OpenFile* curFile=directoryFile; Directory* dir=new Directory(NumDirEntries); dir->FetchFrom(curFile); int sector; for(int i=0;dirs[i]!=NULL;i++) { if((sector=dir->Find(dirs[i],true))<0) { printf("ListDir: Unable to find directory:%s\n",dirs[i]); return; } curFile=new OpenFile(sector); dir->FetchFrom(curFile); } dir->List(); delete curFile; delete dir; return; } else { delete dirs; printf("Unable to List file \n"); } }
-
为了增加区分度,对List时调用的函数
../lab5/directory.cc->Directory::List()
进行修改:输出目录时颜色设置为蓝色。
为了将目录下的子目录内容和文件都进行输出,这里对List函数进行改进,更改为递归输出。
为了输出时可以比较明显地看出层次结构,这里通过更改缩进地方式来完成对于缩进的改进。为了表现这一点,就需要确认产生的缩进层次,这一点通过更改
List
函数的传入参数确定。即令其传入参数为int
类型数字n
,代表输出文件名前要输出的\t
的个数。为了不改变原有的函数功能,默认
n=0
void Directory::List(int cntTable) { // printf the table space to show the level char TableSpace[cntTable+1]; for(int i=0;i<cntTable;i++) TableSpace[i]='\t'; TableSpace[cntTable]='\0'; for (int i = 0; i < tableSize; i++) { if (table[i].inUse) { if(table[i].direct) {// Directory Directory* dir=new Directory(10); OpenFile* dirFile=new OpenFile(table[i].sector); // printf("FetchBefore!\n"); dir->FetchFrom(dirFile); printf("%s\033[34m\033[1m%s\033[0m\n",TableSpace,table[i].name); // printf("ListBefore!\n"); dir->List(cntTable+1); delete dir; delete dirFile; } else {// normal Files printf("%s%s\n",TableSpace,table[i].name); } } } }
-
-
对于参数
-D
,注意到对于参数-D
,产生调用时会调用根目录的函数Print
为了在输出时更好地区分调用内容,这里采用对输出内容进行着色的方式进行输出。
函数更改如下(只贴关键的代码):
for (int i = 0; i < tableSize; i++) { if (table[i].inUse && !table[i].direct) { printf("\033[0mName: %s, Sector: %d\n", table[i].name, table[i].sector); hdr->FetchFrom(table[i].sector); hdr->Print(); } else if(table[i].inUse && table[i].direct) { printf("\033[34m\033[1mDirectory: %s, Sector: %d\n",table[i].name,table[i].sector); hdr->FetchFrom(table[i].sector); hdr->Print(); printf("\033[0m"); } }
多级目录的测试
-
首先make。
-
然后通过
./nachos -f
生成DISK
磁盘文件 -
通过
./nachos -cp ./test/small ./dir/ttt/small
将Linux文件small
复制到Nachos
下的目录./dir/ttt
下。
-
通过
./nachos -ld ./dir/ttt
查看ttt目录下的内容
通过./nachos -p ./dir/ttt/small
尝试输出文件内容:
-
通过
./nachos -r ./dir/ttt/small
尝试删除文件,可以看到此时的ttt
目录下已经没有文件了
再通过
./nachos -D
进一步查看内容,与删除前相比,位图中的第11,12块被释放,说明文件small已经成功删除
-
通过
./nachos -rd ./dir
删除目录及其子目录,可以发现,目录文件的inUse
项被设置为了0。同时位图也释放了。删除成功! -
类文件树的输出测试:
重新创建一个DISK进行后续测试。
通过几个命令,使得整体目录结构为:
- 根目录下包含文件
small
,目录dir
- 目录
dir
下包含文件file
,目录ttt
和ttt2
- 目录
ttt
下包含一个文件file
- 目录
ttt2
下包含一个文件file2
命令如下:
./nachos -cp ./test/small small ./nachos -cp ./test/empty ./dir/file ./nachos -cp ./test/small ./dir/ttt/file ./nachos -cp ./test/empty ./dir/ttt2/file2
然后通过以下命令分别进行输出查看:
./nachos -l
./nachos -ld ./dir
- 根目录下包含文件
感受
整个实验相对复杂的地方在于对于文件系统的扩展。为了实现文件系统的扩展,需要对文件的创造和删除非常熟悉,还需要对目录文件和整个文件系统存储的结构有比较深得了解。扩展文件系统的过程中对诸多函数都产生了更改,并且加入了新的Nachos参数和相关函数。函数之间的相互调用错综复杂,对于函数运行中可能出现的错误都要尽量考虑周到,例如创建OpenFile类对象的时候,一定要先检查构造函数需要的参数sector
的正负情况!编写新的函数时一定要仔细考虑好将该函数添加到哪个类中,作为类的成员函数。函数添加到这个类中还有没有复用性更高的方案?能否将这个函数独立出来以供使用?一系列问题都是在构建大型项目时需要仔细考虑的。
虽然非常麻烦,但是程序成功运行时的快乐也是无与伦比的!