提交 a6817c0f 编写于 作者: chyyuu1972's avatar chyyuu1972

add chy/sec0 本章代码导读

上级 e2263635
......@@ -235,6 +235,34 @@ easyfs文件系统的设计实现有五层。它的最底层就是对块设备
而具体使用这两个函数的是自底向上的第二层 -- 块缓存。块缓存是把应用要访问的数据放到一块内存区域中,减少磁盘读写的次数,提高系统性能。块缓存通过 ``read_block`` 和 ``write_block`` 函数接口来读写磁盘数据。这些磁盘数据会缓存在内存中。表示块缓存的数据结构是 ``BlockCache`` 。当我们创建一个 ``BlockCache`` 的时候,将触发一次 ``read_block`` 函数调用,将一个块上的数据从磁盘读到块缓冲区中。由于缓存磁盘块的内存有限,我们需要实现缓存的替换,这就需要实现类似与页替换算法的缓存替换算法。为了简单,我们实现的是FIFO缓存替换算法。具体替换过程是块缓存全局管理器 ``BlockCacheManager`` 中的成员函数 ``get_block_cache`` 来完成的。
有了块缓存,我们就可以在内存中方便地处理easyfs文件系统在磁盘上的各种数据了。
有了块缓存,我们就可以在内存中方便地处理easyfs文件系统在磁盘上的各种数据了,这就是第三层文件系统的磁盘数据结构。easyfs文件系统中的所有需要持久保存的数据都会放到磁盘上,这包括了管理这个文件系统的 **超级块 (Super Block)**,管理空闲磁盘块的 **索引节点位图区** 和 **数据块位图区** ,以及管理文件的 **索引节点区** 和 放置文件数据的 **数据块区** 组成
<TO-BE-CONTINUE>
\ No newline at end of file
easyfs文件系统中管理这些磁盘数据的控制逻辑主要集中在 **磁盘块管理器** 中,这是文件系统的第四层。对于文件系统管理而言,其核心是 ``EasyFileSystem`` 数据结构及其关键成员函数:
- EasyFileSystem.create:创建文件系统
- EasyFileSystem.open:打开文件系统
- EasyFileSystem.alloc_inode:分配inode (dealloc_inode未实现,所以还不能删除文件)
- EasyFileSystem.alloc_data:分配数据块
- EasyFileSystem.dealloc_data:回收数据块
对于单个文件的管理和读写的控制逻辑主要是 **索引节点** 来完成,这是文件系统的第五层,其核心是 ``Inode`` 数据结构及其关键成员函数:
- Inode.new:在磁盘上的文件系统中创建一个inode
- Inode.find:根据文件名查找对应的磁盘上的inode
- Inode.create:在根目录下创建一个文件
- Inode.read_at:根据inode找到文件数据所在的磁盘数据块,并读到内存中
- Inode.write_at:根据inode找到文件数据所在的磁盘数据块,把内存中数据写入到磁盘数据块中
上述五层就构成了easyfs文件系统的整个内容。我们可以把easyfs文件系统看成是一个库,被应用程序调用。而 ``easy-fs-fuse`` 这个应用就通过调用easyfs文件系统库中各种函数,并用Linux上的文件模拟了一个块设备,就可以在这个模拟的块设备上创建了一个easyfs文件系统。
第三步,我们需要把easfs文件系统加入到我们的操作系统内核中。这还需要做两件事情,第一件是在Qemu模拟的 ``virtio`` 块设备上实现块设备驱动程序 ``os/src/drivers/block/virtio_blk.rs`` 。由于我们可以直接使用 ``virtio-drivers`` crate中的块设备驱动,所以只要提供这个块设备驱动所需要的内存申请与释放以及虚实地址转换的4个函数就可以了。而我们之前操作系统中的虚存管理实现中,以及有这些函数,导致块设备驱动程序很简单,具体实现细节都被 ``virtio-drivers`` crate封装好了。
第二件事情是把文件访问相关的系统调用与easyfs文件系统连接起来。在easfs文件系统中是没有进程的概念的。而进程是程序运行过程中访问资源的管理实体,这就要对 ``easy-fs`` crate 提供的 ``Inode`` 结构进一步封装,形成 ``OSInode`` 结构,以表示进程中一个打开的常规文件。对于应用程序而言,它理解的磁盘数据是常规的文件和目录,不是 ``OSInode`` 这样相对复杂的结构。其实常规文件对应的 OSInode 是文件在操作系统内核中的内部表示,因此需要为它实现 File Trait 从而能够可以将它放入到进程文件描述符表中,并通过 sys_read/write 系统调用进行读写。这样就建立了文件与 ``OSInode`` 的对应关系,并通过上面描述的三个步骤完成了包含文件系统的操作系统内核,并能给应用提供基于文件的系统调用服务。
完成包含文件系统的操作系统内核后,我们在shell程序和内核中支持命令行参数的解析和传递,这样可以让应用根据灵活地通过命令行参数来动态地表示要操作的文件。这需要扩展对应的系统调用 ``sys_exec`` ,主要的改动就是在创建新进程时,把命令行参数压入用户栈中,这样应用程序在执行时就可以从用户栈中获取到命令行的参数值了。
在上一章,我们提到了把标准输出设备在文件描述符表中的文件描述符的值规定为 1 ,用 Stdin 表示;把标准输入设备在文件描述符表中的文件描述符的值规定为 0,用 stdout 表示 。另外,还有一条文件描述符相关的重要规则:即进程打开一个文件的时候,内核总是会将文件分配到该进程文件描述符表中编号 最小的 空闲位置。利用这些约定,只实现新的系统调用 ``sys_dup`` 完成对文件描述符的复制,就可以巧妙地实现标准 I/O 重定向功能了。
具体思路是,在某应用进程执行之前,父进程(比如 user_shell进程)要对子应用进程的文件描述符表进行某种替换。以输出为例,父进程在创建子进程前,提前打开一个常规文件 A,然后 ``fork`` 子进程,在子进程的最初执行中,通过 ``sys_close`` 关闭 Stdin 文件描述符,用 ``sys_dup`` 复制常规文件 A的文件描述符,这样 Stdin 文件描述符实际上指向的就是常规文件A了,这这时再通过 ``sys_close`` 关闭常规文件A的文件描述符。至此,常规文件A替换掉了应用文件描述符表位置 1 处的标准输出文件,这就完成了所谓的 **重定向** ,即完成了执行新应用前的准备工作。
接下来是子进程调用 ``sys_exec`` 系统调用,创建并开始执行新子应用进程。在重定向之后,新的子应用进程认为自己输出到 fd=1 的标准输出文件,但实际上是输出到父进程(比如 user_shell进程)指定的文件A中。文件这一抽象概念透明化了文件、I/O设备之间的差异,因为在进程看来无论是标准输出还是常规文件都是一种文件,可以通过同样的接口来读写。这就是文件的强大之处。
\ No newline at end of file
Markdown is supported
0% .
You are about to add 0 people to the discussion. Proceed with caution.
先完成此消息的编辑!
想要评论请 注册