项目5:GOSFS文件系统
2.6 Project5
2.6.1项目设计目的
了解文件系统的设计原理,掌握操作系统文件系统的具体实现技术。
2.6.2项目设计要求
为实现GOSFS文件系统,用户在“/src/geeekos /gosfs.c”中添加代码,实现以下函数。
- GOSFS_Fstat()函数:为给定的文件得到元数据;
- GOSFS_Read()函数:从给定文件的当前位置读数据;
- GOSFS_Write()函数:从给定文件的当前位置写数;
- GOSFS_Close()函数:关闭给定文件;
- GOSFS_Read GOSFS_Fstat_Directory()函数:为一个打开的目录得到元数据;
- GOSFS_Close_Directory()函数:关闭给定目录。_Entry()函数:从打开的目录表读一个目录项;
- GOSFS_Open()函数:为给定的路径名打开一个文件;
- GOSFS_Create_Directory()函数:为给定的路径创建一个目录;
- GOSFS_Open_Directory()函数:为给定的路径打开一个目录。
- GOSFS_Delete()函数:为给定的路径名删除一个文件;
- GOSFS_stat()函数:为给定的路径得到元数据(大小,权限等信息);
- GOSFS_Sync()函数:对磁盘上的文件系统数据实现同步操作;
- GOSFS_Format()函数:格式化GOSFS文件系统操作;
- GOSFS_Mount()函数:挂载文件系统操作。
2.6.3原理分析
从Geekos系统层面分析,其原本搭载的文件系统为PFAT,需仿照PFAT文件的实现原理来实现GOSFS文件系统。
1.磁盘的逻辑结构
图表 1磁盘的逻辑结构
2.文件基本框架
图表 2文件的基本框架
3.文件系统的结构
图表 3文件系统的结构
4.多层索引的图示
图表 4多层索引的演示
2.6.4主要操作分析
(1)FORMAT格式化操作:GOSFS_Format()函数,目的是将原始磁盘格式化为GOSFS格式,将挂载区域blockDev进行GOSFS格式化。
利用Get_Num_Blocks函数获取磁盘容量,并FindNumBlocks函数计算所需块数量。创建根目录,使得指针指向目录,并创建超级块,即GOSFS_Superblock结构体(定义于gosfs.h头文件下),Malloc(byteCountSuperblock)赋予超级块的空间,GOSFS_Superblock->magic变量表示在GOSFS_Mount挂载中检查是否为GOSFS文件系统,其块数量以及大小也需赋值。最后将超级块写入硬盘即可,此处,写入时需用到缓存来进行辅助操作。
根据所掌握的信息可知,Format操作完成文件系统的格式化操作时,在系统编译时GeekOS会自动生成10兆大小的磁盘镜像,并初始化内容为全零。GOSFS_Format将这种原始磁盘格式化为GOSFS格式的磁盘,操作包括写入引导块,文件系统初始超级块以及根目录信息节点。需要注意的是,该操作只是针对GOSFS,不能格式化PFAT。
(2)挂载GOSFS操作:GOSFS_Mount()函数,目的是挂载mountPoint指向的文件系统。
该操作首先初始化内存中的GOSFS_Superblock,之后初始化Mount_Point,最后创建一个空目录,通常规定为/d。具体操作为,首先建立一个新的高速缓冲区(缓冲区的存取的互斥的),标记超级块,以及超级块里的数据,利用超级块结构体下的magic变量,进行魔数检查,验证是否为GOSFS文件系统,若检查是,则释放缓冲区,并创建文件系统实例(创建GOSFS文件系统的空目录)。
据所掌握的信息可知,GOSFS_Mount操作负责将一块GOSFS格式的磁盘挂载到GeekOS。这个操作必须在磁盘访问操作进行之前完成。挂载操作首先初始化内存中的实体超级块,之后初始化虚拟超级块,最后初始化GOSFS根目录“d”。挂载操作成功完成后,文件系统就可以访问超级块与文件系统根目录,并完成相应操作。
(3)高速缓冲区操作:其实际上是开内存上开辟一个空间,使数据读写时通过该缓冲区,即使得一部分保留在内存当中。当然,在高速缓冲区(内存)中读写数据明显要比在磁盘中读写数据要快得多。并且,缓冲区的存取的互斥的,保证了数据的正确性和安全性。
高速缓冲区的使用需要经过申请-修改-释放三个步骤,其在bufcache.c文件中有所定义,Create_FS_Buffer_Cache()函数为创建一个新的缓冲区,Get_FS_Buffer()函数为调用该创建的缓冲区,并带有代码Mutex_Lock(&cache->lock)(表示在缓冲区上加锁),也就是确保缓冲区的存取互斥,等待Get_Buffer函数调用成功后,既可释放锁。其Modify_FS_Buffer()函数为修改缓冲区操作,Sync_FS_Buffer()函数为同步缓冲区操作,而Release_FS_Buffer()函数为释放缓冲区操作。
(4)挂载点、索引以及重要关联文件(目录)结构体介绍:
a.挂载点:gosfs.c文件下有一个很重要的内容,就是挂载点Mount_Point,其Mount_Point结构体在vfs.h中有定义,作为挂载点,是进行文件操作的关键对象。
其Mount_Point结构体中,包含了Mount_Point_Ops数据结构(与各种基本文件操作相关),而辅助基本文件操作的指针则存储在该定义下的fsData变量之中。
Mount_Point挂载点也是辅助格式化并挂载完成GOSFS文件系统,即创建文件及目录。
b.Inode索引:GOSFS文件系统使用inode形式,在每一个目录下分配一个inode,inode表示目录条目的数量,而非物理大小。而读文件即可通过inode文件索引来找到所需的目录,inode可保存目录和文件到相关信息,其中,有blockLisas成员数组记录了文件数据块指针。
c.关联文件结构体:利用该结构体可有效关联文件的基本操作
static struct File_Ops s_gosfsFileOps = {//文件 &GOSFS_FStat, &GOSFS_Read, &GOSFS_Write, &GOSFS_Seek, &GOSFS_Close, 0, /* Read_Entry */ &GOSFS_Clone, }; |
d.关联目录结构体:利用该结构体可有效关联目录的基本操作
static struct File_Ops s_gosfsDirOps = {//目录 &GOSFS_FStat_Directory, 0, /* Read */ 0, /* Write */ // 0, /* Seek */ &GOSFS_Seek, &GOSFS_Close_Directory, &GOSFS_Read_Entry, }; |
(5)读写操作:通过读写操作来分析具体过程。
了解写操作,GOSFS_Write()函数,针对写操作,首先对其上锁,防止读写同步操作,污染数据或者读到脏数据,检查写操作是否被允许,若被允许写入时,计算需要写入的数据块(即赋值初始数据块、起始数据块地址、末尾数据块),后将文件中的数据按块的方式写入新块。
在写入时使用循环的方式利用CreateFileBlock函数分配空间,并利用GetPhysicalBlockByLogical函数,即通过块的线性地址得到块的物理地址,计算开始写入的物理地址,利用Get_FS_Buffer缓冲区写入数据,当然,写完记得释放缓冲区,同时,使inode信息和文件描述符保持最新,并释放锁。最后,返回写好完毕的指令。
而读操作,与写操作类同。
其是从给定文件的当前位置读数据。首先了解下GOSFS_FileEntry结构体(在gosfs.h头文件中),其中定义着文件索引以及文件对象(对象中包括锁和缓冲区),即inode文件索引、instance文件对象(GOSFS_Instance结构体)。
首先对其上锁(即instance文件对象中的锁),防止读写同步操作,污染数据或者读到脏数据,检查读操作是否被允许,若被允许读出时,检查文件指针是否在文件尾,通过循环的方式,从块头到块尾查找,并标记实际地址。同样,利用Get_FS_Buffer缓冲区读出数据,读取完释放缓冲区,并将指针更新读到的地方,至此,具体读操作完成。
(6)其它文件操作:举例目录方面的具体操作。
a.创建一个指定路径的目录,GOSFS_Create_Directory()函数中实现。有定义目录结构GOSFS_Directory、高速缓冲区FS_Buffer、文件对象GOSFS_Instance。
首先,将挂载点的信息mountPoint->fsData复赋值于该文件对象GOSFS_Instance,上锁,获取存储空间,并获取所指定路径,从路径中删除文件名,并清空已有索引。检查是否已经存在该路径下的目录,若无,则标记好索引,并对文件对象进行适当的填充,最后,释放锁。
b.删除有给定路径命名的文件或目录,与其创建的操作类同(当然,首先必须先找到给定路径,检查是否存在)。
c. 从打开的目录中读取目录项,GOSFS_Read_Entry()函数中实现。
必须知道的是,在每个创建的目录下,都会有一个独特的索引inode、类型type以及里头所定义的文件名(可查看GOSFS_Directory结构体,在gosfs.h文件中)。
在该函数中,主要使用了两个标准C库函数strcpy()和memcpy(),此两函数区别在于strcpy提供了字符串的复制。即strcpy只用于字符串复制,并且它不仅复制字符串内容之外,还会复制字符串的结束符。memcpy提供了一般内存的复制。即memcpy对于需要复制的内容没有限制,因此用途更广。
利用strcpy()复制目录下所有的文件名,而memcpy()则复制目录下的索引信息。
2.6.5运行输出分析结果
原有文件格式为PFAT系统 测试文件格式化:format ide1 gosfs 挂载GOSFS文件:mount ide1 /d gosfs 显示所有文件:ls /d 创建文件夹:mkdir /d/xxx 创建文件:touch /d/xxx 删除文件夹或者文件:rm /d/xxx |
1.格式化与挂载GOSFS文件系统
(2)显示所有文件
(3)创建文件夹以及文件
(4)删除文件夹以及文件
2.6.6解决作业问题
(1)GeekOS系统原始的文件系统是什么格式?
原始的文件系统是PFAT文件系统。
(2)在GeekOS系统中,如何注册挂载一个新的文件系统?
GOSFS_Format()和GOSFS_Mount()两个函数,前者先格式化硬盘,后者主要用于挂载到GeekOS上。
(3)在新文件系统中,文件目录项如何设置?
每个目录分配一个inode,表示目录条目的数量,而不是物理大小,具体的内容仅由inode的直接块指针引用,创建目录项实体,主要涉及GOSFS_Dir_Entry结构体,其中保存了目录和文件的相关信息,其中的blockLisas数组记录了文件数据块指针。
(4)在新文件系统中,如何创建一个文件?
通过调用自定义Find_InodeByName()子函数检查文件是否存在,如果文件不存在,再进行写操作的检查,允许写操作则调用CreateFileINode()函数来创建inode节点,其中进行搜索根节点和间接节点的操作;如果文件存在,则调用vfs.c中内置的Allocate_File()分配文件对象,其中文件对象参数s_gosfsFileOps关联了文件相关操作&GOSFS_Write(),&GOSFS_Read(),&GOSFS_Seek(),&GOSFS_Clone()等,最后返回该文件指针。
也可以认为是,先通过Find_InodeByName()判断文件是否存在,存在了就不能拿这个名创建,不存在就可以创建文件,先用CreateFileINode()函数来创建inode节点,接着创建文件实体,接着把文件实体的信息赋值好,接着通过Allocate_File()分配文件对象,最后返回该文件指针。
(5)在新文件系统中,如何读写一个文件?
写文件操作:检查写操作是否允许被执行,接着计算需要写入的数据块startblock以及写入起始地址startblockOffset,for循环计算需要写入的数据块数量,每一次循环调用CreateFileBlock()分配空间,通过GetPhysicalByLogical()函数来计算开始写入的物理地址,最后通过Get_FS_Buffer()函数写数据,结束循环后更新inode节点的状态信息读文件操作:检查读操作是否允许被执行,再检查文件指针是否在文件尾,遍历开始块到结束块,计算开始读的物理地址,将数据读入缓存,最后不用时记得释放缓存。
(6)在新文件系统中,文件的目录结构如何设置?
文件的目录结构分为直接块、间接块还有二级间接的方式。
(7)在新文件系统中,如何给一个文件分配磁盘空间?
以块的形式给一个文件分配磁盘空间。
gitee代码地址,仅供参考,请勿模仿!
gitee地址https://gitee.com/Nal_9526/csdn_my_code/tree/master/geekos-0.3.0