文件系统实习报告.docx
- 文档编号:9536055
- 上传时间:2023-02-05
- 格式:DOCX
- 页数:14
- 大小:27.44KB
文件系统实习报告.docx
《文件系统实习报告.docx》由会员分享,可在线阅读,更多相关《文件系统实习报告.docx(14页珍藏版)》请在冰豆网上搜索。
文件系统实习报告
文件系统实习报告
姓名李炜学号1100012810
日期5月6日
内容一:
总体概述
这次实习都过扩展文件系统以及添加相应的系统调用,帮助我们理解文件在磁盘中实际的存储方式,目录、文件头和文件之间的关系,以及控制台和文件读写之间的关系。
概念主要涉及到了inode的实现方式,多级索引,多级目录等。
【用简洁的语言描述本次lab的主要内容;阐述本次lab中涉及到的重要的概念,技术,原理等,以及其他你认为的最重要的知识点。
这一部分主要是看大家对lab的总体的理解。
要求:
简洁,不需要面面俱到,把重要的知识点阐述清楚即可。
】
内容二:
任务完成情况
任务完成列表(Y/N)
Exercise1
Exercise2
Exercise3
Exercise4
Exercise5
Exercise6
第一部分
Y
Y
Y
Y
Y
第二部分
Y
Y
Y
第三部分
Y
Y
challenge
Y
N
N
Y
具体Exercise的完成情况
第一部分:
文件系统的基本操作
Exercise1:
Filesys.hfilesys.cc
从filesys.h的注释中,我们可以看到,nachos使用了两套文件系统,一个是基于unix(linux)本身的文件系统的类似于包装函数,另一个是需要我们实现的真正的文件系统,现在的nachos文件系统有很多限制,比如说,这个文件系统不支持多层的文件目录,而是只有root目录,而且由于系统启动的限制,标记磁盘使用情况的bitmap和目录分别在sector0,sector1。
这样就限制了可以使用的磁盘的大小和可以创建的文件的数量,就像其它大多数文件系统一样,nachos需要支持初始化文件系统,创建文件,打开文件,删除文件,打印目录中的文件,打印文件信息。
而这个文件系统中关键的数据结构就是前面所说的空闲区表,和根目录。
下面具体来看需要实现的这些函数的初步实现。
创建文件系统的时候首先查看是不是已经被“格式化”了,也就是是不是已经存在了文件系统,如果不存在的话,那么先要创建并初始化前面经常提到的bitmap和目录,而因为这两个数据结构本身也需要存储在文件中,所以首先标记宏定义的0号和1号sector是已经被占用了的,但是在nachos中实际上bitmap和根目录也是文件,同样需要文件头来管理,于是0号和1号实际上存储的分别是bitmap和根目录的文件头,而真正的bitmap和根目录需要文件头接着使用allocate函数来分配,并且writeback,向分配出的两个块写回到文件中,完成之后,打开这两个文件并将初始化后的bitmap和目录写入到这两个文件。
如果文件系统已经存在的话,那么直接new出两个结构即可,之后的操作可以直接使用文件中的信息了。
对于创建文件来说,步骤如下,首先确保同目录下不存在同名文件,之后为文件头分配磁盘块(只有一个块),为文件实际的数据分配磁盘空间,向目录中加入这个文件名,将文件头写入到磁盘中,刷新bitmap和目录信息到磁盘中的这两个文件中。
对于打开文件来说,只需要打开目录文件,找到具有给出的名字的文件,然后打开这个文件即可。
对于删除文件来说,需要从目录中去掉这一项,删掉文件头在磁盘上占用的块,删掉文件数据占据的块,将bitmap和目录的变化写回磁盘。
Filehdr.hfilehdr.cc
这两个文件和前面提到的文件头有关,也就是原理课上讲到过的i-node,从头文件中可以看出,文件的头存储的全部是直接索引(占用的文件块号),这就限定了文件的大小目前不能超过4K,而且还有一点很重要的是,每个文件头都只能占用一个块,这就限制了文件头中能够存储的文件的信息。
除此之外,文件头锁存储的信息十分有限,只有文件的大小(bytes)和占用的sector多少。
对于其中的功能来说,allocate通过向bitmap不断申请磁盘块(直到足够为止),来获得文件所需要的空间,而deallocate则正好相反,通过将datasectors中的所有磁盘块还原为未使用来释放空间。
其它函数都很简单,基本上是对synchdisk的包装函数,这里就不介绍了。
Directory.hdirectory.cc
对于目录来说,其实就是许多文件名和文件头所在块的组合,这个组合被定义为一个叫做DirectoryEntry的类,这个类中除了包含这两个信息之外,还包括目录项是否被使用。
而对于这个目录来说,最主要的数据结构就是table(DirectorEntry数组)。
下面来看具体的函数,对于构造函数来说,就是创建出上面所说的table,并且初始化。
FetchFrom的作用是从已经有了目录结构的文件中,把这个结构整体读到table中。
writeBack则相反,是将table的内容写入到一个文件中。
FindIndex是通过文件名来查找这个文件在table中的索引号。
Find则是通过先找到索引号,之后返回文件所对应的文件头的块号。
Add是通过检查目录中是否有重名文件或者目录是否仍有空间,如果是的话就把这个文件名-文件头块号对写入到table当中。
Remove十分简单,把table中对应的这一项的inUse设置成False(原来inUse是标记是不是有效的,现在才看出来)。
OpenFile.hopenFile.cc
和filesys一样,openfile也有两种实现,一种是unix的包装,令一种是需要我们自己去改进的简单的打开文件的方式。
可以看出,对于一个打开文件操作来说,最重要的是文件头和当前读到的位置,对应的就是hdr和seekPosition,下面来看具体的函数。
构造函数的作用是从一个sector中读入文件头,并且把读取位置设置成0。
Read和write差不多,都是从当前位置开始读制定长度的内容到into指向的位置,使读取位置这个值增加到读到的位置,并且返回真正读取到的字节数。
Readat函数将文件从指定的位置向后读指定长度(如果够长的话),但是不同的是读取文件位置的指不变,先是读文件到一个buf缓冲数组中,之后从buf中再复制到into指定的位置。
Exercise2:
扩展文件信息,我选择在文件头中添加文件信息(也可以在目录中添加),对于文件类型来说,我在filehdr中新定义了一个类叫Filedtype,为了节省空间,我只用了一个char来存储文件类型,按照从低位到高位的顺序,使用位操作来设置和获取信息,我定义第一位代表可读,第二位代表可写,第三位代表可执行,通过与&操作来获取信息,通过或|操作来设置信息。
对于创建时间来说,我认为给文件头allocate的时候算创建,于是我在allocate开始的地方加上createTime=stats->totalTicks,而对于lastModifyTime来说,我选择在writeback的里面写入类似的赋值语句,而lastReferTime则是在FetchFrom里面,后来在调试的时候发现如果创建之后就没有再读过,那么lastReferTime就会是0,这样不符合实际,所以我在allocate里面给三个时间都赋了值。
但是定义了这些属性之后还需要调整文件头中的dataSectors的大小,因为每个文件头都只占了一个sector,增加了额外的信息之后就会减少一个文件所能使用的块数。
对于路径来说,这个比较麻烦,实际上是我在完成了exercise3以后才回来完成的,我在文件头里面加入一个信息是父目录的文件头的块号,但是因为文件头里面并没有存储改文件的名字,名字在上级目录中,所以我向上找两级父目录,在这个祖父目录中通过父目录的块号来找到父目录的文件名(如果中间遇到了parentDire是-1的话,说明遇到了root,停止向上查找),为了实现这个功能,我在directory中加入了一个findName函数,这个函数通过匹配sector号来找到文件名。
在实现了多级目录之后,只要调用-l就可以打印出所有我定义在文件头中的信息,以及文件的父目录。
Exercise3:
这个练习要求通过使用间接索引来使得文件的大小突破4K,主要思路并不难想象,定义一个一级索引作为例子,在文件头中使用一个int型属性来指示一级索引所在的文件块,然后如果文件小于直接索引所能提供的大小,那么把这个间接索引值赋成无效的-1,否则在bitmap中找一个空闲块,赋给这个间接索引值,然后通过synchdisk的writesector把写回这个块即可。
想明白以后,最大的困难是不知道文件是怎么写的,因为之前看到过许多各种各样的writeback,有的是用块号,有的是用openfile。
参考文件头自己的writeback方法,它用的是synchdisk的writesector,这个函数接受一个块号作为地址,但是开始的时候我不明白的是为什么第二个参数,也就是写的内容是(char*)this,比较奇怪,后来想了很久明白了这里就是把这个对象的内容整体写到块中,而这个对象的内容就是类中的属性。
也就是说我可以把任何我想写的东西写入磁盘块中,只要大小不超过一个块的大小,而且必须包装成char*数组的形式,于是我定义一个int型数组来存放文件头中需要的磁盘块号,在分配或者修改的时候把这个数组写回磁盘即可。
在分配函数allocate中,我首先判断需要的大小是不是超过直接索引能够存储的范围,如果没有的话,皆大欢喜,直接使用原来的代码,否则,先给直接索引分配需要的磁盘块,之后给间接索引分配一个块,并且存储到间接索引这个属性中,然后创建一个前面所说的数组,大小就是Sectorsize/sizeof(int),这样,给需要的块找到相应的位置,其余的赋成-1,最后把这个数组写到找到的间接索引指向的块,大功告成。
测试的时候,我先用python制造了一个超过4K大小的文件叫huge,之后使用nachos-cp把这个文件复制到nachos中再-D显示内容,结果成功了,说明间接索引能够很好的工作。
Exercise4:
多级目录主要和directory有关,但是在实际实现的时候发现directory的功能实在是太弱了,仅靠directory无法实现多级目录,directory里面实际上与多级目录相关的是add这个函数,但是这个函数实际上也只是将文件名和sector号的这样一个对加入到directory对象的table属性中(如果没有重名的时候),而真正的创建目录等操作这里无法完成,也就是说得在其它地方创建好目录,甚至写到相应的磁盘块之后,这时向directory中加入这一项才有意义。
而且向directory中加入的只应该是对应文件头的块号(这一点我开始的时候没有按照这样实现,结果到了后来出现了很多矛盾)。
因为目录的fetchfrom和writeback针对的都是openfile,这个openfile需要的是文件头,也就是说directory里的fetchfrom需要通过目录文件头的块号来打开目录。
所以如果自己需要建目录的话,不仅需要写目录所在的块,还要创建并写入目录对应的文件头所在的块。
再看每个目录项中存储的内容,最开始的时候是inUse和文件名-块号对,为了区别目录中的目录项是文件还是目录,还需要加入是否是目录这个属性,为了节省空间,我同样使用位运算来标记inUse和isDire两个状态,即提供了get和set方法。
之后我想仿照创建文件并加入目录的方法,来创建目录并加入目录,首先来实验只加到根目录中,我的做法是修改filesys中的create,要求create的参数中包括要创建的是不是目录。
做法基本上照抄创建文件的方法,不同的是allocate的时候需要的大小是DirectoryFileSize(有宏定义好的),并且不仅要写回根目录、bitmap和文件头,还需要写回新创建的目录。
实验的时候创建一个目录dire,并且list出来,能够显示,但是当然是空的。
接下来就是考虑如何真正实现多级目录,也就是说怎么在我新创建的目录下创建目录或者添加文件呢,我想到了正在使用的linux在文件头中有.和..两个文件,..代表的是上一级目录(前面已经介绍了实现),那么就是说还需要支持.文件,也就是当前工作目录。
怎么办呢?
我在filesys中添加了workingDireFile这个openfile*类型的属性(因为bitmap和根目录也是通过这个属性存储的)。
在filesys初始化的时候把创建出来之后的根目录赋给工作目录(符合常理),在create的时候首先判断工作目录是不是Null,如果是的话,那么把根目录赋给工作目录,之后接下来的一切之前和根目录有关的操作,都改成使用工作目录。
在open和remove中也同样如此。
有了这些还不够,我们还需要支持改变工作目录,否则就不可能在新的目录下创建文件或者目录,于是我定义了一个gotoDirectory的函数,接受名字(目录或文件)作为参数,这个函数首先打开工作目录,在工作目录下找是否有具有这个名字的目录,如果有这个目录的话,那么把这个目录作为工作目录。
这样工作就基本完成了。
可是测试的时候要看到结果,于是我修改了print函数(主要是directory中的),这里主要就是判断是否是目录,如果不是目录,那么依旧使用原来的print,如果是目录的话,那么先打开这个目录,之后执行目录的print,这样即使有多层的目录也可以递归地打印出结果了,list的修改方式类似,也需要递归,不过在文件头层面上需要打印出文件中我自己定义的信息和父目录名,方法上面已经说过了。
Exercise5:
动态调整文件大小
在这个任务中,我主要做了将文件扩大的操作,也就是说如果当前文件的大小不够需要写入的内容的大小,那么我就根据另外需要的磁盘块的数量,给这个文件分配更多的磁盘块。
为了实现这个功能,我在filehdr中定义了一个函数叫做AllocateMore,这个函数接受两个参数,一个是另外需要的文件块数,另一个是实际需要的字节数。
在这个函数的实现过程中,我主要参考了allocate和create两个函数,要得到更多的空间,首先需要先得到标记磁盘空闲位置的bitmap,因为filesys里面有这个属性,所以直接读出freeMapFile这个属性并且通过它取出bitmap,然后判断直接索引中是不是还有剩余空间,之后判断直接索引是不是足够提供需要的磁盘块,如果是的话,那么把需要的磁盘块分配给这个文件,增加文件头中的磁盘块数到实际块数,写回bitmap,并且返回True。
之后如果直接索引不足以提供需要的磁盘块数,那么先把直接索引中剩余的块分配出去,之后再将间接索引中的块分配出去,直到满足需求为止。
其中,如果本身没有间接索引的话,还需要首先给这个间接索引分配一个块,最后的时候还要把这些都写回到磁盘中。
第二部分:
系统调用
Exercise6:
Syscall.hStart.sException.cc
这两个文件需要一起结合着看,syscall这个头文件可以说没有没有相对应的.cc文件,它对应的实际上是start.s文件,就我们关心的几个函数来说,create,open,close,read,write这几个函数都是在syscall里面定义了函数的signature,而在start.s中把这些函数对应的系统调用号SC_...放入二号寄存器中,使得exception里面的exceptionhandler可以识别出对应的系统调用类型,来进行相应的处理。
由于系统调用不能通过传统的方式传递参数,在start里面的注释中可以看出,4~7号寄存器可以用来传递参数,而2号寄存器在exception里面应该作为传递返回值的寄存器。
由于exception在用户线程部分就已经用过多次,这里就不介绍了。
Exercise7,8:
系统调用和调试
就像之前实现tlbmiss和pagefault的时候一样,这次也是主要修改exception中的exceptionhandler,其实有了前面的基础,这个练习相对简单,但是最麻烦的是传递参数的问题,虽然知道是通过4个寄存器来传递参数,但是实际编写的时候发现并不是这个简单,比如create需要的参数是一个char*指针,指向的是一个字符串,这个字符串是要创建的文件的名字,开始的时候我直接把从4号寄存器中读出的int值强制转化为char*直接输出,发现出现了段错误,其它任何提示都没有,直接就头大了,后来经过同学提醒,想起了这里的内存空间应该是一致的内存空间而不是宿主机的内存空间,如果直接使用char*转化过的那个int型地址,得到的其实是宿主机上的相应地址而不是nachos的内存地址。
这样就是说我们应该去machine->mainMemory里面找这个字符串,有用的还是得到的那个地址,但是麻烦的是readMemory这个函数只接受int*作为写入的地址,也就是说如果想直接向我自己定义的char数组中一个一个地写入char,是不可以的,因为这样会覆盖掉相邻位置的字节。
于是只能模拟读int的方式,一次读入4个字节,每次把地址加4(而不是1)。
这样操作之后终于可以正确地输出这个名字参数了。
接着把名字和文件大小(我默认为一个sector的大小)作为参数传递给filesys的create,就可以创建文件了。
但是开始写的时候改正完这个错误还不算完,程序运行的时候就像死循环一样,不停地在create里面转,后来想到之前写的tlbmiss的部分,明白了这里也应该改pc指针的值,把nextpc的值赋给pc寄存器,这样之后create终于可以很好地运行了。
完成了一个系统调用,其它的其实就简单多了,大多数是函数的包装。
对于open来说,还需要在当前线程的地址空间中增加把这个打开文件号的功能,我在地址空间中定义了一个存放打开文件的数组,每次找空的位置放入新的打开文件。
这样open就基本完成了,close就是delete数组中这个文件号对应的文件,然后把数组中这个位置置为null即可。
对于write和read来说,需要有读文件写入buffer的操作或者从buffer里面读然后写入到文件,因为openfile的read和write都是根据seekposition的位置读写的,具有不确定性,于是我选择了使用readat和writeat,和读文件名的时候不同的是,这里buffer和mainMemory都是char类型的,所以在写buffer的时候可以一个字节一个字节地写。
而在写文件的时候还需要判断当前文件的大小是不是足够盛放这些要写的内容,如果不够的话,那么还需要先allocatemore。
测试程序的话,很简单,就是仿照halt.c的写法,先create,然后open,write,read,close最后halt就可以了。
这里我碰到的一个问题是不能在main里面定义变量(所谓的局部变量),必须在main外面定义全局变量,否则编译不会通过。
Exercise9:
A)阅读代码:
Synchdisk.hsynchdisk.cc
Synchdick的主要作用是确保磁盘访问的互斥性,为了做到这一点,它是用了信号量Semaphore和互斥锁lock,lock的作用是起到比较“霸道”的读写锁的作用,也就是说,在同一时刻,只能有一个线程在读或者写,而不是想真正的读写锁那样只排斥读的时候的写操作,或者写的时候的读操作。
而Semaphore是用来起到类似于中断的作用,当发起读或者写操作以后,就使信号量进行P操作,而当磁盘的读或者写操作结束后,就会进行V操作,唤醒synchdisk,继续执行刚才的操作。
而这个V操作其实是通过synchdisk的包装函数RequestDone函数来实现的,这个包装函数的作用在于disk操作结束之后就会自动调用作为参数传入disk的构造函数,这样disk在进行完读写操作后就可以自动调用这个函数,从而间接进行V操作。
而V操作执行完毕之后,就会解锁。
B)实现SynchConsole
其实在通过看progtest里面有关console的实现就可以看出,nachos的console也是可以互斥访问的,但是当前是通过在外部包上PV操作来实现的,在ConsoleTest中,创建了两个信号量,一个是readAvail,一个是WriteDone,在取字操作之前,首先readAvail进行P操作,把这个字符反过来输出到屏幕上之后,WriteDone进行P操作,这样,就可以仿照这样的方式,创建一个新的类SynchConsole,像SynchDisk一样对Console进行包装,从而实现目标。
为了避免编译的麻烦,我选择在console中直接定义SynchConsole类,而不另外使用其它文件。
仿照SynchDisk,我在SynchConsole中使用了读锁readLock,写锁writeLock,因为console中使用了readAvail和writeDone两个信号量,而Console会在读写控制台之后调用两个信号量的V操作,所以我在SynchConsole中,也定义了两个包装函数也叫readAvail和writeDone,这两个函数也像progtest里面的同名函数一样,直接调用synchConsole中的对应的V操作。
于是,在构造函数中,new出读写锁,读写信号量,以及真正进行控制台操作的console成员变量,在putchar中首先取得写锁,然后调用console的putchar函数,之后调用写信号量的P操作,最后释放写锁,getchar的操作与此类似,只是要先调用读的信号量的P操作,然后调用console的getchar函数。
这样之后,synchConsole就完成了。
Exercise 10:
为了保证第一个要求,每一个线程拥有独自的文件访问位置,我在Addrspace中定义了一个OpenFile指针数组,为了简便,我直接通过宏指定这个数组的大小为10,因为这里只是限制用户打开的文件之间互不干扰,于是我在处理系统调用Open的时候(exceptionhandler里面),把要打开的OpenFile指针放到currentThread的space里面,为了保持ExceptionHandler代码的简洁,我在addrspace里面定义了一个AddToOpenFile函数,用来通过遍历数组找到空位,把OpenFile的指针放入到space的openFiles数组(我自己定义的)中。
这个函数会返回在数组中的下标,这样就可以把这个文件号作为返回值了。
相应的,还需要在Close里面加上从数组中去除要关闭的OpenFile,于是我把数组中的指针delete掉,并且置为NULL。
之后在Read和Write中也就可以通过文件下标直接得到space中的OpenFile了。
对于第二个要求,我认为既然已经使用了SynchDisk,那么就是原子化的了,因为在读写之前都必须首先申请锁。
对于第三个要求,为了保证在要删除一个文件时必须保证其它线程已经关闭这个文件,我在system.cc中定义了一个全局的打开文件链表,链表中存放文件打开的次数和文件头所在的sector号,并且实现了AddOpenFile和RemoveOpenFile两个函数,AddOpenFile在链表中查找是否已经打开了这个文件(通过打开文件所在的sector号是否相同来判断),如果已经打开了这个文件,那么打开次数加一,否则把这一项加入到链表中,打开次数设置为1。
RemoveOpenFile也是先找这个文件,如果找到则给引用次数减一,如果没找到,返回-1。
为了在删除判断的时候方便,我还定义了GetOpenFileNum这个函数,这个函数通过和上面一样的方法,遍历找到这个文件的打开次数,在FileSys得到Remove里面,如果查询到要删除的打开文件次数是0,那么删除这个文件,否则返回Fa
- 配套讲稿:
如PPT文件的首页显示word图标,表示该PPT已包含配套word讲稿。双击word图标可打开word文档。
- 特殊限制:
部分文档作品中含有的国旗、国徽等图片,仅作为作品整体效果示例展示,禁止商用。设计者仅对作品中独创性部分享有著作权。
- 关 键 词:
- 文件系统 实习 报告