34 | 仓库管理:如何实现文件的六大基本操作?
下载APP
关闭
渠道合作
推荐作者
34 | 仓库管理:如何实现文件的六大基本操作?
2021-07-26 LMOS 来自北京
《操作系统实战45讲》
课程介绍
讲述:陈晨
时长20:33大小18.77M
你好,我是 LMOS。
我们在上一节课中,已经建立了仓库,并对仓库进行了划分,就是文件系统的格式化。有了仓库就需要往里面存取东西,对于我们的仓库来说,就是存取应用程序的文件。
所以今天我们要给仓库增加一些相关的操作,这些操作主要用于新建、打开、关闭、读写文件,它们也是文件系统的标准功能,自然即使我们这个最小的文件系统,也必须要支持。
辅助操作
通过上一节课的学习,我们了解了文件系统格式化操作,不难发现文件系统格式化并不复杂,但是它们需要大量的辅助函数。同样的,完成文件相关的操作,我们也需要大量的辅助函数。为了让你更加清楚每个实现细节,这里我们先来实现文件操作相关的辅助函数。
操作根目录文件
根据我们文件系统的设计,不管是新建、删除、打开一个文件,首先都要找到与该文件对应的 rfsdir_t 结构。
在我们的文件系统中,一个文件的 rfsdir_t 结构就储存在根目录文件中,所以想要读取文件对应的 rfsdir_t 结构,首先就要获取和释放根目录文件。
下面我们来实现获取和释放根目录文件的函数,代码如下所示。
上述代码中,get_rootdir 函数的作用就是读取文件系统超级块中 rfsdir_t 结构到一个缓冲区中,del_rootdir 函数则是用来释放这个缓冲区,其代码非常简单,我已经帮你写好了。
获取根目录文件的方法也很容易,根据超级块中的 rfsdir_t 结构中的信息,读取根目录文件的逻辑储存块就行了。而释放根目录文件,就是把根目录文件的储存块回写到储存设备中去,最后释放对应的缓冲区就可以了。
获取文件名
下面我们来实现获取文件名,在我们的印象中,一个完整的文件名应该是这样的“/cosmos/drivers/drvrfs.c”,这样的文件名包含了完整目录路径。
除了第一个“/”是根目录外,其它的“/”只是一个目录路径分隔符。然而,在很多情况下,我们通常需要把目录路径分隔符去除,提取其中的目录名称或者文件名称。为了简化问题,我们对文件系统来点限制,我们的文件名只能是“/xxxx”这种类型的。
下面我们就来实现去除路径分隔符提取文件名称的函数,代码如下所示。
上述代码中,完成获取文件名的是 rfs_ret_fname 函数,这个函数可以把 fpath 指向的路径名中的文件名提取出来,放到 buf 指向的缓冲区中,但在这之前,需要先调用 rfs_chkfilepath 函数检查路径名是不是“/xxxx”的形式,这是这个功能正常实现的必要条件。
判断文件是否存在
获取了文件名称,我们还需要实现这样一个功能:判断一个文件是否存在。因为新建和删除文件,要先判断储存设备里是不是存在着这个文件。具体来说,新建文件时,无法新建相同文件名的文件;删除文件时,不能删除不存在的文件。
我们一起通过后面这个函数还完成这个功能,代码如下所示。
上述代码中,rfs_chkfileisindev 函数逻辑很简单。首先是检查文件名的长度,接着获取了根目录文件,然后遍历根其中的所有 rfsdir_t 结构并比较文件名是否相同,相同就返回 1,不同就返回其它值,最后释放了根目录文件。
因为 get_rootdirfile_blk 函数已经把根目录文件读取到内存里了,所以可以用 dirp 指针和 maxchkp 指针操作其中的数据。
好了,操作根目录文件、获取文件名、判断一个文件是否存在的三大函数就实现了,有了它们,再去实现文件相关的其它操作就方便多了,我们接着探索。
文件相关的操作
直到现在,我们还没对任何文件进行操作,而我们实现文件系统,就是为了应用程序更好地存放自己的“劳动成果”——文件,因此一个文件系统必须要支持一些文件操作。
下面我们将依次实现新建、删除、打开、读写以及关闭文件,这几大文件操作,这也是文件系统需要提供的最基本的功能。
新建文件
在没有文件之前,对任何文件本身的操作都是无效的,所以我们首先就要实现新建文件这个功能。
在写代码之前,我们还是先来看一看如何新建一个文件,一共可以分成后面这 4 步。
1. 从文件路径名中提取出纯文件名,检查储存设备上是否已经存在这个文件。
2. 分配一个空闲的逻辑储存块,并在根目录文件的末尾写入这个新建文件对应的 rfsdir_t 结构。
3. 在一个新的 4KB 大小的缓冲区中,初始化新建文件对应的 fimgrhd_t 结构。
4. 把第 3 步对应的缓冲区里的数据,写入到先前分配的空闲逻辑储存块中。
下面我们先来写好新建文件的接口函数。
我们在新建文件的接口函数中,就实现了前面第一步,完成了提取文件名和检查文件是否在储存设备中存在的工作。接着我们来实现真正新建文件的函数,就是上述代码中 rfs_new_dirfileblk 函数,代码如下所示。
看完上述代码,我想提醒你,在 rfs_new_dirfileblk 函数中有两点很关键。
第一,前面反复提到的目录文件中存放的就是一系列的 rfsdir_t 结构。
第二,fmp 和 ffmp 这两个指针很重要。fmp 指针指向的是根目录文件的 fimgrhd_t 结构,因为要写入一个新的 rfsdir_t 结构,所以要获取并改写根目录文件的 fimgrhd_t 结构中的数据。而 ffmp 指针指向的是新建文件的 fimgrhd_t 结构,并且初始化了其中的一些数据。最后,该函数把这个缓冲区中的数据写入到分配的空闲逻辑储存块中,同时释放了根目录文件和缓冲区。
删除文件
新建文件的操作完成了,下面我们来实现删除文件的操作。
如果只能新建文件而不能删除文件,那么储存设备的空间最终会耗尽,所以文件系统就必须支持删除文件的操作。
同样的,还是先来了解删除文件的方法。删除文件可以通过后面这 4 步来实现。
1. 从文件路径名中提取出纯文件名。
2. 获取根目录文件,从根目录文件中查找待删除文件的 rfsdir_t 结构,然后释放该文件占用的逻辑储存块。
3. 初始化与待删除文件相对应的 rfsdir_t 结构,并设置 rfsdir_t 结构的类型为 RDR_DEL_TYPE。
4. 释放根目录文件。
这次我们用三个函数来实现这些步骤,删除文件的接口函数的代码如下。
删除文件的接口函数非常之简单,就是判断一下标志,接着调用了 rfs_del_dirfileblk 函数,下面我们就来写好这个 rfs_del_dirfileblk 函数。
rfs_del_dirfileblk 函数只是提取了文件名,然后调用了一个删除文件的核心函数,这个核心函数就是 del_dirfileblk_core 函数,它的实现代码如下所示。
上述代码中的 del_dirfileblk_core 函数,它主要是遍历根目录文件中所有的 rfsdir_t 结构,并比较其文件名,看看删除的文件名称是否相同,相同就释放该 rfsdir_t 结构的 rdr_blknr 字段对应的逻辑储存块,清除该 rfsdir_t 结构中的数据,同时设置该 rfsdir_t 结构的类型为删除类型。
你可以这样理解:删除一个文件,就是把这个文件对应的 rfsdir_t 结构中的数据清空,这样就无法查找到这个文件了。同时,也要释放该文件占用的逻辑储存块。因为没有清空文件数据,所以可以通过反删除软件找回文件。
打开文件
接下来,我们就要实现打开文件操作了。一个已经存在的文件,要对它进行读写操作,首先就应该打开这个文件。
Cosmos 内核上层组件调用设备驱动程序时,都需要建立一个相应的 objnode_t 结构,把这个 I/O 包发送给相应的驱动程序,但是 objnode_t 结构不仅仅是用于驱动程序,它还用于表示进程使用了哪些资源,例如打开了哪些设备或者文件,而每打开一个设备或者文件就建立一个 objnode_t 结构,放在特定进程的资源表中。
为了适应文件系统设备驱动程序,在 cosmos/include/krlinc/krlobjnode_t.h 文件中,需要在 objnode_t 结构中增加一些东西,代码如下所示。
上述代码中 objnode_t 结构里增加了两个字段,一个是指向文件路径名的指针,表示打开哪个文件。因为要知道一个文件的所有信息,所以增加了指向对应文件的 fimgrhd_t 结构指针,也就是我们增加的第二个字段。
现在我们来看看打开一个文件的流程。一共也是 4 步。
1. 从 objnode_t 结构的文件路径提取文件名。
2. 获取根目录文件,在该文件中搜索对应的 rfsdir_t 结构,看看文件是否存在。
3. 分配一个 4KB 缓存区,把该文件对应的 rfsdir_t 结构中指向的逻辑储存块读取到缓存区中,然后释放根目录文件。
4. 把缓冲区中的 fimgrhd_t 结构的地址,保存到 objnode_t 结构的 on_finode 域中。
下面来写两个函数实现这些流程,同样我们需要先写好接口函数,代码如下所示。
接口函数 rfs_open_file 中只是对参数进行了检查。然后调用了核心函数,这个函数就是 rfs_openfileblk,它的代码实现如下所示。
结合上面的代码我们能够看到,通过 rfs_openfileblk 函数中的 for 循环,可以遍历要打开的文件在根目录文件中对应的 rfsdir_t 结构,然后把对应文件占用的逻辑储存块读取到缓冲区中,最后返回这个缓冲区的首地址。
因为这个缓冲区开始的空间中,就存放着其文件对应的 fimgrhd_t 结构,所以返回 fimgrhd_t 结构的地址,整个打开文件的流程就结束了。
读写文件
刚才我们已经实现了打开文件, 而打开一个文件,就是为了对这个文件进行读写。
其实对文件的读写包含两个操作,一个是从储存设备中读取文件的数据,另一个是把文件的数据写入到储存设备中。
咱们先来看看如何读取已经打开的文件中的数据,大致的流程如下。
1. 检查 objnode_t 结构中用于存放文件数据的缓冲区及其大小。
2. 检查 imgrhd_t 结构中文件相关的信息。
3. 把文件的数据读取到 objnode_t 结构中指向的缓冲区中。
通过后面的代码,我们把读文件的接口函数跟核心函数一起实现。
上述代码中读取文件数据的函数很简单,关键是要明白前面那个打开文件的函数,因为在那里它已经把文件数据复制到一个缓冲区中了,rfs_readfileblk 函数中的参数 buf、len 都是接口函数 rfs_read_file 从 objnode_t 结构中提取出来的,其它的部分我已经通过注释已经说明了。
好了,我们下面就来实现怎么向文件中写入数据,和读取文件的流程一样,只不过要将要写入的数据复制到打开文件时为其分配的缓冲区中,最后还要把打开文件时为其分配的缓冲区中的数据,写入到相应的逻辑储存块中。
我们还是把写文件的接口函数和核心函数一起实现,代码如下所示。
上述代码中,你要注意的是,rfs_writefileblk 函数永远都是从 fimgrhd_t 结构的 fmd_curfinwbkoff 字段中的偏移量开始写入文件数据的,比如向空文件中写入 2 个字节,那么其 fmd_curfinwbkoff 字段的值就是 2,因为第 0、1 个字节空间已经被占用了,这就是追加写入数据的方式。
rfs_writefileblk 函数最后调用 write_rfsdevblk 函数把文件数据写入到相应的逻辑储存块中,完成数据同步。我们发现只要打开文件了,读写文件还是很简单的,最后还要实现关闭文件的操作。
关闭文件
有打开文件的操作,就需要有关闭文件的操作,因为打开一个文件,会为此分配一个缓冲区,这些都是系统资源,所以需要一个关闭文件的操作来释放这些资源,以防止系统资源泄漏。
关闭文件的流程很简单,首先检查文件是否已经打开。然后把文件写入到对应的逻辑储存块中,完成数据的同步。最后释放文件数据占用的缓冲区。下面我们开始写代码实现,我们依然把接口和核心函数放在一起实现,代码如下所示。
上述代码是非常简单的,但在目前的情况下,rfs_closefileblk 函数中是没有必要调用 write_rfsdevblk 函数的,因为前面在写入文件数据的同时,就已经把文件的数据写入到逻辑储存块中去了。最后释放了先前打开文件时分配的缓冲区,而 objnode_t 结构不应该在此释放,它是由 Cosmos 内核上层组件进行释放的。
串联整合
到目前为止,我们实现了文件相关的操作,并且提供了接口函数,但是我们的文件系统是以设备的形式存在的,所以文件操作的接口,必须要串联整合到文件系统设备驱动程序之中,文件系统才能真正工作。
下面我们就去整合联串文件系统设备驱动程序。首先来串联整合文件系统的打开文件操作和新建文件操作,代码如下所示。
上述代码中 rfs_open 函数对应于设备驱动程序的打开功能派发函数,但没有相应的新建功能派发函数,于是我们就根据 objnode_t 结构中访问标志域设置不同的编码,来进行判断。
接着我们来串联整合关闭文件的操作。这次要简单一些,因为设备驱动程序有对应的关闭功能派发函数,直接调用关闭文件操作的接口函数就可以了,代码如下所示。
然后是文件读写操作的串联整合,设备驱动程序也有对应的读写功能派发函数,同样也是直接调用文件读写操作的接口函数即可,代码如下所示。
最后,来串联整合稍微有点复杂的删除文件操作,这是因为设备驱动程序没有对应的功能派发函数,所以我们需要用到设备驱动程序的控制功能派发函数,代码如下所示。
上述代码中,我们给文件系统设备分配了一个 FSDEV_IOCTRCD_DELFILE(一个整数)控制码,Cosmos 内核上层组件的代码就可以根据需要,设置 objnode_t 结构中的控制码就能达到相应的目的了。
现在,文件相关的操作已经串联整合好了。
测试
前面实现了文件系统的 6 种最常用的文件操作,并且已经整合到文件系统设备驱动程序框架代码中去了,可是这些代码究竟对不对,测试运行了才知道。
下面来写好测试代码。要注意的是,Cosmos 下的任何设备驱动程序都必须要有 objnode_t 结构才能运行。所以,在这里我们需要手动建立一个 objnode_t 结构并设置好其中的字段,模拟一下 Cosmos 上层组件调用设备驱动程序的过程。
这一过程我们可以写个 test_fsys 函数来实现,代码如下所示。
上述代码虽然有点长,因为我们一下子测试了关于文件的 6 大操作。每个文件操作失败后都会死机,不会继续向下运行。
测试逻辑很简单:开始会建立并打开一个文件,接着写入数据,然后读取文件中数据进行比较,看看是不是和之前写入的数据相等,最后删除这个文件并再次打开,看是否会出错。因为文件已经删除了,打开一个已经删除的文件自然要出错,出错就说明测试成功。
现在我们把 test_fsys 函数放在 rfs_entry 函数的最后调用,然后打开终端切换到 cosmos 目录下执行 make vboxtest 命令,最后不出意外的话,你会看到如下图所示的情况。
文件操作测试示意图
从图里我们能看到,文件中的数据和最后重新打开已经删除文件时出现的错误,这说明了我们的代码是正确无误的。
至此 ,测试了文件相关的 6 大操作的代码,代码质量都是相当高的,都达到了我们的预期,一个简单、有诸多限制但却五脏俱全的文件系统就实现了。
重点回顾
这节课告一段落,恭喜你坚持到这里。
文件系统虽然复杂,但我们发现只要做得足够“小”,就能大大降低了实现的难度。虽然降低了实现的难度,但我们的 rfs 文件系统依然包含了一个正常文件系统所具有的功能特性,现在我来为你梳理一下本节课的重点:
1. 首先是文件系统的辅助操作,因为文件系统的复杂性,所以必须要实现一些如获取与释放根目录文件、获取文件名、判断文件是否存在等基础辅助操作函数。
2. 然后实现了文件系统必须要提供的 6 大文件操作:新建文件、删除文件、打开文件、读写文件、关闭文件。
3. 最后把这些文件操作全部串联整合到文件系统设备驱动程序之中,并且进行了测试,确认代码正确无误。
今天这节课,我们又实现了 Cosmos 内核的一个基础组件,即文件系统,不过它是以设备的形式存在的,这样做是为了方便以后的扩展和移植。
现在文件系统是实现了,不过还不够完善。你可能在想,我们文件系统在内存中,一断电数据就全完了。是的,不过你可以尝试写好硬盘驱动,然后把内存中的逻辑储存块写入到硬盘中就行了,期待你的实现。
思考题
请你想一想,我们这个简单的、小的,却五脏俱全的文件系统有哪些限制?
欢迎你在留言区记录你的收获或疑问,也鼓励你边学边练,多多动手实践。同时我推荐你把这节课分享给身边的朋友,跟他一起学习进步。
好,我是 LMOS,我们下节课见。
分享给需要的人,Ta购买本课程,你将得20元
生成海报并分享
赞 5
提建议
© 版权归极客邦科技所有,未经许可不得传播售卖。 页面已增加防盗追踪,如有侵权极客邦将依法追究其法律责任。
上一篇
33 | 仓库划分:文件系统的格式化操作
下一篇
35 | 瞧一瞧Linux:虚拟文件系统如何管理文件?
精选留言(9)
- neohope置顶2021-08-07四、文件系统使用【1、只有一级目录;2、文件管理结构+文件大小<=4K】 1、判断文件是否存在 计算得到文件名 找到根目录文件块 跳过管理结构,遍历全部rfsdir_t目录结构,如果有重名的就返回,没有就失败 2、新建文件 A、确认文件并不存在,存在就报错 B、找到根目录文件块 C、申请一个逻辑块 D、跳过管理结构,新增一个rfsdir_t目录结构,并指向新申请的逻辑块 E、在逻辑块开始建立新的fimgrhd_t文件管理结构 3、删除文件 A、找到根目录文件快 B、跳过管理结构,遍历全部rfsdir_t目录结构,如果没有就失败 C、将rfsdir_t标识为删除 D、回收逻辑块 4、打开文件 A、找到根目录文件快 B、跳过管理结构,遍历全部rfsdir_t目录结构,如果没有就失败 C、读取逻辑块,返回 5、读取文件 A、判断文件已打开 B、根据文件偏移及读取长度,返回数据 6、写入文件 A、判断文件已打开 B、数据追加到缓存中 C、缓存写入到设备中 7、关闭设备 A、判断文件已打开 B、缓存写入到设备中 C、释放缓存展开
作者回复: 总结到位
3 - pedro2021-07-26限制1:不可持久化,不支持crash恢复,应加入磁盘块的写入,内存中有一定文件块的缓存,支持日志,防止系统崩溃,文件数据丢失。 限制2:缺少抽象层,无法支持多种格式的文件。 限制3:小量内存式文件系统,没有使用磁盘,不支持 mount 等骚操作。 等等……
作者回复: 哈哈是的,你有能力可以写个硬盘驱动,在rfs这个驱动中,将IO包继续下发给硬盘驱动,让硬盘驱动写入到硬盘,由此,有层层驱动堆叠的IO栈就形成了
3 - LDxy2021-07-26好像还缺少seek操作
作者回复: 对,没有实现
2 - Kinco.2021-10-201. 不支持多种格式的文件; 2. 不支持多层目录; 3. 不支持seek操作。
作者回复: 是的 是的
1 - 艾恩凝2022-05-12打卡,果然不支持多级目录,更多的是体会一下,os中的文件操作
作者回复: 是的 支持多级 目录 代码会更加复杂
- MONKEYG2022-05-07我想请问下,这个try_entry是谁在调用的😂😂
作者回复: 没有这个函数啊
- ifelse2022-02-22文件查询
作者回复: 嗯嗯
- kocgockohgoh王裒2022-01-06请问删除文件的时候 是不是在根目录文件产生空洞啊 新建文件总是从根目录文件末尾操作
作者回复: 是的
- al_培龙2021-08-03好像不支持多级目录吧
作者回复: 对 对 对
共 2 条评论