【操作系统】 简易文件系统
这个是本学期最后一次的操作系统实验,个人觉得做得还行,故写篇博客分享一下。
实验要求
-
创建一个100M的文件或者创建一个100M的共享内存 -
尝试自行设计一个C语言小程序,使用步骤1分配的100M空间(共享内存或mmap),然后假设这100M空间为一个空白磁盘,设计一个简单的文件系统管理这个空白磁盘,给出文件和目录管理的基本数据结构,并画出文件系统基本结构图,以及基本操作接口。(30分) -
在步骤1的基础上实现部分文件操作接口操作,创建目录mkdir,删除目录rmdir,修改名称,创建文件open,修改文件,删除文件rm,查看文件系统目录结构ls。(40分)注明:全部在内存中实现 -
参考进程同步的相关章节,通过信号量机制实现多个终端对上述文件系统的互斥访问,系统中的一个文件允许多个进程读,不允许写操作;或者只允许一个写操作,不允许读。(30分)
(1)磁盘基本数据结构
首先申请一段100M的内存空间,将这段内存空间视为我们的磁盘空间,来进行本次实验的磁盘的文件系统管理。这里我一开始是使用malloc操作进行,但在第三部分中需要用到信号量机制,需要进行进程间通信,所以在最后还是使用了共享内存的方式进行。
使用共享内存的方式参考先前的综合实验1进行。这里申请了100MB空间之后,将得到共享内存ID:shmid,使用shmat将共享内存映射到进程空间中,则使用shared_memory即可访问共享内存。
关于文件系统的结构划分,这里为了尽可能模拟Linux的ext2文件系统,于是采取以下划分,与ext2不同的是,由于一个4KB的数据块,最多表示410248*(4*1024) = 128MB的数据空间,而128MB在Linux系统中是远远不够用的,于是在ext2文件系统中,采用了多个块组的形式。而本次实验中,由于我们申请的内存空间仅有100M,于是使用一个块组的形式是足够使用的,于是为了简单起见,本次实验将整个磁盘空间视为一个块组,同时也不需要使用引导块来模拟linux的启动引导功能。
下图展示的是linux中的Ext2文件系统,从下图中可以看出与本次实验的不同之处,在ext2文件系统中,一个块组中含有多个块组描述符,来存放文件系统中各个块组的状态,但是本次实验并不需要,所以本次实验省略块组描述符项。
在文件系统中,超级块存储的是块组中的全局信息,例如空闲inode的个数等,不过这些信息存入在超级块中在本次实验过程中没有体现出其应有的作用,因为我们只用了一个超级块,而超级块在第三部分进程间通信中就体现了其重要之处,我们可以将一些块组全局的信息存储在超级块中,由于超级块是出于我们的“磁盘”的一部分,是共享内存的一部分,于是不同的进程之间可以通过访问超级块来获取块组的一些全局信息,这些信息是重要的。在本次实验中,本来应该将一些其他的静态信息存储入超级块中才能尽可能模拟ext2,但是由于时间关系,这里只存入了必要信息。下图是本次实验中超级块的声明。
注意到这里我使用了一个union联合这个数据结构,使用这个数据结构的原因是因为在ext2文件系统中,由于磁盘的读取是按照一个一个数据块来读取的,超级块是需要占据一个数据块的,为了使超级块所占的内存满足一个数据块的大小,于是将超级块的本身内存作为一个结构体和一个数据块数据结构联合起来,使得超级块真实存储在一个数据块中。
物理数据块设置为一个char结构体,BLOCK_SIZE为4096,也就是占4KB。
关于数据区和位图,其结构如下图所示,为了简单起见和时间关系,这里采取一个数据块存储一个文件的方式(有时间是可以改成混合索引的方式),下图中的数据区中,每一个单位为一个数据块,然后位图单独占据一个数据块。
我们使用一个char数组来实现一个bitmap,由于一个char为一个字节8个位,所以一个char是可以表示8个数据块的,于是我们bitmap的行就只要BLOCK_SIZE/8,即可用BLOCK_SIZE个位来表示所有的数据块。
由于在UNIX中万物皆文件,所有文件和目录的存储方式应该是比较相似的,目录和文件之间的关系如下图所示,他们的区别在于目录中的文件区存储的是目录项,而文件中的文件区存储的是数据。目录中的一个目录项位一个文件的FCB,一个FCB就是一个目录项。
而目录和文件的数据结构的定义如下图所示。文件File是由目录区中的FCB和文件区的数据部分组合起来的,然后目录的DirFile是由目录区中的FCB和文件区中的多个FCB项组合而成的。这个定义和上图的逻辑结构是相同的。文件和目录的区别主要在于区每一个记录的类型不一样。
而FCB中按照ext2的定义是由一个文件名和索引结点编号组成。存放的是文件的控制信息,索引结点编号指代的是inode区中的inode编号,如下图所示。一个索引指针指向了inode区中的一个inode项,然后inode项中含有数据区的编号,通过这个编号索引到文件所在的位置。
在inode中存放的是关于文件的一些控制信息,这里我在inode项中定义了四项,分别为文件类型、父结点的inode编号,文件的大小以及数据块的编号。在ext2文件系统中,数据块号这一项是由含15个指针的数组组成的,其中12个为直接索引项,1个一级索引,1个二级索引,1个三级索引,通过这个数组实现的混合索引,但是这里由于时间关系和简单起见,简单的定义一个数据块号中只放有一个文件,也就是我们文件的大小不大于4KB。
关于inode其声明如下图所示。
(2)实现基本文件管理操作。
当磁盘结构规定好之后,就可以进行文件管理操作的编写,首先是mkdir语句,该条语句为在当前目录下新建一个新的目录。那么问题来了,我们的根目录怎么办。我做了一个判断,当没有根目录的时候创建一个。这里则要求我们使用两个变量,当前文件名和当前目录文件指针。
新建一个目录,需要先查询空闲inode,空闲的状态可以由位图反映出来,这里查询位图分配空间inode的操作如下图所示。
这里的操作比较神奇,也是本人在本次实验中的得意之处。按照我们磁盘的划分结构,我们的bitmap是占用了一整个数据块的,所占用的内存是和数据块大小一致的。于是只要得到数据块之间位图的起始位置,就是程序中的位图的指针所指向的位置。所以只要将datablock偏移数据块到位图起始位置,然后将其地址强制类型转化为bitmap类型,就可以用bitmap解释这个数据块。而bitmap由char数组组成(在先前已经提到),一列为8个字节,需要进行位操作。
这里将需要判断的位通过右移到第一位,然后和1相与判断这个数的奇偶性,结果就是这个位为1还是0,然后当其为0时,我们要设置位图相应位为1,也就是使用这个位置的inode,这里将1做左移操作,到相应的位后与原位图相或操作,将相应位置为1,最后再返回位在位图中的位置。
同理,对于数据块的位图,也是类似的操作。
当得到inode的位置之后,要索引到inode区的位置,这里的实现方式如下图所示,同样,由于inode是4个int是16个字节,是4KB的因数,能够充斥所有的数据块,这里同样将inode区的起始指针通过强制类型转化位Inode,用inode解释这个区域,于是inode的位置就可以通过起始位置加指针偏移得到。
同样的,对于数据区的索引,也是同样的方式。
整个mkdir的实现如下:
首先是先得到inode位置和数据块位置编号,然后索引到在“磁盘”中的实际位置,然后由于这里新建的是一个目录,所以通过类似的方式,将数据块指针强制类型转化位目录类型指针,用目录来解释这个数据块,然后就可以进行数据块的写入了。当数据块写入完毕之后,在将这个目录的FCB拷贝到当前目录的目录项中,让当前目录项增加一项。
下面是rmdir的写法:
其中主要的方法为删除目录项,这一项的实现方式如下:
由于这里目录项的存储方式是无序的,所以这里的删除目录项使用的代替的方式。代替的方式如下图所示。
实现这两个主要的函数之后,其他的函数比如ls、ll、touch(新建一个文件)等等都是类似而且比较简单的,出于篇幅限制这里不再展示,并且本次实验我还通过目录这种树型结构实现了简单的cd命令,思路就是当前路径的跳转和转换,不属于实验要求,此处不再展示。
(3)信号量机制。
第三部分为实现进程间通信,使得在进行读写操作的时候,按照读写者问题,实现一个写写互斥、读写互斥,读读不互斥的功能。这里按照助教给的方式结合有名信号量实现即可。实现过程如下图所示,其中中间的switch为读入命令之后的函数调用选择。
个写写互斥、读写互斥,读读不互斥的功能。这里按照助教给的方式结合有名信号量实现即可。实现过程如下图所示,其中中间的switch为读入命令之后的函数调用选择。
工程文件见:https://download.csdn.net/download/Dong142857/21866819
|