精华内容
下载资源
问答
  • nachos

    2021-04-04 15:30:32
    nachos实验记录实验一分析threads文件夹内容threadscheduler初始化函数Initialize()的工作main()函数的工作gdb基本使用实验三分析synch文件实验内容 实验一 分析threads文件夹内容 思路:通过thread类中的方法,来...

    实验一

    分析threads文件夹内容

    思路:通过thread类中的方法,来调用schedule类中的调度函数,实现线程的创建,就绪,运行,阻塞和结束五种状态的转换。

    thread

    1. 创建线程:fork()。首先为线程分配栈资源(StackAllocate()),然后将线程状态设为就绪态(setStatus()),并放入就绪队列中(Append()),等待被调度即可。
    2. 线程阻塞:sleep()。首先将状态设置为阻塞态,然后从就绪队列中寻找新的线程来调度(FindNextToRun()),如果就绪队列中有线程就可以执行该线程(run()),如果就绪队列中没有线程则阻塞等待时钟中断到来(idle())。
    3. 线程切换:yield()。 从就绪队列中寻找新的线程来调度(FindNextToRun()),如果就绪队列中有线程就可以执行该线程(run()),同时将当前运行的线程放入就绪队列中(ReadyToRun());如果就绪队列中没有线程,则修改状态后直接退出即可。(区别于sleep(),yield()不会进入阻塞等待状态)
    4. 线程结束:finish()。先将线程标记为待删除的线程(Run()中进行删除),然后直接调用sleep()即可。为什么不调用yield():因为yield()虽然也释放cpu资源,但却将线程再次放入就绪队列中,而线程结束则意味着线程不需要再使用cpu,因此不应该再放入就绪队列中,调用sleep()则正好满足要求。

    scheduler

    1. 寻找下一个运行的线程:FindNextToRun()。查看就绪队列中是否还有线程,如果有就从就绪队列中删除这个线程,并将其返回;如果没有线程则返回NULL。
      准备执行线程:ReadyToRun()。本质上就是创建到就绪态的转换,先将线程状态设为就绪态(setStatus()),然后将其放入就绪队列中即可(Append())。
    2. 执行线程:Run()。本质上就是就绪态到运行态的转换,先将线程状态设为运行态(setStatus()),然后进行上下文的切换(SWITCH()),最后判断被调度下去的线程是否需要销毁(finish()中进行的设置),如果要销毁那么就进行destory。

    初始化函数Initialize()的工作

    这个属于system类,即当执行一个main()函数时,操作系统自己进行的初始化工作。首先进行硬件资源如磁盘和网络设备的初始化工作,这里频繁使用ifdef,endif的搭配组合,工程项目中须要掌握这点。之后进行中断,调度队列,时钟的初始化。然后便可以创建main()主线程,并将其设为运行状态。

    main()函数的工作

    注意,这个main()函数并不是我们写的main()函数,而是nachos操作系统运行的内核程序,因为操作系统本质上也是一个程序,而nachos作为一个模拟的操作系统也自然需要去运行。main()函数首先调用Initialize()函数进行初始化工作,需要关注的是之后调用了ThreadTest()函数做测试,其中又调用了fork()创建了新的线程,并用新的线程和主线程均调用了SimpleThread()函数,但是传递的参数却不同,因此执行结果自然不同。

    gdb基本使用

    1. 查看函数地址:首先list,然后找到要查看的函数位置,利用break设置断点即可。
    2. 查看线程对象地址:首先list,找到创建线程的代码位置,并利用break在创建线程返回值的位置设置断点,然后用run命令运行线程,到达这个位置时会暂停,此时利用p查看线程地址即可。
    3. 查看汇编代码的返回地址:可以用break在函数入口打断点,然后利用disass查看汇编代码,由于函数返回时一定会将返回地址放入寄存器中,则可以在返回的汇编代码的下一步中,利用info r查看寄存器内容,从而得到返回地址。需要注意的是,s和n命令都是c语言级的,想要单步执行汇编代码,需要利用si和ni,这些命令是汇编级的。

    实验三

    分析synch文件

    首先需要了解的是,list文件中自己实现了链表节点,并基于此实现了链表类,然后在synch文件中利用这个链表实现了信号量,互斥锁和条件变量。其中信号量主要是维护了一个等待当前信号量的线程队列和信号量的值,而互斥锁则是维护了一个大小为1的信号量和锁的拥有者,由于本次实验我们使用信号量和互斥锁来实现生产者消费者模型,因此条件变量不在此详述,只需知道条件变量和互斥锁配合也完全可以实现生产者消费者模型,这其实可以作为线程池的实现方式。

    1. P()操作:为了实现线程安全,信号量的操作必须是原子性的,而保证原子性的方法就是关掉中断,即禁止了线程切换。然后判断当前信号量的值是否为0,若为0则需要加入队列,并进入阻塞状态;若不为0则可以将信号量值减一,重新打开中断即可。
    2. V()操作:依然需要先关闭中断,然后判断当前队列中是否有等待的线程,若有则将其从等待队列中删除,并准备执行(ReadyToRun())。最后将信号量值加一,并打开中断。
    3. Acquire()操作:首先依然是关闭中断,然后修改锁的持有者,并直接调用P()操作即可,最后记得重新打开中断。
    4. Release()操作:首先关闭中断,然后锁的持有者设为NULL,并直接调用V()操作即可,最后记得重新打开中断。

    实验内容

    缓冲区是在ring文件中实现的,就是一个简单的数组,并用in和out标记当前插入和取出的位置。还需要注意的是由于是环形缓冲区,因此需要取余。
    需要填充的只有prodcons文件,首先明确的是由于缓冲区是临界区,因此需要互斥锁;并且缓冲区的大小有限,不可能无限取,也不可能无限填充,因此需要生产者和消费者进行同步的信号量。因此完成的工作主要是信号量和互斥锁的初始化,以及生产者的填入数据和消费者的取出数据工作。由于初始时默认缓冲区大小为空,因此对应的取出信号量为0,而填入信号量则为缓冲区大小。此时消费者会进入阻塞状态,等待生产者填入数据。
    当生产者填入数据时首先遵循先同步后互斥的原则,先调用填入信号量的P()操作,然后调用加互斥锁。然后将数据填入缓冲区中,最后再解锁和调用取出信号量的V()操作即可。
    消费者取出数据道理也类似,先调用取出信号量的P()操作,再加锁。将数据取出后,再解锁和调用填入信号量的V()操作即可。

    实验四,五

    理解nachos的文件系统

    简单介绍

    文件系统是指操作系统层面对磁盘文件的管理。大的层面讲就是内部维护文件的管理方式,并向用户提供对文件进行操作的接口。
    例如用户在使用Linux环境下使用的文件命令:touch,cp,mv,ls,rm…这些都是对Linux中文件进行的操作,其都是通过调用文件的相关操作实现。
    在介绍详细的文件操作过程之前,首先说明一下文件的读写和存储单位,由于磁盘文件的读写单位并不是整个文件,而是一个个磁盘块,文件数据也是按照磁盘块的单位存放在文件中。因此我们需要管理的最小单位是磁盘块,而磁盘块是由一个个扇区组成,一般一个磁盘块有2^n个扇区,实验所用的nachos系统中为了简化,将一个磁盘默认为一个扇区,大小为128B(后面的实验内容将不再区分磁盘块和扇区,统一使用磁盘块表示,但须明白其实这既是磁盘块,也是扇区)。这样一个磁盘文件便由多个磁盘块组成,操作系统负责对所有的磁盘块进行编址,并进行保存。
    既然磁盘文件由多个磁盘块组成,当我们需要去读写某个文件时,就需要去访问对应的所有磁盘块,因此需要记录某个文件所拥有的所有磁盘块。文件大小,文件所占磁盘块等信息都统一保存在文件头中,文件头也属于文件,其占据一个磁盘块。由于文件头的存在,我们可以从中方便的得知文件的相关信息,但我们如何直到文件头的位置呢?读写文件时如何寻找文件头呢?这就需要一个根目录表的结构了,其维护了一个目录表,表中有多个目录项,记录着硬盘中所有文件的名字和文件头的存放位置。
    现在读写文件的简单过程已经梳理好,但是创建和删除文件的过程还没有构建。当用户使用touch命令创建一个文件时,操作系统又是如何操作的呢?
    前面说过文件的存储单位是磁盘块,那么创建文件时,我们自然需要为创建的文件分配所需的空闲磁盘块个数 。操作系统如何知道哪些磁盘块是空闲的呢?nachos中空闲磁盘块的管理方式是位示图。其通过位的方式记录每块是否已经被分配。这样当我们需要创建文件时,就查找位示图,找出所需的块数,分配即可。
    那么回收呢?nachos系统中回收时并不会真的释放文件内容等,而是将位示图中文件头和文件数据对应的磁盘块标记为空闲,并在目录表中将对应文件项标记为不再使用,仅此而已。
    以上简单介绍了文件的创建删除,以及寻找文件的过程。接下来还需要介绍一下文件的复制过程等。
    文件的复制过程需要利用上面的几种操作,比如首先需要得到源文件的大小,然后读取源文件数据,写入目标文件中。写入文件过程较为复杂,需要根据写入数据的大小,判断是否需要为目标文件分配新的磁盘块,这些过程后面将详细介绍。
    大致框架已经搭好,下面将分别详细介绍上面及部分:

    管理方式

    1. 位示图:由于磁盘文件的读写单位并不是整个文件,而是一个个磁盘块,文件数据也是按照磁盘块的单位存放在文件中。因此需要对空闲的磁盘块进行管理,这是通过位示图的方法实现的;位示图是一个bit数组,其中每bit都代表这一位对应的磁盘块是否处于空闲状态。位示图也有相应的文件头,并存放在磁盘的第4到第131字节(第0到3字节为nachos硬盘魔数)中,其中记录了位示图在第三个磁盘块中,位示图文件也占据一个磁盘块。
    2. 根目录表:存放了n条目录项,每一项通过一个三元组,对一个文件建立了简单的“索引”,这个三元组由<name, inuse, sector>,分别表示某个文件的名字,以及文件当前是否存在,还有文件头(下面介绍)所在的扇区号。前面曾经简单介绍说当删除一个文件时并不会真正执行文件数据的删除等操作,那么如何知道该文件是否被删除呢?就是通过位示图+目录项的方式,位示图比较好理解,而目录项就是通过inuse来作为标记为进行记录的,当文件被删除时,inuse位修改为false即可。
      每一个目录项占20B,该实验中设定允许存放的最大文件数目为10,因此所需要的大小为200B,必须由两个磁盘块来存放。根目录表的文件头存放在第二个磁盘块中,而根目录表文件存放在第四和第五个磁盘块中。
    3. 文件头:文件头类似于文件的一个组织者,其记录了文件的大小,文件所占据的磁盘块数目,以及用一个数组记录占据的各个磁盘块。通过这种方式,当我们需要访问一个文件时,只需要从根目录表的文件头开始,先找到根目录表所在磁盘块,并根据name从中读出所要查找的文件的三元组,三元组的sector部分记录着所要查找的文件的文件头所在磁盘块,然后读取对应的磁盘块,获得文件头内容,其中记录着文件数据保存在哪些磁盘块上,依次读取即可。
      以下是nachos文件系统的简单布局方式:
      在这里插入图片描述

    nachos模拟的文件系统

    由于实验用的是nachos操作系统,其只是一个简单的模拟文件系统,并没有实现真正的硬盘等,因此在介绍一些文件的具体操作之前需要了解一下用磁盘模拟硬盘的方法,方便之后的代码理解。
    我们前面已经介绍了nachos系统的文件管理方式,那么我们如何模拟硬盘呢?实验中采用的是在一个磁盘文件中模拟,既然是在文件中,那么就涉及到文件的读写过程,事实上无论是对文件的任何处理,我们都需要先将文件中保存的目录表和位示图从文件中读出来,并用根目录表和位示图这两个类进行保存,后面对相应数据进行修改,最后写回文件,也就是虚拟的硬盘中。

    文件操作

    1. create:文件的操作中最早的往往是文件的创建工作,因为创建后才能进行后续的相关处理。创建时我们需要首先将保存在虚拟硬盘上的根目录表和位示图读出,判断目录表中是否已经存在对应文件名,如果存在则创建失败;如果不存在,则从位示图中寻找一个空闲磁盘块进行分配,用来保存文件的文件头。如果没有空闲块,创建失败;否则,创建成功,并修改位示图对应位,以及在根目录表中插入一个目录项,并执行文件头的初始化操作,如文件大小和磁盘块数等。最后需要将文件头,根目录表和位示图写回虚拟硬盘中。
    2. copy:复制文件的过程实验中已经提供,首先需要根据源文件的大小,创建一个新的nachos文件;然后循环读出源文件的数据,并写入nachos文件中,因为文件大小是已知的,因此我们可以直接进行磁盘块的分配。然后依次写入磁盘块中,并将相应的位示图和根目录表进行修改。至于写入磁盘块的具体操作,下面会详细讲解。
    3. write:写入文件之前,我们需要首先找到文件的文件头,这个过程之前已经详细介绍过,从文件头中可以得知当前文件的总大小,以及磁盘块,从而计算出最后一个磁盘块所使用的空间,然后根据我们要写入的数据,判断是否需要分配新的磁盘块。如果需要,那么读入位示图,并调用相关函数,寻找一块新的空闲磁盘块进行分配,若没有空闲磁盘块返回错误即可。若成功分配,那么还需要修改文件的文件头。获得足够的磁盘块后,需要将当前的数据依次写入磁盘块中,由于我们对文件读写的最小单位是磁盘块,因此文件是存在内碎片问题的,即磁盘块中可能并没有完全被文件数据占满空间。因此我们还需要判断当前磁盘块是否对齐,如果没有对齐,需要先读出最后一块磁盘块,然后在此磁盘块后面进行追加式的写入。最后需要将文件内容完全写回硬盘中。
    4. remove:文件的删除操作实际上前面已经简单介绍过了。首先依然需要将位示图和根目录表读出,nachos系统中回收时并不会真的释放文件内容等,而是将位示图中文件头和文件数据对应的磁盘块标记为空闲,并在根目录表中将对应的目录项的inuse字段标记为false。最后需要将位示图和根目录表写回虚拟硬盘中即可。

    实验六

    nachos可执行文件

    由于nachos是一个操作系统,因此其运行的可执行文件也是nachos系统下支持的特定文件,其后缀为.noff。分析noff.h文件,可发现其文件内容按照段式存储,主要由code段,initData段,uninitData段,除此之外还有标识noff文件的魔数。那么段又是由什么构成的呢?首先思考段的构成中必须包含该段数据的内存地址,还有一个就是由于段和页存储特点不同,页式存储中每页的大小是固定不变的,而段式存储是根据段的具体含义,大小可自由定义,因此还需要记录段的大小。这样分析之后,段的数据结构呼之欲出:虚拟地址空间中的地址virtualAddr,以及段数据在对应文件中的位置inFileAddr,还有段的大小size

    进程的创建和执行过程

    提到进程的创建和执行,Linux系统下首先想到的方法是先使用fork()调用创建子进程,然后通过exec()调用执行对应的可执行文件。nachos系统中也是同理,但这里我们主要讨论的是根据文件名进行主进程的创建和执行(还有另一个原因是此时nachos系统还不支持多道程序设计,因此无法支持多进程模式,只能存在一个进程。实验7,8中将对此进行改进)。
    接下来将从userprog/proggtest.cc文件中的StartProcess()函数开始,对nachos系统中进程的创建和执行过程进行详细的分析:
    从大的层面来总结,首先需要根据可执行文件的名称,打开文件;之后根据文件大小创建用户地址空间,并初始化页表内容;然后读入对应的段数据放入用户地址空间;用户地址空间创建之后,本来应该需要创建对应的进程块,其中保存进程的标识,文件描述符等信息,但是由于此时nachos系统并没有涉及到多进程,因此也没有维护进程块的数据结构,而是将地址空间,页表等信息由各自的类维护。既然不维护特定的进程块,那么就需要将系统当前的用户地址空间切换为对应进程的地址空间,并初始化当前的寄存器,需要注意的是每个进程的地址从0开始,因此PC寄存器的首个存储内容一定为0;同时修改页表指针,使其指向当前的进程。这样便进行了一个简单的进程创建过程,然后便可以调用Run()函数执行进程。

    1. open():根据文件名打开文件,直接通过nachos文件系统中实现的open()函数即可实现。
    2. AddrSpace():用户地址空间的构造函数,而用户地址空间的类AddrSpace其实只是维护了一个页表指针和页的数目。因此与其说初始化用户地址空间,不如说初始化页表,而页表的数据结构中主要有虚地址,实地址,以及当前位是否有效,是否为脏页…其中一些内容在本实验中并无实际意义,比如脏页,因为此时nachos系统都是直接将段数据放入物理内存中,因此不存在页面置换的情况,也自然不存在脏页写回的问题。那么,在这个构造函数中,需要做的主要是利用文件的ReadAt()函数读入可执行文件的段数据,由于nachos地址空间由段数据和堆栈指针构成,因此可据此获得用户地址空间的大小,从而计算出所需要的页表数目,然后便可对页表中的每一页进行初始化即可,最后还有一步很重要的操作是将各段数据放入物理内存中(此时还不支持调页的方式)。
    3. InitRegisters():cpu寄存器的初始化过程,其中不仅包括通用寄存器,还有很重要的两个寄存器是PC寄存器和nextPC寄存器,两者也同样需要初始化,寄存器初始之后,还有需要初始化的是堆栈指针,也在这个函数中进行即可。
    4. RestoreState():修改机器的页表指针指向当前进程的页表即可。
    5. Run():进程的执行过程,这是一个很复杂的函数,其中涉及到每一个指令的执行过程,其中执行指令的过程均通过调用OneInstruction()函数进行。函数中执行的具体过程为:首先根据PC寄存器中存储的地址,调用ReadMem()函数获取对应地址所存储的指令,然后分析并执行指令即可,最后还需要修改PC寄存器和其他寄存器的内容,使其保存各自对应指令的地址。需要补充一下的是ReadMem()函数,因为PC寄存器中保存的是虚地址,因此该函数的作用其实类似于MMU单元,需要将虚地址转换为实地址,这个步骤主要是通过调用函数Translate()函数来实现的,其中主要过程为判断是否存在快表以及是否命中快表,如果没有命中则需要访问页表,并修改其响应的标识位。而ReadMem()函数除了调用Translate()函数以后,还进行了地址转换过程中的各种异常处理,以及不同数据存储方式的读取过程(字节,字…)。

    以上就是实验六的相关内容,其更主要的一个作用是为了解进程的创建和执行所需要维护的一些数据结构和基本过程,为实验七,八做铺垫,因为很多东西在该实验中还没有实现,需要在接下来的两个实验中自己补充实现。

    实验七,八

    最后两个实验是对上一个实验的进一步理解和完善,上一个实验更倾向于进程的创建和执行过程,最后两个实验则更侧重于系统调用的实现相关数据结构的使用过程

    系统调用

    1. exec():该系统调用的功能时加载运行另一个应用程序,在Linux环境下使用时实现的方法往往是依然在此进程中,只不过用另一块地址空间替换了当前进程的地址空间,因此当前进程中“exec(“xxx”)“之后的代码一般是不执行的,因为地址空间已经改变,即代码段和数据段等内容也均改变。但是此次实验中使用nochos环境下,exec()的实现方法和Linux环境下的略有不同:首先依然需要从特定寄存器读入exec()的参数(文件名称),然后据此参数打开文件,装入内存中,但是并不是替换当前进程的地址空间,而是建立一个新的进程,并分配空闲地址空间,有点类似于fork()调用,但是创建的新线程和原来的线程并没有父子关系。而且,也不是立刻执行对应的新线程,只是将其放入就绪队列中,等待被调度后执行。也正是因为没有采用地址空间替换的方式,原线程中exec()调用之后的代码依然会继续执行。
    2. exit()和join():退出当前线程,并且保存进程的退出状态,用状态码记录下来。那么,进程都已经退出了,如何获取对应的状态码呢?或者说状态码保存在哪里?这就引出了join()调用,其参数是进程pid,含义为等待对应的进程,并返回其退出状态,记调用join()的线程为joiner,被等待的线程为joinee。可是,问题依然没有解决,调用join()时joiner线程如何判断当前joinee线程是否已经结束了呢?或者说它是该继续等待还是直接获取退出码返回。为此,本实验中为进程额外建立一种新的状态–terminated状态,即当进程该退出时不会立刻退出,而是进入terminated状态,并且维护了一个terminated链表,这样joiner线程就可以在调用join()时根据等待的pid遍历terminated链表,如果存在自己等待的线程,那么就说明joinee已经执行结束,可以直接记录joinee的状态码,并继续执行当前线程后面的代码;而如果不存在自己等待的线程,则需要进入等待状态,直到joinee结束为止。因此,除了维护一个terminated链表,还需要维护一个waiting链表,用来记录等待状态的joiner。那么,什么时候使其退出等待状态呢?逻辑也很简单,当joinee线程退出时,遍历waiting链表,如果找到等待自己的joiner线程,就可以唤醒它,并将自己的状态码传递给joiner线程;而如果没有找到joiner线程,则直接进入terminated状态即可。这样就解决了join()和exit()调用的耦合关系。还有一个小细节就是,所有线程结束之后都会进入terminated状态,并加入terminated链表,那么什么时候从链表中删去该线程呢?本实验中采用的一种暴力的方法,就是通过在调用exit()时,规定一个特殊的状态码,作为清空terminated链表,当然,这种方法存在很大的问题,不宜采用。。。

    数据结构

    1. pid相关:为了实现系统调用,除了逻辑和基本功能,还需要使用一些辅助的数据来记录数据,比如进程pid,比如退出状态码…首先考虑pid的问题,pid主要用来每个线程的处理,以及join调用中joiner等待joinee时的参数,因此考虑用两个属性UserProgramId和waitingProcessSpaceId来分别表示线程对应的pid和join()时的参数joinee(没有使用join调用的进程可忽略这个属性),前者可以在分配pid时即可构建,而后者则在调用在使用join时赋值即可。
    2. 进程状态相关:之前讨论中已经提及,为了给系统调用join()和exit()提供支持,需要为进程加一个terminated状态,并且还需要在thread类中增加一个terminated链表和waiting链表,分别保存结束的进程以及调用join()后等待的进程。
    3. 状态码相关:状态码只与exit()和join()调用相关,即分别对应joinee线程和joiner线程,也可以用两个属性exitCode和waitProcessExitCode来表示,前者可以在exit()传入参数时赋值,后者则需要在调用join()时具体考虑:遍历terminated链表中如果找到了对应的joinee,则可以直接获取waitProcessExitCode,而如果没有找到对应的joinee,则进入等待状态,并加入waiting链表中。直到joinee线程结束时,在遍历waiting链表时,再将对应的joiner线程的waitProcessExitCode设为当前线程的exitCode,并唤醒对应线程。

    其他内容

    除了上面的处理之外,还有一些额外的辅助信息,如AddrSpace文件中将文件中内存装入主存,由于nachos原来只支持单进程,所以直接从虚地址的0地址开始即可,但是现在支持多进程同时存在,因此装入主存的位置就需要根据利用页的分配原则,将虚地址转换为主存的实地址进行装载。还有使系统调用和其他的指令处理过程统一起来的AdvancePC()函数等。

    总结

    到这里,nachos实验就全部结束了。整体个过程下来,最大的感受就是对操作系统的进程管理和内存管理有了更深入的理解,从开始编写源程序文件保存到磁盘上开始,涉及到文件的存储方式,以及文件目录表的管理,磁盘文件的分配等,当然,还有与文件相关的一些系统调用,如open(),create(),write(),copy()等;当源程序文件装载到内存中时,静态的文件就变成了动态的进程和线程,这里又涉及到包括进程的创建与执行,内存中为进程进行地址的分配,以及寻址时虚地址和实地址的转换,进程的上下文切换过程等。当然,这一切的前提,都是基于操作系统首先进行装载,因为本质上操作系统也是一个进程,上面的这些功能都是通过运行这个进程来实现的。因此当我们开机时,首先需要通过一个init()函数来将操作系统内核装入内存中,然后运行操作系统进程来进行接下来的过程。
    但是,说到底nachos只是一个简单的操作系统的模拟,它并不是一个真正的可用的操作系统,甚至比起类似于Linux,Windows操作系统远远不及,本实验的目的只是打开我对操作系统这个庞然大物的面纱,接下来需要我进一步去探索其中更加复杂的奥秘。对于操作系统的学习之路,才只是刚刚开始而已。

    展开全文
  • nachosNachos XMU操作系统课程实验
  • NACHOS代码

    2018-12-04 21:56:28
    NACHOS操作系统代码,建议安装在Ubuntu Linux 16.04 LTS 系统 32位。
  • Nachos系统

    2019-04-16 19:09:01
    Nachos的全称是“Not Another Completely Heuristic Operating System”,它是一个可修改和跟踪的操作系统教学软件.
  • nachos:os.lab.nachos-源码

    2021-04-29 12:09:16
    nachos os.lab.nachos nachos实验一的代码,应该是完全的。我并没有系统地读完,仅测试了几个功能,发现是与实验一相符合的。 这个代码是去年计算机一位学长提交的。 by 乔森远
  • nachos资源下载

    2020-09-28 15:29:33
    nachos资源下载
  • nachos.zip

    2020-09-28 15:25:29
    nachos.zip
  • nachos 介绍

    2013-11-02 10:30:48
    mit的实验操作系统nachos介绍,帮你全面认识nachos,更好的掌握操作系统原理
  • Nachos:基于Nachos的PKU高级操作系统课程项目和说明
  • 玉米片 NachOs 用于我的操作系统课程
  • nachos整理.zip

    2020-04-02 00:45:59
    nachos,project1和project2的实现,nachos-java版/eclipse/win系统;包括源码,实验报告,使用说明
  • Nachos 3.4

    2012-04-06 22:05:47
    Linux 平台下 的教学操作系统Nachos.
  • Nachos中文教程

    2018-01-01 23:14:26
    Nachos中文教程,包括环境配置,函数分析,线程,文件,虚拟内存,网络等。
  • Nachos编译与使用–Nachos配置 操作系统课程设计要求使用Nachos,因此这里便是对Nachos的初步使用 实验环境:32位Ubuntu 实验对象:c++版Nachos 实验参考:http://blog.sina.com.cn/s/blog_a2dded3d010194pj.html ...

    Nachos编译与使用–Nachos配置

    写在前面
    操作系统课程设计要求使用Nachos,因此这里便是对Nachos的初步使用
    这里的实验环境采用的是32位的Ubuntu,为什么呢,因为64位的系统,在进行Nachos编译时会出现一个指针类型转换错误的问题,目前尚未解决,大致会出现类似错误(void*)–>(int),这是因为64位系统的void类型指针是一个64位的指针,而int是一个32位的指针,因此出现精度缺失错误。解决的话可能需要使用new进行一个整型变量的强制类型转换。但还是建议更换系统更加方便一些,当然如果是大佬的话,那就随意了。


    实验需求
    实验环境:32位Ubuntu
    实验对象:c++版Nachos
    实验参考:http://blog.sina.com.cn/s/blog_a2dded3d010194pj.html
    实验所需:http://pan.baidu.com/share/link?shareid=2032464898&uk=2822100601


    实验步骤

    1. 下载Nachos
      在实验所需的连接中下载Nachos,得到一个压缩包
      在这里插入图片描述
    2. 提取文件
      双击压缩包,提取文件到一个新建的文件夹中,我这里是code-linux
      在这里插入图片描述
    3. 进入文件夹并进入子文件夹Nachos-3.4
      在这里插入图片描述
      在这里插入图片描述
    4. 进入code文件夹
      code文件夹所存放的便是Nachos系统的源代码,在这个文件夹中,每一个文件夹都对应着操作系统的某方面功能
      在这里插入图片描述
      其中machine是用来模拟虚拟机的,network是模拟网络的,其余都对应着英文意思
    5. 右键进入终端
      在文件夹中打开终端,执行make操作,生成可执行文件XXX.o
      5.1
      在这里插入图片描述
      发现出现错误,仔细观察原因,大致是因为Makefile文件中第一行gmake命令没有找到,使用gedit打开Makefile,果然如此
      在这里插入图片描述
      这个错误的原因是因为在Ubuntu系统中没有gmake这个命令,而是使用的make命令进行编译文件,因此改成make后保存并在终端中使用make命令重新编译。
      5.2
      重新编译后发现又出现新的错误
      在这里插入图片描述
      仔细观察错误原因,发现是在Makefile.common这个文件中的“-fwritable-strings”在Ubuntu中没有找到,果断打开Makefile.common文件,找到这个地方,删掉
      在这里插入图片描述
      5.3
      再次在终端使用make重新编译,发现可以正常编译了
      在这里插入图片描述
    6. 使用Nachos
      刚刚编译好了Nachos,现在使用一下吧
      6.1
      进入threads文件夹,打开终端,运行./nachos
      在这里插入图片描述
      运行成功,其实运行的文件就是threads文件夹中的nachos文件
      在这里插入图片描述
    展开全文
  • nachos 详解

    2012-03-27 22:01:43
    nachos中文介绍,分块介绍:中断,调度,文件系统 ,磁盘等
  • a road map through nachos

    2018-10-27 17:42:47
    a road map through nachos,分为 Introduction to Nacho、Nachos Machine、Nachos Threads、User-Level Processes、Nachos Filesystem、Experience With Nachos Assignments、MIPS Architecture等七个部分
  • nachos shell

    2009-06-08 18:18:04
    nachos shell 's ppt 这是nachos第一个shell 的实现
  • nachos实验报告 lab1

    2018-09-13 17:17:43
    北大 操作系统 nachos实验报告 实验1 非常认真完成的报告
  • nachos实验报告

    2014-06-11 19:30:09
    nachos实验报告..包含代码
  • 操作系统模块构建(通过NACHOS)。 通过最初在UC Berkley UC中开发的NACHOS来仿真OS内核。 使用Java SE 1.7进行的项目,已构建了2,000行以上的源代码,包括进程/线程,文件系统,虚拟内存,中断,CPU调度程序,死锁...
  • nachos java版本

    2014-10-03 14:34:12
    nachos java版本 教学用代码 很全 Nachos包括主要包括 机器模拟(machine)线程管理(thread文件系统管理(machine)用户程序(userprog)虚拟存储(vm)网络系统(network)
  • nachos系统调用实验

    2017-05-14 22:42:20
    nachos操作系统的教学实验,系统调用实验
  • nachos课程设计报告

    2017-11-19 19:19:55
    Nachos 是一款教学用的操作系统平台,它的全名叫做"Not Another Completely Heuristic Operating System“,Nachos 的运行必须借助于宿主机, 它通过软件模拟了各种硬件系统,包括中断系统、存储系统、磁盘文件、网络等...
  • NachOS:操作系统硬件
  • nachos-3.4

    2014-03-17 21:21:55
    nachos-3.4源代码,从官网上下的。Nachos的全称是“Not Another Completely Heuristic Operating System”,它是一个可修改和跟踪的操作系统教学软件。它给出了一个支持多线程和虚拟存储的操作系统骨架,可让学生在...
  • 文章目录前言一、实验基础信息实验信息实验目的实验任务二、实验基本方法2.1 Nachos 的硬盘及文件系统2.2 Nachos 的文件系统命令2.3 两个 UNIX 命令2.4 Nachos 文件系统的删除操作2.5 DISK 文件的创建2.6 Openfile::...

    前言

    如果你对这篇文章可感兴趣,可以点击「【访客必读 - 指引页】一文囊括主页内所有高质量博客」,查看完整博客分类与对应链接。

    一、实验基础信息

    实验信息

    日期:2019.11.28

    题目:Nachos 的文件系统、扩展 Nachos 的文件系统

    实验目的

    (实验四)

    1. 理解 N a c h o s Nachos Nachos 硬盘是如何创建的。
    2. 熟悉查看 N a c h o s Nachos Nachos 硬盘上的内容的方法。
    3. 理解硬盘初始化的过程(如何在硬盘上创建一个文件系统)。
    4. 了解 N a c h o s Nachos Nachos 文件系统提供了哪些命令,哪些命令已经实现,哪些需要你自己实现。
    5. 理解已经实现的文件系统命令的实现原理。
    6. 理解硬盘空闲块的管理方法。
    7. 理解目录文件的结构与管理。
    8. 理解文件的结构与文件数据块的分配方法。
    9. 了解一个文件系统命令执行后,硬盘的布局。
    10. 分析目前 N a c h o s Nachos Nachos 不能对文件进行扩展的原因,考虑解决方法。

    (实验五)

    1. 理解文件系统中文件操作的实现方法,如文件打开、读、写、扩展、定位、关闭等。
    2. 理解如何管理硬盘空闲块。
    3. 创建文件时,如何为文件分配目录项及文件头(FCB)。
    4. 理解文件扩展时,如何为要扩展的数据查找并分配空闲块。
    5. 理解文件扩展后,文件大小是如何记录与保存的。
    6. 文件被删除后,如何回收为其分配的资源,如文件头、目录项、硬盘块等。
    实验任务

    (实验四)

    • 阅读代码:…/filesys/fstest.cc、synchdisk.cc、openfile.cc、filesys.cc、directory.cc、filehdr.cc,…/threads/main.cc、…/machine/disk.cc、…/userprog/bitmap.cc

    • 分析代码

      1. …/lab5/main.cc 调用了 …/threads/system.cc 中的 Initialize() 创建了硬盘 DISK。分析 …/filesys/synchdisk.cc 及 …/machine/disk.cc,理解 Nachos 创建硬盘的过程与方法。

      2. 分析 …/lab5/main.cc,了解 Nachos 文件系统提供了哪些命令,对每个命令进行测试,根据执行结果观察哪些命令已经实现(正确运行),哪些无法正确运行(尚未完全实现,需要你自己完善)。

        分析 …/lab5/fstest.cc 及 …/filesys/filesys.cc,理解 Nachos 对这些命令的处理过程与方法。

      3. 分析 …/filesys/filesys.cc,特别是构造函数 FileSystem::FileSystem(…),理解 Nachos 硬盘 “DISK” 的创建及硬盘格式化(创建文件系统)的处理过程。

      4. 利用命令 hexdump -C DISK 查看硬盘格式化后硬盘的布局,理解格式化硬盘所完成的工作,以及文件系统管理涉及到的一些数据结构组织与使用,如头文件(FCB)、目录表与目录项、空闲块管理位示图等。

        结合输出结果,分析 FileSystem::FileSystem(…) 初始化文件系统时涉及到的几个模块,如 …/filesys/filehdr.h,directory.h(directory.cc),…/userprog/bitmap.h,理解文件头(FCB)的结构与组织、硬盘空闲块管理使用的位示图文件、目录表文件及目录下的组织与结构,以及它们在硬盘上的位置。

      5. 利用命令 nachos -cp …/test/small 复制文件 …/test/small 到硬盘 DISK 中。

      6. 利用命令 hexdump -C DISK 查看硬盘格式化后硬盘的布局,理解创建一个文件后相关的结构在硬盘上的存储布局。

      7. 复制更多的文件到 DISK 中,然后删除一个文件,利用 hexdump -C DISK 查看文件的布局,分析文件系统的管理策略。

    (实验五)

    Nachos 文件系统包括如下类模块,Disk、SynchDisk、BitMap、FileHeader、OpenFile、Directory、FileSystem,各类之间的相互关系如下所示。

    目前 Nachos 实现的文件系统存在诸多限制,其中之一是文件大小不能扩展,即无法在已经存在的文件尾部追加数据。因此实验五需要修改 Nachos 的文件系统,以满足如下功能。

    1. 文件创建时,其大小可初始化为0。
    2. 当一个文件写入更多的数据时,其大小可随之增大。
    3. 要求能够在从一个文件的任何位置开始写入数据,即能够正确处理命令行参数 -ap,-hap 以及 -nap。

    二、实验基本方法

    2.1 Nachos 的硬盘及文件系统

    硬盘及文件系统具有以下特点:

    • 磁盘开始的4个字节是硬盘标识,其值为0x456789ab,用于指明该硬盘是一个 Nachos 硬盘。

    • 由于硬盘的前4个字节为硬盘标识,因此从第4~131号字节为0号扇区部分,第132~259号字节为1号扇区部分,即第4号字节为硬盘数据的0字节位。Nachos 文件系统具体的硬盘布局如下所示。

      可以发现0号扇区为位图文件头,1号扇区为文件目录表文件头。系统启动时需要根据目录文件文件头来访问根目录,因此将这两个重要的文件头存储到0、1号扇区中,便于系统启动时从明确、固定的位置来访问。

    • 硬盘的数据存储分为四级,分别是 硬盘 ⟶ \longrightarrow 磁道 ⟶ \longrightarrow 扇区 ⟶ \longrightarrow 字节。Nachos 硬盘中包括32个磁道,每个磁道包括32个扇区,每个扇区为128字节,每个字节8位,因此 Nachos 硬盘容量为 0x80KB,具体的代码可在 disk.h 找到,具体内容如下所示。

    • 由于一个扇区的内存为128字节,因此将每个逻辑块大小也设置为128字节与一个扇区对应,利用之后的编程实现。这也是和传统OS的一个区别,传统OS中的一个逻辑块一般包含 2 n 2^n 2n 个扇区,且 n > 0 n>0 n>0

    • Nachos 采用了一级目录结构,最多可创建10个文件。可在 …filesys/filesys.cc 中宏定义处查看。

    • 在 Nachos 中,一个目录文件由 “文件头+目录表” 组成。查看 …/filesys/directory.h 可以得到文件目录项的信息,其中信息如下所示。注意该类的三个成员变量为公有变量。除了文件头信息之外,下面还给出了文件目录表、文件头的成员变量信息。

      class DirectoryEntry { // 文件目录
        public:
          bool inUse;				// 该目录项是否投入使用
          int sector;				// 文件头所在扇区号
          char name[FileNameMaxLen + 1];	// 文件名,+1用于'\0'
      };
      
      class Directory {	// 文件目录表
      	...
        private:
          int tableSize;			// 文件目录表大小
          DirectoryEntry *table;		// 文件目录表, 形式为 <file name, file header location>
      };
      
      class FileHeader { // 文件头
      	...
        private:
          int numBytes;			// 该文件的字节数
          int numSectors;			// 该文件的数据区块数
          int dataSectors[NumDirect];		// 每一块数据区所在扇区位置
      };
      

      由上述代码可知,每个文件头由 “文件头+数据块” 组成。在 Nachos 中,一个文件头的大小等于一个扇区大小,因此 N u m D i r e c t = ( ( S e c t o r S i z e − 2 ∗ s i z e o f ( i n t ) ) / s i z e o f ( i n t ) ) = 30 NumDirect = ((SectorSize - 2 * sizeof(int)) / sizeof(int)) = 30 NumDirect=((SectorSize2sizeof(int))/sizeof(int))=30,因此一个单文件最大为 30 ∗ 128 B = 3840 B 30*128B = 3840B 30128B=3840B。除此之外,可以发现 Nachos 文件系统没有采用索引文件的方式分配文件数据,而是依次记录数据块所在扇区位置,类似于直接块的索引方式。

    • 查看 …/filesys/filesys.cc 中文件系统的构造函数,可以发现以下信息。

      #define FreeMapSector 		0
      #define DirectorySector 	1
      #define FreeMapFileSize 	(NumSectors / BitsInByte)
      #define NumDirEntries 		10
      #define DirectoryFileSize 	(sizeof(DirectoryEntry) * NumDirEntries)
      
      FileSystem::FileSystem(bool format)
      { 
          if (format) {   // 是否应该初始化磁盘
            BitMap *freeMap = new BitMap(NumSectors); // 创建文件位图
            Directory *directory = new Directory(NumDirEntries); // 创建包含10个文件目录项的文件目录表
            FileHeader *mapHdr = new FileHeader; // 创建文件位图的文件头
            FileHeader *dirHdr = new FileHeader; // 创建文件目录表的文件头
            
            // 第一步:文件位图中标记0、1号扇区被占用
            freeMap->Mark(FreeMapSector);		// 0号扇区为文件位图文件头
            freeMap->Mark(DirectorySector); // 1号扇区为文件目录表文件头
      
      			// 第二步:在文件系统中分配位图文件与文件目录表的空间(传入文件位图与空间大小)
            ASSERT(mapHdr->Allocate(freeMap, FreeMapFileSize));   // 位图文件大小为128字节,1个扇区
            ASSERT(dirHdr->Allocate(freeMap, DirectoryFileSize)); // 十个文件目录项大小
      
      			// 第三步:将更新后的位图文件头、文件目录表文件头写入磁盘
            mapHdr->WriteBack(FreeMapSector);   // 传入对应文件头所在扇区号
            dirHdr->WriteBack(DirectorySector);
      
            // 第四步:创建位图文件、文件目录表的Openfile, Openfile中存储文件头与文件读写位置
            freeMapFile = new OpenFile(FreeMapSector);			// 传入文件头所在扇区, 用于创建Openfile文件头
            directoryFile = new OpenFile(DirectorySector);	// 文件读写位置初始为0
           
      			// 第五步:将位图文件信息、文件目录项信息传入对应Openfile中
            freeMap->WriteBack(freeMapFile);	 // 确定Openfile的起始扇区与结束扇区,开辟文件缓冲区
            directory->WriteBack(directoryFile); // 初始化整个文件
          } else {
            // 非初始化操作,则根据原有位图文件头、文件目录表文件头信息初始化Openfile
            freeMapFile = new OpenFile(FreeMapSector);
            directoryFile = new OpenFile(DirectorySector);
          }
      }
      

      其中主要包含了位图文件、文件目录表的文件头、Openfile的创建,并将初始化信息写入磁盘。其中位图文件用每位的0、1来表示磁盘该位置是否空闲,分配时从前开始找,一旦有空位则直接分配。另外,从上面文件系统创建的部分可以发现,当文件创建后其大小则无法改变。

    • 至此可以得知 Nachos 文件系统在硬盘DISK中的布局如下表所示。

    2.2 Nachos 的文件系统命令

    1. nachos -d f 表示打开 filesys 目录下的所有 debug 信息输出开关

    2. nachos [-d f] -f 表示格式化 nachos 模拟的硬盘 DISK,在使用其它文件系统命令之前需要将硬盘格式化。硬盘格式化的内容已在上述部分说明清楚。该调试参数定义在 …/threads/system.cc 中。

    3. nachos [-d f] -cp UNIX_filename nachos_filename,将一个 Unix 文件系统中的文件 UNIX_filename 复制到 nachos 文件系统中,并重新命名为 nachos_filename。该参数及下述所有参数均定义在 …/threads/main.cc 中。

    4. nachos [-d f] -p nachos_filename 表示输出 nachos 中文件 nachos_filename 的内容。

    5. nachos [-d f] -r nachos_filename 表示删除 nachos 中文件 nachos_filename 。

    6. nachos [-d f] -l 表示输出 nachos 当前的文件目录。

    7. nachos [-d f] -t 表示测试 nachos 文件系统的性能。

    8. nachos [-d f] -D 表示输出整个 nachos 文件系统的信息,包括位图文件、文件头、目录文件和普通文件。

    除此之外,还包括我们接下来需要自行实现的三个文件命令,-ap、-hap、-nap。

    1. nachos [-d f] -ap UNIX_filename nachos_filename 表示将一个 UNIX 文件内容添加到 nachos 文件的末尾。
    2. nachos [-d f] -hap UNIX_filename nachos_filename 表示将一个 UNIX 文件内容从 nachos 文件的中间部分开始向后添加并覆盖 nachos 文件的后半部分。
    3. nachos [-d f] -nap nachos_filename1 nachos_filename2 表示将 nachos 中的 file1 中的内容添加到 file2 的文件末尾。

    2.3 两个 UNIX 命令

    • od 命令(od [OPTION] [FILE])

    该命令用于格式化输出文件中的数据,即对文件中的数据进行无二义性的解释。无论是IEEE754格式的浮点数还是ASCII码,od命令都能按照要求输出它们的值。

    -a:表示ASCII码的名字

    -b:选择单字节,并按照3个数值位对应的八进制数进行解释

    -c:选择ASCII码字符或转义字符,用八进制显示文件偏移量。

    -d:无符号2字节单位

    -f:单精度浮点数

    -i:十进制整型

    -l:十进制长整型

    -o:选择两个字节的单元,并按照八进制进行解释

    -s:选择两字节单元并按照十进制解释

    -x:选择两字节单元并按照十六进制解释

    • hexdump 命令(hexdump [OPTION] [FILE])

    使用 -c 参数,则简单输出 ASCII 字符信息,用十六进制显示文件偏移量。

    使用 -C 参数,显示结果则分为三列(文件偏移量、字节的十六进制、ASCII字符)。

    文件偏移量显示的单位不同,也是这两个命令一个区别。

    2.4 Nachos 文件系统的删除操作

    Nachos 文件系统删除一个文件仅修改两个部分。

    • 一是将位图文件中被删除文件数据与文件头所在扇区清空;
    • 二是在文件目录表中将被删除文件对应的文件目录中的 inUse 变量清空,表明该目录项闲置。

    下述代码为文件系统中的 remove 函数,函数中重要代码部分均有注释。

    /* 从目录表中移除、将位图中对应数据与头文件扇区清空 */
    bool FileSystem::Remove(char *name) {
      	// 定义临时变量
        Directory *directory;
        BitMap *freeMap;
        FileHeader *fileHdr;
        int sector;
        
      	// 从 DISK 中取回文件目录表信息
        directory = new Directory(NumDirEntries);
        directory->FetchFrom(directoryFile);
      	// 找到对应文件所在扇区
        sector = directory->Find(name);
        if (sector == -1) {
           delete directory;
           return FALSE;			 // 文件未找到 
        }
      	// 从DISK 中取回对应文件文件头的信息
        fileHdr = new FileHeader;
        fileHdr->FetchFrom(sector);
    		// 从DISK 中取回位图文件
        freeMap = new BitMap(NumSectors);
        freeMap->FetchFrom(freeMapFile);
    		
        fileHdr->Deallocate(freeMap);  		// 将文件数据信息从位图中移除
        freeMap->Clear(sector);						// 将文件头信息从位图中移除
        directory->Remove(name);					// 文件目录表中移除该文件
    
        freeMap->WriteBack(freeMapFile);			// 更新位图
        directory->WriteBack(directoryFile);  // 更新文件目录表
        delete fileHdr;
        delete directory;
        delete freeMap;
        return TRUE;
    } 
    
    bool Directory::Remove(char *name) { 
        int i = FindIndex(name); 		//找到该文件在目录表中位置
        if (i == -1) return FALSE; 	// 文件不存在
        table[i].inUse = FALSE;			// 该目录项置空闲
        return TRUE;	
    }
    

    2.5 DISK 文件的创建

    nachos 硬盘文件的生成在初始化函数中实现,其具体过程分为以下几步。

    1. …/threads/system.cc 中 Initialize 函数,创建 synchDisk 实例。

    1. SynchDisk 的初始化函数中创建了 disk 实例。

    1. Disk 的初始化函数中,下述代码生成了 DISK 文件。即当 DISK 文件不存在时,运行下述代码。

    1. …/machine/sysdep.cc 文件中的 OpenForWrite 函数生成了 DISK 文件。

    2.6 Openfile::WriteAt() 函数

    该函数用于将数据追加在 openfile 文件的某一个位置之后。主要细节在于一开始确认输入数据是否合法,然后确定数据所在扇区号,如果开头或结尾扇区不完整,则将缺少的数据拷贝到缓冲区中,最后将缓冲区中的数据写入 OpenFile 中即可。

    int OpenFile::WriteAt(char *from, int numBytes, int position) // 数据来源文件, 文件大小, 文件输入位置
    {
        int fileLength = hdr->FileLength();     			// 返回文件字节数
        int i, firstSector, lastSector, numSectors;
        bool firstAligned, lastAligned;
        char *buf;
    		// 检查输入是否合法以及避免输入数据超过文件大小
        if ((numBytes <= 0) || (position >= fileLength)) return 0;
      	if ((position + numBytes) > fileLength) numBytes = fileLength - position;
    
        firstSector = divRoundDown(position, SectorSize);   							// 确定第一个扇区
        lastSector = divRoundDown(position + numBytes - 1, SectorSize);   // 确定最后一个扇区
        numSectors = 1 + lastSector - firstSector;  											// 输入数据涵盖扇区数
        buf = new char[numSectors * SectorSize];    											// 创建完整的数据缓冲区		
    
      	// 确定起始位置是否为扇区开头,结束位置是否为扇区结尾
        firstAligned = (bool)(position == (firstSector * SectorSize));
        lastAligned = (bool)((position + numBytes) == ((lastSector + 1) * SectorSize));
      	// 如果起始位置不在扇区开头或结尾,则将开头和结尾扇区的全部内容放入缓冲区中
        if (!firstAligned)
            ReadAt(buf, SectorSize, firstSector * SectorSize);	
        if (!lastAligned && ((firstSector != lastSector) || firstAligned))
            ReadAt(&buf[(lastSector - firstSector) * SectorSize], 
    				SectorSize, lastSector * SectorSize);	
    
        // 将 from 中的数据拷贝到缓冲区的对应区域中
      	bcopy(from, &buf[position - (firstSector * SectorSize)], numBytes);
    		// 将缓冲区数据写入到 openfile 的对应扇区中
        for (i = firstSector; i <= lastSector; i++)	
            synchDisk->WriteSector(hdr->ByteToSector(i * SectorSize), 
    				&buf[(i - firstSector) * SectorSize]);
        delete [] buf;
        return numBytes;
    }
    

    三、源代码及注释

    在实验原理部分已经介绍了一些关键函数,该部分的主要内容是列出本次实验中自行设计的代码并附上关键注释。

    3.1 fstest.cc

    void Append(char *from, char *to, int half);

    void Append(char *from, char *to, int half) {
        FILE *fp;														// 关键变量
        OpenFile* openFile;
        int amountRead, fileLength, start;	// start为添加文件的开始位置
        char *buffer;
        // 打开UNIX文件
        if ((fp = fopen(from, "r")) == NULL) {	 
          printf("Copy: couldn't open input file %s\n", from);
          return;
        }
        // 计算UNIX文件长度
        fseek(fp, 0, 2);		
        fileLength = ftell(fp);
        fseek(fp, 0, 0);
    		// 添加文件大小为0
        if (fileLength == 0) {
            printf("Append: nothing to append from file %s\n", from);
            return;
        }
    	 	// 打开目标文件
        if ( (openFile = fileSystem->Open(to)) == NULL) {
        	if (!fileSystem->Create(to, 0)) { // 目标文件不存在,因此重新创建一个
        	    printf("Append: couldn't create the file %s to append\n", to); // 文件创建失败
        	    fclose(fp);
        	    return;
        	}
        	openFile = fileSystem->Open(to); // 打开新创建的文件
        }
        ASSERT(openFile != NULL);
        start = openFile->Length(); 			// 给start位置赋值
        if (half) start = start / 2;
        openFile->Seek(start);
        
        buffer = new char[TransferSize];	// 定义传输数据的缓冲区
        while ((amountRead = fread(buffer, sizeof(char), TransferSize, fp)) > 0) {
            int result = openFile->Write(buffer, amountRead);   // 会调用WriteAt函数
            if(result < 0){								// 数据读取发生错误
                printf("\nERROR!!!\n");
                printf("Insuficient Disk Space, or File is too big!\nWriting Terminated!\n");
                break;
            }
            ASSERT(result == amountRead);
        }
        delete [] buffer;
        // 将文件头写回硬盘
        openFile->WriteBack();
        DEBUG('f',"inodes have been written back\n");
        // 关闭UNIX与Nachos文件
        delete openFile;
        fclose(fp);
    }
    

    void NAppend(char *from, char *to);

    void NAppend(char *from, char *to) {
        OpenFile* openFileFrom;							// 关键变量定义
        OpenFile* openFileTo;
        int amountRead, fileLength, start;	// start为appending的开始位置
        char *buffer;
      	// from文件不能与to文件相同
        if (!strncmp(from, to, FileNameMaxLen)) { 
        	printf("NAppend: should be different files\n");
        	return;
        }
    		// from文件不存在
        if ( (openFileFrom = fileSystem->Open(from)) == NULL) {
        	printf("NAppend:  file %s does not exist\n", from);
        	return;
        }
    		// from文件的长度
        fileLength = openFileFrom->Length();
        if (fileLength == 0) {  // 添加的数据为空
        	printf("NAppend: nothing to append from file %s\n", from);
        	return;
        }
    	 	// 打开to文件
        if ( (openFileTo = fileSystem->Open(to)) == NULL) {
        	if (!fileSystem->Create(to, 0)) { // to文件不存在,则重新创建一个
        	    printf("Append: couldn't create the file %s to append\n", to);
        	    delete openFileFrom;
        	    return;
        	}
        	openFileTo = fileSystem->Open(to);
        }
        ASSERT(openFileTo != NULL);
        // 将to文件指针移动到末尾
        start = openFileTo->Length();
        openFileTo->Seek(start);
        // 将添加的数据通过数据缓冲区传送
        buffer = new char[TransferSize];
        // 将from文件指针移到文件开头
        openFileFrom->Seek(0);
        while ( (amountRead = openFileFrom->Read(buffer, TransferSize)) > 0) {
            int result = openFileTo->Write(buffer, amountRead);
            if(result < 0){
                printf("\nERROR!!!\n");	// 数据传输过程出现错误
                printf("Insuficient Disk Space, or File is Too Big!\nWriting Terminated!\n");
                break;
            }
            ASSERT(result == amountRead);
        }
        delete [] buffer;
        // 更新to文件头
        openFileTo->WriteBack();
        DEBUG('f',"inode have been written back!\n");
        // 关闭 to 和 from 文件
        delete openFileTo;
        delete openFileFrom;
    }
    

    3.2 OpenFile

    class OpenFile{};

    我们在 OpenFile 中添加了新的成员变量,hdrSector,用于表示文件头所在扇区号。

    class OpenFile {
      private:
      	FileHeader *hdr;							// 文件头句柄
        int seekPosition, hdrSector;	// 文件读取位置、文件头所在扇区号
    };
    

    因此我们需要该类的构造函数,赋予扇区号的初值。

    OpenFile::OpenFile(int sector) {
        hdrSector = sector; 					// 赋予初值
        hdr = new FileHeader;
        hdr->FetchFrom(sector);				// 获取文件头
        seekPosition = 0;
    }
    

    int WriteAt(char *from, int numBytes, int position);

    由于在第二部分已经给出了未修改部分的 WriteAt 函数,因此下面仅给出该函数修改的关键部分内容。

    if ((numBytes <= 0) || (position > fileLength)) return -1;			// 检查输入
    if ((position + numBytes) > fileLength){
      int incrementBytes = (position+numBytes)-fileLength;
      BitMap *freeBitMap = fileSystem->getBitMap(); 								// 取出位图文件
      bool hdrRet;
      hdrRet = hdr->Allocate(freeBitMap,fileLength,incrementBytes); // 此处修改文件头、位图文件
      if(!hdrRet) return -1;  						// 分配空间时出错
      fileSystem->setBitMap(freeBitMap);  // 更新位图文件
    }
    

    void WriteBack();

    void OpenFile::WriteBack(){
        hdr->WriteBack(hdrSector);
    }
    

    3.3 FileSystem

    BitMap* getBitMap();

    BitMap* FileSystem::getBitMap() {
        BitMap *freeBitMap = new BitMap(NumSectors); //1024个扇区
        freeBitMap->FetchFrom(freeMapFile);
        return freeBitMap;
    }
    

    void setBitMap(BitMap* freeMap);

    void FileSystem::setBitMap(BitMap* freeMap) {
        freeMap->WriteBack(freeMapFile);    // 将位图文件写回磁盘
    }
    

    3.4 FileHeader

    bool Allocate(BitMap *freeMap, int fileSize, int incrementBytes);

    bool FileHeader::Allocate(BitMap *freeMap,int fileSize, int incrementBytes) {
        // 修改位图文件信息以及文件头的信息,但修改结果均未写入磁盘中
        if(numSectors > 30) return false;   								// 超出限定大小
        if((fileSize == 0) && (incrementBytes > 0)){ 				// 在空文件后追加数据
            if(freeMap->NumClear() < 1) return false; 			// 空间不足
            dataSectors[0] = freeMap->Find(); 							// 先分配一个空闲磁盘块,并更新文件头信息
            numSectors = 1;
            numBytes = 0;
        }
        numBytes = fileSize;
        int offset = numSectors * SectorSize - numBytes;		// 原文件最后一个扇区块空闲空间
        int newSectorBytes = incrementBytes-offset; 				// 需要填的数据-最后一个扇区块空闲空间
        // 最后一个扇区的空闲空间足够
        if(newSectorBytes <= 0){
            numBytes = numBytes+incrementBytes; 						// 更新文件头中的文件大小
            return true;
        }
        // 最后一个扇区的空闲空间不足
        int moreSectors = divRoundUp(newSectorBytes,SectorSize);
        if(numSectors+moreSectors > 30) return false;   		// 文件过大,超过30个磁盘块
        if(freeMap->NumClear() < moreSectors) return false; // 无足够扇区用于分配
        for(int i = numSectors; i < numSectors+moreSectors; i++) dataSectors[i] = freeMap->Find();
        numBytes = numBytes+incrementBytes;     						// 更新文件大小
        numSectors = numSectors+moreSectors;    						// 更新文件扇区块数
        return true;
    }
    

    四、实验测试方法及结果

    1. 测试文件

    利用 UNIX 的命令 od 或 hexdump 来检查模拟硬盘 DISK 的内容。测试文件为 …/filesys/test 中 small、medium、big 三个文件。

    使用 od -c test/small 来显示文件信息。

    使用 hexdump -c test/small 来显示文件信息。

    使用 hexdump -C test/small 来显示文件信息。

    2. 编译Nachos的文件系统

    编译 code/filesys,Makefile 内容如下图所示。

    Makefile.local 文件内容如下图所示。

    3. 测试 Nachos 文件系统

    (a)运行 nachos -f 命令,即创建一个 nachos 模拟硬盘 DISK 并创建一个文件系统,当前目录中出现了 DISK 文件。

    (b)在上述命令基础上,运行 nachos -D,显示硬盘 DISK 中的文件系统,如下所示。

    上述信息表示0号扇区为位图文件头,1号扇区为文件目录表文件头,2号扇区为位图文件,3、4号扇区为文件目录表文件。

    (c)继续运行 od -c DISK 命令,即用 od 命令显示 DISK 文件中信息,输出结果如下所示。

    (d)运行 hexdump -c DISK 命令,即用 hexdump 命令显示 DISK 文件中信息,输出结果如下所示。

    (e)运行 hexdump -C DISK 命令,同时显示 DISK 文件偏移量、字节的十六进制、ASCII字符 三个信息,输出结果如下所示。

    (f)运行 nachos -cp test/small small 命令,将 small 文件拷贝到 nachos 模拟硬盘中。

    拷贝完成后,我们运行 nachos -l 命令,输出 nachos 当前的文件目录。我们可以查看到输出结果中包含了 small 这一刚拷贝进去的文件,即 cp 命令执行结果正确。

    继续运行 nachos -p small 命令,查看 nachos 模拟硬盘中 small 文件的具体信息。我们可以查看到输出结果中包含了 “This is the spring of our discontent.” 信息。

    再执行 nachos -D 命令,输出整个 nachos 文件系统的信息,具体信息如下所示。可以看到与最初的nachos模拟硬盘相比,复制small文件后,模拟硬盘中5、6扇区被占用,其中5号扇区为small文件的文件头,6号扇区为small文件的具体信息。

    继续执行 od -c DISK 命令,用 od 命令查看当前 DISK 文件中信息,输出结果如下所示。不难发现,DISK 中 “small“ 信息为3号扇区中文件目标表文件中的信息,而 ”This is the spring of our discontent.“ 则为6号扇区中 small 文件中的信息。

    再执行 hexdump -c DISK 命令,即用 hexdump 命令查看 DISK 文件中信息,输出结果如下所示。该输出信息与od -c 命令的最大区别在于该命令文件偏移量用16进制表示,而 od -c 命令的文件偏移量用8进制表示。

    再执行 hexdump -C DISK 命令,输出结果如下所示。该命令与 hexdump -c 命令最大的差别在于该命令还显示了ASCII 字符的信息。也可以观察到下述文件的数据信息与上述操作显示的信息均一致。

    (h)接下来我们将 medium 文件拷贝进文件系统,然后继续执行上述的 DISK 信息输出,以此来查看模拟硬盘发生的变化。由于在下一部分 “nachos文件系统在硬盘上的布局“ 中较为明确地包含了文件布局的信息,因此接下来我们仅对 hexdump -C DISK 命令之后的结果进行简单分析。(详细分析在下一部分中已经包含的较为完善)

    不难发现,文件目录表中包含了medium文件,且在之后的数据扇区中也出现了medium文件的内容。

    (i)接下来我们将 big 文件拷贝进文件系统,然后继续执行上述的 DISK 信息输出,以此来查看模拟硬盘发生的变化。

    ​ …

    ​ …

    由上述输出信息可以发现文件目录表中包含了big文件,且之后的数据扇区中也出现了big文件的内容。

    (j)接下来我们利用 …/nachos -r medium 命令将 medium 文件从 DISK 中删除,并利用 hexdump -C DISK 命令输出删除后的结果。

    可以发现文件目录中仍然包含 medium 文件名,且在数据块中也包含 medium 文件的数据内容。而删除操作的真正变化在于位图文件中 medium 头文件、数据文件所在扇区均被清空,且文件目录表中 medium 对应文件的 inUse 变量被清空。这些操作的代码细节在第二部分的实验基本方法中均已提及。

    4. 查看 Nachos 文件系统在硬盘上的布局

    4.1 硬盘格式化

    格式化硬盘分别两步,首先(1)将原有 DISK 文件删除,(2)再利用 nachos -f 命令格式化硬盘。
    接下来我们利用 hexdump -C DISK 命令查看 DISK 中最初的数据信息。

    • 前4个字节的信息(0x0~0x3)

    在上述数据信息中,一行显示16个字节信息,每个字节用两个16进制来表示,其中前4个字节(0x0~0x3)为 ab 89 67 45 与之前代码中宏定义的 MagicNumber 一致,作为该磁盘的标识。

    • 0号扇区信息(0x4~0x83)

    之后128个字节(0x4~0x83)为0号扇区存储空间,存放了位图文件的文件头。文件头中变量如下所示,依次为文件字节数、区块数以及每一区块所在扇区位置。因此0号扇区中的第一个字节(0x4~0x7)表示位图文件字节数,即 0x80,为128字节。第二个字节(0x8~0xB)表示位图文件的扇区数,即 0x01,为1个扇区。因此第三个字节表示位图文件第1个数据块所在扇区位置,即 0x02,为2号扇区。

    class FileHeader { // 文件头
    	...
      private:
        int numBytes;			// 该文件的字节数
        int numSectors;			// 该文件的数据区块数
        int dataSectors[NumDirect];		// 每一块数据区所在扇区位置
    };
    
    • 1号扇区信息(0x84~0x103)

    再看1号扇区中的信息(0x84~0x103),该扇区存放了目录表文件头数据,因此前4个字节为目录表文件大小,为0xc8,即200个字节。

    class DirectoryEntry { // 文件目录
      public:
        bool inUse;				// 该目录项是否投入使用
        int sector;				// 文件头所在扇区号
        char name[FileNameMaxLen + 1];	// 文件名,+1用于'\0'
    };
    

    由于在宏定义中定义了文件名长度,因此我们可以得知一个文件目录项大小为一个bool类型,10个char类型,1个int类型。但是 c++ 中要采用数据对齐,因此不同变量存储空间以占用空间最大的变量类型为准,因此每个变量所占空间需要为4的倍数,因此一个文件目录项大小为 4+4+12 = 20 个字节。又由于我们最多定义10个目录项,因此目录表文件大小为200个字节。

    1号扇区第5~8字节表示系统为目录文件数据所分配的扇区数,即0x02,数值为2,分配了两个扇区。

    0x8C~0x8F表示第1个数据块所在扇区,为0x03,即3号扇区。

    0x90~0x93表示第2个数据块所在扇区,为0x04,即4号扇区。

    • 2号扇区信息(0x104~0x183)

    由位图文件头中的信息可知,2号扇区存储了位图文件的信息。位图文件第1个字节为0x1f,即表示前8个扇区是否被占用。11111000,即前5个扇区被分配,其余扇区均为空闲,与间接验证了之前数据的正确性。

    • 3号扇区(0x184~0x203)与4号扇区信息(0x204~0x283)

    3、4号扇区存储了文件目录项的具体信息,目前没有任何文件,因此这两个扇区中值均为0。

    最后 nachos -D 输出整个文件系统的信息用于验证上述观察结果。可以发现只有前5个扇区中有文件,与之前观察的数据一致。

    4.2 复制 small 文件到硬盘

    利用 nachos -cp test/small small 将 small 文件复制到硬盘中,并利用 hexdump -C DISK 命令来输出模拟磁盘数据。

    • 0号扇区(0x4~0x83)与1号扇区(0x84~0x103)仍然存储位图、文件目录表的文件头,文件头信息没有变化,其文件中的数据发生了变化。

    • 2号扇区(0x104~0x183)存储位图文件,其中第一个字节为0x7f,其余字节均为0。0x7f二进制形式为 11111110,即前7个扇区均被占用,其余扇区仍为空。

    • 3号扇区(0x184~0x203)存储文件目录表的部分信息,其中每个目录项都是个三元组 <bool inUSe, int sector, char name[FileNameMaxLen]+1>,且由文件头信息可知当前只有一个目录项,因此前4个字节(0x184~0x187)表示三元组中的inUse,此处采用数据对齐的原则,即与最大的数据类型保持一致,因此此处bool变量占用了4个字节。第一个字节值为01,表示目录项正被使用。

      第5~8个字节(0x188~0x18B)表示该目录项文件头存储的扇区号,此处为05,即5号扇区。

      之后的10个字节为文件名,此处为ASCII码值为 small。

      由于当前只有1个目录项,因此其余目录项均为空。

    • 4号扇区(0x204~0x283)为目录表文件的第2个扇区,目前为空。

    • 5号扇区(0x284~0x303)为 “small” 文件的文件头。每个文件头存储一个3元组,<int numBytes, int numSectors, int dataSectors[NumDirect]>

      第1~4个字节(0x284~0x287),表示该文件的字节数,为0x26,即38字节,查看small文件中的数据,恰好为38个字节,此处数据正确。

      第5~8个字节(0x288~0x28B),表示该文件的扇区数,为0x01,即1字节,该文件占用了1个扇区。

      第9~12个字节(0x28C~0x28F),表示该文件第一个数据块所存储的扇区,为0x06,即6号扇区。

    • 6号扇区(0x304~0x383)为 “small” 文件的数据块,其中 0x304~0x329 为该文件存储信息,0x0a为换行符。

    此处还有一个地方需要注意。即文件目录表占用200个字节,其中并没有计算文件目录表中 tableSize 这个变量,即 nachos 并没有将这个变量存储在模拟 DISK 上,但这个变量仍然存在于 nachos 的可执行文件中,并不会凭空消失。由下面文件目录表的 WriteBack 函数可以得知,nachos 仅将目录表存储在了 DISK 上。

    总结一下,在仅包含一个small文件的文件系统中,模拟硬盘DISK的分布如下。

    扇区起始地址扇区号扇区存储内容
    0x04~0x830号扇区位图文件头
    0x84~0x1031号扇区文件目录表文件头
    0x104~0x1832号扇区位图文件
    0x184~0x2033号扇区目录表文件第一个数据块
    0x204~0x2834号扇区目录表文件第二个数据块
    0x284~0x3035号扇区small文件头
    0x304~0x3836号扇区small文件

    最后输入命令 nachos -D,输出整个文件系统的信息用于数据验证。由下图输出信息可知上述数据分析过程正确。

    4.3 复制 big 文件到硬盘

    利用 nachos -cp test/big big 将 big 文件复制到硬盘中,并利用 hexdump -C DISK 命令来输出模拟磁盘数据。

    ​ …

    • 0号扇区(0x4~0x83)与1号扇区(0x84~0x103)仍然存储位图、文件目录表的文件头,文件头信息没有变化,其文件中的数据发生了变化。

    • 2号扇区(0x104~0x183)存储位图文件,其中第一个字节为0xff,第二个字节为0x1f,其余字节均为0。即1111111111111000,表示前13个扇区均被占用,其余扇区仍为空。

    • 3号扇区(0x184~0x203)中增加了big文件的目录项,表明big文件头存储在7号扇区。

    • 4、5、6号扇区没有发生变化,因此不再赘述。

    • 7号扇区(0x384~0x403)存储了big文件的文件头。

      第1~4个字节(0x384~0x387),big文件大小为0x260,即608个字节。

      第5~8个字节(0x388~0x38B),big文件占用扇区数为0x05,即5个扇区。

      之后的5个int数据表明了上述5个扇区分别存储在8、9、10、11、12号扇区中。

    • 8、9、10、11、12号扇区中存放big文件的数据。

    总结一下,在包含 small 和 big 文件的文件系统中,模拟硬盘DISK的分布如下。

    扇区起始地址扇区号扇区存储内容
    0x04~0x830号扇区位图文件头
    0x84~0x1031号扇区文件目录表文件头
    0x104~0x1832号扇区位图文件
    0x184~0x2033号扇区目录表文件第一个数据块
    0x204~0x2834号扇区目录表文件第二个数据块
    0x284~0x3035号扇区small文件头
    0x304~0x3836号扇区small文件
    0x384~0x4037号扇区big文件头
    0x404~0x6838~12号扇区big文件

    最后输入命令 nachos -D,输出整个文件系统的信息用于数据验证。由下图输出信息可知上述数据分析过程正确。

    4.4 在硬盘上删除文件

    利用 nachos -r small 将 small 文件从模拟硬盘中删除,并利用 hexdump -C DISK 命令来输出模拟磁盘数据。

    ​ …

    • 可以看到位图文件中数据发生了变化,从ff变成了9f,即1111变成了1001,5、6号扇区变空闲。
    • 目录表文件中small文件的inUse变量从0x01变成0x00,即标志该目录项空闲。
    • 除此之外,不难发现 small 文件在目录表中文件名、文件头所占扇区号均为清除。而且 small 文件头中的信息也均未被清楚,文件的内容也未被清楚。

    输入命令 nachos -D,输出整个文件系统的信息用于数据验证。由下图输出信息可知上述数据分析过程正确。

    nachos 调用了 FileSystem::Remove(char *name) 删除文件,上述实验原理中已提及该部分内容。

    最后,我们可以发现,如果被删除文件还没有被覆盖,则我们可以根据文件名在目录表中找到该文件对应的目录项,将 inUse 变量置1,再在位示图中将其对应数据、文件头扇区恢复即可。nachos 删除文件的策略为文件恢复带来了极大的便利。

    5. 扩展文件的实现与测试

    5.1 nachos -ap 与 -hap 命令的实现

    我们观察 main.cc 中是如何实现 nachos -ap 命令的,具体代码如下所示。

    else if (!strcmp(*argv, "-ap")) {  // 将 UNIX 的文件添加到 Nachos 文件的尾部
      ASSERT(argc > 2);
      Append(*(argv + 1), *(argv + 2), 0);
      argCount = 3;
    } 
    

    可以发现 “-ap” 的命令调用了函数 Append(),而该函数主要调用的是 OpenFile::Write(),OpenFile::Write() 调用的是 OpenFile::WriteAt(),因此我们接下来考虑如何修改 OpenFile::WriteAt() 函数来实现该功能。

    修改 OpenFile::WriteAt() 函数

    由实验基本方法中对于原有 WriteAt 函数的分析可以看到,原有函数并不支持写入的数据超过其文件原有大小,因此我们现在需要修改该函数来实现可以从文件尾写数据的功能。

    找到该函数中原有的两个约束,具体代码如下所示。

    // 检查输入是否合法以及避免输入数据超过文件大小
    if ((numBytes <= 0) || (position > fileLength)) return 0;
    if ((position + numBytes) > fileLength) numBytes = fileLength - position;
    

    将第一个约束修改为返回 -1。而对于第二个约束我们需要分类讨论。

    (1)如果原来文件最后一个扇区的剩余空间足以容纳要写入的 numBytes 个字节,则我们不需要为写入操作分配新的扇区,直接在原文件的最后一个扇区中写入数据即可。此处我们需要修改文件头中文件大小属性,并在文件写操作结束后将文件头写会硬盘原来的扇区中。

    此种情况出现的原因在于文件的大小不一定是扇区的整数倍,但我们给文件分配的空间都是整数个扇区大小,因此文件所在的最后一个扇区可能会出现空间浪费,即最后一个扇区有空闲空间。

    (2)如果原来文件最后一个扇区的剩余空间太小,无法容纳要写入的 numBytes 个字节,则需要为写入操作分配新的扇区,即将原文件的最后一个扇区写满后,将剩余数据写入新分配的扇区中。

    因此我们修改第二个约束时,需要修改文件头中文件大小属性,并将新分配的扇区在空闲块管理位示图中对应位置置1,并将修改的文件头和位图写会到DISK中。修改的代码如下所示。

    修改 FileSystem 类,增加 setBitMap() 与 getBitMap() 函数

    修改该类的目的是从能够从硬盘中读取空闲块位示图文件,并在位图文件的内容修改之后再写回磁盘中,修改后的代码如下所示。

    还需要在 FileSystem 类定义中声明这两个函数,具体声明如下所示。

    修改 OpenFile::OpenFile() 以及 OpenFile::WriteBack() 函数

    我们需要需要将修改后的文件头写回硬盘,因此我们需要修改上述两个函数。

    下述代码为 OpenFile 的构造函数,查看该代码可以发现该类中文件头的句柄由文件头所在扇区号所决定。

    OpenFile::OpenFile(int sector) { 
        hdr = new FileHeader;
        hdr->FetchFrom(sector); // 取出该扇区中数据
        seekPosition = 0;   		// 文件读取位置为0
    }
    

    因此我们需要将修改后的文件头写回硬盘时,需要获取该文件头所在扇区号,因此我们在OpenFile类中增加一个 hdrSector 变量用于记录该文件头所在扇区号。

    修改 OpenFile 的构造函数,加入 hdrSector 变量的赋值。

    加入了 hdrSector 变量之后,我们再来实现 OpenFile::WriteBack() 函数,如下图所示。

    [外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-Nvu5WYVa-1597809920243)(/Users/gene_liu/Library/Application Support/typora-user-images/image-20191203113739473.png)]

    修改 FileHeader::Allocate() 函数

    接下来我们需要实现将写入数据分配硬盘空间的函数。由于写入的数据可能利用文件最后一个扇区的剩余空间,也可能为其新分配扇区(硬盘块),因此在代码中我们需要分类讨论。

    首先我们需要为 FileHeader 类添加构造函数,将文件头的扇区索引表清空,具体代码如下所示。

    [外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-sjKTPaB9-1597809920250)(/Users/gene_liu/Library/Application Support/typora-user-images/image-20191203114337701.png)]

    然后我们再来实现 FileHeader::Allocate() 函数,实现过程如下所示。

    我们重载 FileHeader::Allocate(BitMap *freeMap, int fileSize, int incrementBytes) 函数,并根据扩展的数据大小 incrementBytes 来判断是否需要分配新的扇区块,修改后的代码如下所示。

    [外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-d4IixVR6-1597809920252)(/Users/gene_liu/Library/Application Support/typora-user-images/image-20191203193958481.png)]

    在上述代码中,我们分为三步进行实现。

    (1)我们判断是否可以分配,判断扇区是否足够,文件是否过大。如果是空文件我们需要先分配一个单独扇区。

    (2)判断最后一个扇区中的剩余空间是否有足够空间存放,如果能放下就 不bi用开新空间。

    (3)计算需要开辟的新扇区,并在位图文件中寻找新扇区进行分配。

    修改 fstest.cc 的 Append() 函数

    在 Append() 函数中,我们需要不断更新写指针的位置,并在写入操作结束后将文件头写回硬盘中。

    [外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-8qJuOUr1-1597809920253)(/Users/gene_liu/Library/Application Support/typora-user-images/image-20191203141150320.png)]

    至此我们已经实现了 nachos 中 -ap 和 -hap 两个命令,接下来我们进行测试。

    首先我们初始化磁盘,然后将 small 文件拷贝到 nachos 中,并输出 ./nachos -D 显示如下信息。

    [外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-eRgcxTsa-1597809920255)(/Users/gene_liu/Library/Application Support/typora-user-images/image-20191203152507762.png)]

    可以发现 small 文件已经存储在了DISK上,接下来我们执行-ap命令,将big文件添加到small文件的后面,并输出 ./nachos -D 显示磁盘信息,具体结果如下所示。

    [外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-ep6Ry8Hp-1597809920256)(/Users/gene_liu/Library/Application Support/typora-user-images/image-20191203152439784.png)]

    可以发现,big文件已经添加到了small文件的后面,结果正确。接下来我们继续测试-hap命令,我们先初始化磁盘,然后将small文件拷贝到DISK中,最后用-hap命令将big文件添加到small文件的后面,我们来查看输出结果有何不同。

    该命令即从nachos文件的中间位置开始添加,并覆盖掉nachos文件的后半部分,因此我们观察结果可知 small 文件的 ***end of file*** 结尾符也被覆盖了,由此可知 -hap 命令执行正确。

    5.2 nachos -nap 命令的实现

    nachos -nap fromNachosFile toNachosFile。该命令将DISK中的其中一个Nachos文件添加到另一个Nachos文件的尾部。由于在实现-ap命令的过程中,我们已经实现了nachos文件的动态增长以及位图、文件头的更新等,因此我们只需在 NAppend() 函数中作出部分修改,并将最后的更新结果写会到磁盘中即可完成该命令的实现。具体修改内容如下所示。

    在该部分中,我们利用OpenFile中的Write函数不断通过缓冲区取出from文件中的数据然后写入到to文件中。并在写入完成之后将更新的文件头写入磁盘中。

    接下来我们测试这个功能是否可以正常运行,我们先格式化DISK,然后依次利用-cp命令将small文件和big文件拷入DISK中,然后利用 ./nachos -D 命令查看DISK中的数据,结果如下图所示。

    img src="/Users/gene_liu/Library/Application Support/typora-user-images/image-20191203160043962.png" alt="image-20191203160043962" style="zoom:60%;" />

    根据上述数据,可以看到small文件和big文件均已存储在了DISK中,接下来我们调用-nap命令,将small文件添加到big文件之后,然后再利用 ./nachos -D 命令输出执行结果,结果如下图所示。

    可以看到big文件之后附上了small文件的内容,该命令运行正确。

    5.3 nachos 文件系统测试

    (1)我们先格式化DISK,然后将small文件通过-cp命令拷入DISK中,并输出当前DISK中的信息,如下图所示。

    [外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-UcJ6MdDN-1597809920256)(/Users/gene_liu/Library/Application Support/typora-user-images/image-20191203161941551.png)]

    当前DISK中只有small一个文件。

    (2)接下来我们尝试继续执行 ./nachos -cp test/small small 命令,观察输出结果。

    [外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-H3WCrOLB-1597809920257)(/Users/gene_liu/Library/Application Support/typora-user-images/image-20191203162056058.png)]

    由于DISK中已有small文件,因此无法继续将small文件拷入DISK中。

    (3)接下来我们执行 ./nachos -ap test/big small 命令,将 UNIX 中的 big 文件添加到 nachos 中的 small 文件之后,我们再查看 small 文件的信息。

    [外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-EzTVXJ1W-1597809920258)(/Users/gene_liu/Library/Application Support/typora-user-images/image-20191203162354501.png)]

    可以观察到位图信息的改变,以及 small 文件后面的确添加上了 big 文件,执行结果正确。

    再执行 hexdump -C DISK,我们可以看到 -ap 命令的执行过程的确是将 small 文件所在扇区的剩余内容填满之后才开辟的新扇区继续添加数据。

    [外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-38J9oJdT-1597809920259)(/Users/gene_liu/Library/Application Support/typora-user-images/image-20191203162543769.png)]

    (4)执行 ./nachos -ap test/medium medium 命令,测试给一个空文件追加数据的功能。

    [外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-UnayS4SR-1597809920259)(/Users/gene_liu/Library/Application Support/typora-user-images/image-20191203162759815.png)]

    可以发现 medium 文件成功地添加进了 DISK 中,该命令执行正确。-ap 命令执行过程中,如果 to 文件不存在,则会创建一个 to 文件用于数据添加。

    (5)再执行 ./nachos -ap test/big small 命令,查看 nachos 是否会为 small 文件开辟不连续的扇区。我们先执行 -p small 命令,查看 big 文件是否添加成功。

    由上图可知,big 文件添加成功。接下来我们再运行 hexdump -C DISK 命令查看 small 文件所分配的扇区是否不连续。

    由上图可知,small 文件的内容的确不连续,中间隔了medium文件头以及medium数据块的信息。

    (6)执行 ./nachos -hap test/medium small 命令,测试从 small 中间写入文件的功能。执行完该命令后,我们再执行 hexdump -C DISK 命令来输出DISK中的具体信息,输出结果如下。

    可以发现该命令执行正确,medium文件信息成功从small文件的中间开始写入,并覆盖原有信息。而未被覆盖的信息保持不变。

    (7)执行 ./nachos -nap medium small 操作,将 medium 文件添加到 small 文件之后,该命令执行后的输出结果如下所示。

    medium 文件的内容成功添加到了 small 文件之后,该命令执行结果正确。

    (8)执行 ./nachos -r small 命令,测试文件删除功能。该命令执行后的DISK硬盘信息如下图所示。

    [外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-P1E7nvdO-1597809920260)(/Users/gene_liu/Library/Application Support/typora-user-images/image-20191203173408494.png)]

    可以发现 DISK 中仅剩 medium 文件头与数据部分,分别占10、11、12号扇区。而 small 文件虽然被删除了,但由于 nachos 的删除机制,small 的数据并没有消失,只是位图文件中 small 文件所在扇区被清空了,以及 small 文件头的 inUse 变量被置 0 了。我们可以用 hexdump -C DISK 命令来验证这一观点。

    可以发现,small 文件的数据仍然存在,因此这一删除机制也更利于数据恢复。

    (9)删除 small 文件之后,我们再执行 nachos -l、-p small 命令,输出结果如下图所示。

    [外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-gG5m2a0o-1597809920261)(/Users/gene_liu/Library/Application Support/typora-user-images/image-20191203173805101.png)]

    再执行 ./nachos -p small 命令,输出结果如下所示。

    [外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-TCwynRR3-1597809920261)(/Users/gene_liu/Library/Application Support/typora-user-images/image-20191203173833913.png)]

    可以看到 small 文件的确已经从 nachos 中删除了,-r 命令执行结果正确。

    (10)继续执行 nachos -ap test/big small 操作,查看 nachos 文件系统对一个文件长度的限制。由于在 nachos 中一个文件头所占空间为一个扇区,因此一个文件最多占用 30 个扇区,即为 30 ∗ 128 B = 3840 B 30*128B = 3840B 30128B=3840B

    我们反复执行 nachos -ap test/big small 操作之后,当 small 文件超过最大容量限制时,将出现错误。

    [外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-3mOOYCsi-1597809920262)(/Users/gene_liu/Library/Application Support/typora-user-images/image-20191203174532066.png)]

    因此我们利用 ./nachos -D 操作查看 DISK 中的信息,输出结果如下图所示。

    [外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-5CBkcSV5-1597809920263)(/Users/gene_liu/Library/Application Support/typora-user-images/image-20191203174610160.png)]

    可以发现 small 文件占用了 30 个扇区,但30个扇区是否都占满了呢?我们执行 nachos -ap test/medium medium 命令,再执行 hexdump -C DISK 命令查看 DISK 中文件存储信息。

    [外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-eJ8rzpX5-1597809920263)(/Users/gene_liu/Library/Application Support/typora-user-images/image-20191203194528874.png)]

    由上图可以发现,30个扇区恰好完全填完。由下述的代码可以发现 nachos 执行 Append 操作时,每次从源文件中取出 10 字节数据存入 nachos 中,如果发现数据存入发生错误则退出。因此由于文件过大无法存入 DISK 时,未填充的字节空间一定小于 10 字节。

    [外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-ftmaSvSQ-1597809920264)(/Users/gene_liu/Library/Application Support/typora-user-images/image-20191203194852748.png)]

    (11)接下来我们继续执行 nachos -ap 操作来创建新的文件,测试 nachos 文件系统中是否最多只能创建 10 个文件。

    当创建到第 11 个文件时发生了错误。我们使用命令 ./nachos -l 来查看当前硬盘中的文件目录。

    可以发现当前 DISK 中恰好有 10 个文件,验证了 nachos 文件系统中最多只能创建 10 个文件说法的正确性。

    (12)我们测试能否创建空文件。建立空文件 empty,使用命令 ./nachos -cp test/empty empty,查看是否可以创建成功。

    执行过程未发生错误,我们调用命令 ./nachos -l 来查看 DISK 中存在的文件,查看 empty 文件是否创建成功,输出结果如下所示。

    由上述的输出结果可以得知 empty 文件创建成功,我们再调用命令 ./nachos -D 来查看 empty 文件是否真的存在,以及该文件所占用的空间,输出结果如下图所示。

    由上述信息可以得知 empty 文件大小为 0,即空文件。因此在 nachos 中创建空文件的功能实现正确。

    至此我们测试了本实验中给出的以及要求自行实现的各个命令行参数,由输出数据可知各命令行参数实现正确。

    五、实验体会

    1. nachos 实现的模拟硬盘 DISK,将所有数据全部存储在了 DISK 中,这意味着没有其它文件数据被存放于 nachos 的可执行文件中。包括位图、文件头等信息均存放于 DISK 中,使用时则从 DISK 中取出,用完即将指针回收。文件系统中的所有数据信息都采用句柄的形式,仅保存类指针,并不保存实际数据,使得空间利用效率大大提高。

    2. 本次实验熟悉了 nachos 文件系统中的文件系统命令,包括将文件直接拷贝到 nachos 模拟硬盘中、输出模拟硬盘中的文件目录、输出模拟硬盘中某个文件的内容以及完整输出整个文件系统的所有信息等功能,功能较为全面且实用。

    3. 除了 nachos 的文件系统命令外,该实验也介绍了 Linux 指令来查看文件中的各位数据,如 od -c filenamehexdump -c filenamehexdump -C filename 等指令来查看文件的 ASCII 码或 16 进制数据。

    4. nachos 文件系统删除文件的策略比较经典且实用。删除文件时仅将文件目录表中该文件的 inUse 变量置 0,以及在位图中将该文件数据、文件头所在扇区清空,而不删除该文件的具体数据。这样的删除操作有几个好处,(1)不会影响删除操作的正确实现;(2)删除操作执行迅速;(3)便于数据恢复。

    5. 在实验五中要求我们实现 -ap、-hap、-nap 三个命令,其中 -ap、-hap 命令调用的是 Append() 函数,而 -nap 命令调用的是 NAppend() 函数,其中三个命令的实现路径如下图所示。

      [外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-EL25bXu7-1597809920264)(/Users/gene_liu/Library/Application Support/typora-user-images/image-20191203225458352.png)]
      实现这三个命令的过程中,我们主要实现了 FileHeader::Allocate() 函数,其余函数更多的主要是在原基础上进行了一些修改或者实现比较简单。

    6. 在 nachos 文件系统中,复制一个文件时所采用的策略是将文件按 10 字节大小的缓冲区进行划分,每次仅传输 10 字节大小的数据写入 DISK 中。这样做的目的是将一个大文件拆成多个小文件进行依次传输,降低传输过程中出错的概率,并且一旦数据传输出现问题,可以查看调试信息确定出错的位置。

    7. 除此之外,在 nachos 文件系统中,将 A 文件 append 到 B 文件末尾时,如果 B 文件所占空间已经到达 30 个扇区,且最后一个扇区仍有空余。则 nachos 会将 A 文件按照最大 10 字节的方式进行拆分,边拆分边将数据传入 DISK 中,上一次数据传入成功,才会进行下一次传送。因此可能会出现 A 文件的部分内容传入了 B 文件末尾,但由于传输过程 B 文件所占扇区数已经达到了 30,导致传输中断。此时 B 文件最后一个扇区的空闲空间小于 10 个字节,且 A 文件仅传输了部分数据到 B 文件中。

    8. 此次实验作为目前为止第一个修改了很多代码的一个实验。在修改代码的过程中,先分析需要实现的命令,再考虑有哪些已经实现的函数可以调用也显得十分重要。因此在之后的实验中,需要实验新的命令时,我们需要仔细分析实现这个命令的主要难点与困难点,并有效地利用实验中已经实现好的各个函数,使整个代码的冗余部分尽可能地少。

    9. 在做实验的过程中,发现 nachos 的文件系统还有很多的扩展空间,比如我们可以一个从文件任意位置进行 append 的函数,甚至将 nachos 中的单级索引目录表修改成一个支持多级索引目录表的文件系统。除此之外,位图也有很大的优化空间。在 nachos 中,位图这个数据结构询问还有多少个空闲扇区以及查询一个空闲扇区时均采用了 O(n) 遍历的方式,但由于 nachos 文件系统中磁盘空间本身比较小,这种 O(n) 的遍历方式便不会有很明显的效率降低。但一旦 nachos 的磁盘空间比较大,这种方式的开销就非常巨大,更常用的应该是记录用一种类似于二叉树的结构将寻找空闲扇区的复杂度降为 O(logn),记录一个变量用于维护当前还有多少个空闲扇区,从 O(n) 降到 O(1),这种数据索引方式在较大的磁盘空间中使用更为有效。

    10. 总结一下,此次实验介绍了 nachos 中的文件系统,并引导实验者实现了 3 个文件系统的命令行参数,加深了实验者对于 nachos 中文件系统的理解,并开始自行实现略为复杂的代码,为之后的实验打下了良好基础。

    展开全文
  • nachos实验一体验Nachos下的并发程序设计 代码
  • nachos中文教程

    2011-07-21 10:52:18
    nachos中文教程,非常全面的学习nachos,电子书
  • nachos-文件系统PPT

    2018-10-03 13:08:20
    教学课件,操作系统-NachOS文件系统讲义

空空如也

空空如也

1 2 3 4 5 ... 20
收藏数 1,601
精华内容 640
关键字:

Nachos