Nachos操作系统-文件系统添加多级目录

多级目录设置

前面提到了,当前的文件系统中并没有完成对于多级目录的设置。

为了更好的了解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<

创建目录及向目录中添加文件

为了不破坏原本Nachos的参数系统,这里想要通过对添加文件时的文件名进行检查,通过检查来确定是否创建目录

即:如果新生成/创建的文件的文件名形如./***/findName

那么应该首先检查当前目录下是否存在要生成的目录,如果存在,即进入目录下。如果不存在,就根据前者的名字创建目录,然后再进入目录下。

根据当前Nachos文件系统中创建文件的规则,可以发现它主要是在../lab5/ftest.cc中的函数Copy中调用了fileSystem->Create(to,fileLength)函数完成了对于文件的创建。

Nachos操作系统-文件系统添加多级目录_第1张图片
为了检查是否需要创建目录,需要在当前的目录中进行检查,来检测目录是否已经存在。

因此这里有一个目录与文件的标识和区分问题,即我们检测目录名时,目录名存在的条件为:同名且类型为目录

根据实验四中得知的目录与文件的相关知识,可以知道目录的目录项中包含三个部分:bool inUseint sectorchar* 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结果:

Nachos操作系统-文件系统添加多级目录_第2张图片

修改函数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
}

构造函数修改:

Nachos操作系统-文件系统添加多级目录_第3张图片

  • 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;iFetchFrom(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;iFetchFrom(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操作系统-文件系统添加多级目录_第4张图片

  • 通过./nachos -ld ./dir/ttt查看ttt目录下的内容

    请添加图片描述
    通过./nachos -p ./dir/ttt/small尝试输出文件内容:
    Nachos操作系统-文件系统添加多级目录_第5张图片

  • 通过./nachos -r ./dir/ttt/small尝试删除文件,可以看到此时的ttt目录下已经没有文件了
    Nachos操作系统-文件系统添加多级目录_第6张图片

    再通过./nachos -D进一步查看内容,与删除前相比,位图中的第11,12块被释放,说明文件small已经成功删除
    Nachos操作系统-文件系统添加多级目录_第7张图片

  • 通过./nachos -rd ./dir删除目录及其子目录,可以发现,目录文件的inUse项被设置为了0。同时位图也释放了。删除成功!

    Nachos操作系统-文件系统添加多级目录_第8张图片

  • 类文件树的输出测试:

    重新创建一个DISK进行后续测试。

    通过几个命令,使得整体目录结构为:

    • 根目录下包含文件small,目录dir
    • 目录dir下包含文件file,目录tttttt2
    • 目录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操作系统-文件系统添加多级目录_第9张图片

    ./nachos -ld ./dir
    

    Nachos操作系统-文件系统添加多级目录_第10张图片

感受

整个实验相对复杂的地方在于对于文件系统的扩展。为了实现文件系统的扩展,需要对文件的创造和删除非常熟悉,还需要对目录文件和整个文件系统存储的结构有比较深得了解。扩展文件系统的过程中对诸多函数都产生了更改,并且加入了新的Nachos参数和相关函数。函数之间的相互调用错综复杂,对于函数运行中可能出现的错误都要尽量考虑周到,例如创建OpenFile类对象的时候,一定要先检查构造函数需要的参数sector的正负情况!编写新的函数时一定要仔细考虑好将该函数添加到哪个类中,作为类的成员函数。函数添加到这个类中还有没有复用性更高的方案?能否将这个函数独立出来以供使用?一系列问题都是在构建大型项目时需要仔细考虑的。

虽然非常麻烦,但是程序成功运行时的快乐也是无与伦比的!

你可能感兴趣的:(操作系统,c++,架构,学习,其他)