课后练习

编程题

    • 扩展 easy-fs 文件系统功能,扩大单个文件的大小, 支持三重间接 inode。
    • 扩展内核功能,支持 stat 系统调用,能显示文件的 inode 元数据信息。
  1. ** 扩展内核功能,支持 mmap 系统调用,支持对文件的映射,实现基于内存读写方式的文件读写功能。

  2. ** 扩展 easy-fs 文件系统功能,支持二级目录结构。可扩展:支持 N 级目录结构。

  3. *** 扩展 easy-fs 文件系统功能,通过日志机制支持 crash 一致性。

问答题

    • 文件系统的功能是什么?

将数据以文件的形式持久化保存在存储设备上。

  1. ** 目前的文件系统只有单级目录,假设想要支持多级文件目录,请描述你设想的实现方式,描述合理即可。

允许在目录项中存在目录(原本只能存在普通文件)即可。

  1. ** 软链接和硬链接是干什么的?有什么区别?当删除一个软链接或硬链接时分别会发生什么?

软硬链接的作用都是给一个文件以”别名”,使得不同的多个路径可以指向同一个文件。当删除软链接时候,对文件没有任何影响,当删除硬链接时,文件的引用计数会被减一,若引用计数为0,则该文件所占据的磁盘空间将会被回收。

文件目录中出现环路

  1. *** 在有了多级目录之后,我们就也可以为一个目录增加硬链接了。在这种情况下,文件树中是否可能出现环路 (软硬链接都可以,鼓励多尝试)?你认为应该如何解决?请在你喜欢的系统上实现一个环路,描述你的实现方式以及系统提示、实际测试结果。

可能出现环路。一种可能的解决方式是在访问文件的时候检查自己遍历的路径中是否有重复的 inode,并在发现环路时返回错误。

    • 目录是一类特殊的文件,存放的是什么内容?用户可以自己修改目录内容吗?

存放的是目录中的文件列表以及他们对应的 inode,通常而言用户不能自己修改目录的内容,但是可以通过操作目录(如 mv 里面的文件)中文件的方式间接修改。

  1. ** 在实际操作系统中,如 Linux,为什么会存在大量的文件系统类型?

因为不同的文件系统有着不同的特性,比如对于特定种类的存储设备的优化,或是快照和多设备管理等高级特性,适用于不同的使用场景。

文件控制块放到目录的影响

  1. ** 可以把文件控制块放到目录项中吗?这样做有什么优缺点?

可以,是对于小目录可以减少一次磁盘访问,提升性能,但是对大目录而言会使得在目录中查找文件的性能降低。

为什么要同时维护进程的打开文件表和操作系统的打开文件表

  1. ** 为什么要同时维护进程的打开文件表和操作系统的打开文件表?这两个打开文件表有什么区别和联系?

多个进程可能会同时打开同一个文件,操作系统级的打开文件表可以加快后续的打开操作,但同时由于每个进程打开文件时使用的访问模式或是偏移量不同,所以还需要进程的打开文件表另外记录。

文件分配的三种方式

  1. ** 文件分配的三种方式是如何组织文件数据块的?各有什么特征(存储、文件读写、可靠性)?

连续分配:实现简单、存取速度快,但是难以动态增加文件大小,长期使用后会产生大量无法使用(过小而无法放入大文件)碎片空间。

链接分配:可以处理文件大小的动态增长,也不会出现碎片,但是只能按顺序访问文件中的块,同时一旦有一个块损坏,后面的其他块也无法读取,可靠性差。

索引分配:可以随机访问文件中的偏移量,但是对于大文件需要实现多级索引,实现较为复杂。

没有及时关闭文件描述符的后果

  1. ** 如果一个程序打开了一个文件,写入了一些数据,但是没有及时关闭,可能会有什么后果?如果打开文件后,又进一步发出了读文件的系统调用,操作系统中各个组件是如何相互协作完成整个读文件的系统调用的?

(若也没有 flush 的话)假如此时操作系统崩溃,尚处于内存缓冲区中未写入磁盘的数据将会丢失,同时也会占用文件描述符,造成资源的浪费

首先是系统调用处理的部分,将这一请求转发给文件系统子系统,文件系统子系统再将其转发给块设备子系统,最后再由块设备子系统转发给实际的磁盘驱动程序读取数据,最终返回给程序。

实现用户态文件系统的思路

  1. *** 文件系统是一个操作系统必要的组件吗?是否可以将文件系统放到用户态?这样做有什么好处?操作系统需要提供哪些基本支持?
  • 不是,如在本章之前的 rCore 就没有文件系统。
  • 可以,如在 Linux 下就有 FUSE 这样的框架可以实现这一点。这样可以使得文件系统的实现更为灵活,开发与调试更为简便。
  • 操作系统需要提供一个注册用户态文件系统实现的机制,以及将收到的文件系统相关系统调用转发给注册的用户态进程的支持。

实验练习

实验练习包括实践作业和问答作业两部分。

理解文件系统比较费事,编程难度适中

实践作业

硬链接

硬链接要求两个不同的目录项指向同一个文件,在我们的文件系统中也就是两个不同名称目录项指向同一个磁盘块。

本节要求实现三个系统调用 sys_linkat、sys_unlinkat、sys_stat

linkat

  • syscall ID: 37

  • 功能:创建一个文件的一个硬链接, linkat 标准接口

  • C接口: int linkat(int olddirfd, char* oldpath, int newdirfd, char* newpath, unsigned int flags)

  • Rust 接口: fn linkat(olddirfd: i32, oldpath: *const u8, newdirfd: i32, newpath: *const u8, flags: u32) -> i32

  • 参数:

    • olddirfd,newdirfd: 仅为了兼容性考虑,本次实验中始终为 AT_FDCWD (-100),可以忽略。

    • flags: 仅为了兼容性考虑,本次实验中始终为 0,可以忽略。

    • oldpath:原有文件路径

    • newpath: 新的链接文件路径。

  • 说明:

    • 为了方便,不考虑新文件路径已经存在的情况(属于未定义行为),除非链接同名文件。

    • 返回值:如果出现了错误则返回 -1,否则返回 0。

  • 可能的错误

    • 链接同名文件。

unlinkat:

  • syscall ID: 35

  • 功能:取消一个文件路径到文件的链接, unlinkat 标准接口

  • C接口: int unlinkat(int dirfd, char* path, unsigned int flags)

  • Rust 接口: fn unlinkat(dirfd: i32, path: *const u8, flags: u32) -> i32

  • 参数:

    • dirfd: 仅为了兼容性考虑,本次实验中始终为 AT_FDCWD (-100),可以忽略。

    • flags: 仅为了兼容性考虑,本次实验中始终为 0,可以忽略。

    • path:文件路径。

  • 说明:

    • 为了方便,不考虑使用 unlink 彻底删除文件的情况。

  • 返回值:如果出现了错误则返回 -1,否则返回 0。

  • 可能的错误

    • 文件不存在。

fstat:

  • syscall ID: 80

  • 功能:获取文件状态。

  • C接口: int fstat(int fd, struct Stat* st)

  • Rust 接口: fn fstat(fd: i32, st: *mut Stat) -> i32

  • 参数:

    • fd: 文件描述符

    • st: 文件状态结构体

    #[repr (C)]
    #[derive (Debug)]
    pub struct Stat {
        /// 文件所在磁盘驱动器号,该实验中写死为 0 即可
        pub dev: u64,
        /// inode 文件所在 inode 编号
        pub ino: u64,
        /// 文件类型
        pub mode: StatMode,
        /// 硬链接数量,初始为 1
        pub nlink: u32,
        /// 无需考虑,为了兼容性设计
        pad: [u64; 7],
    }
    
    /// StatMode 定义:
    bitflags! {
        pub struct StatMode: u32 {
            const NULL  = 0;
            /// directory
            const DIR   = 0o040000;
            /// ordinary regular file
            const FILE  = 0o100000;
        }
    }
    

实验要求

  • 实现分支:ch7-lab

  • 实验目录要求不变

  • 通过所有测例

    在 os 目录下 make run TEST=1 加载所有测例, test_usertest 打包了所有你需要通过的测例,你也可以通过修改这个文件调整本地测试的内容。

    你的内核必须前向兼容,能通过前一章的所有测例。

[! note] 如何调试 easy-fs 如果你在第一章练习题中已经借助 log crate 实现了日志功能,那么你可以直接在 easy-fs 中引入 log crate,通过 log::info!/debug! 等宏即可进行调试并在内核中看到日志输出。具体来说,在 easy-fs 中的修改是:在 easy-fs/Cargo.toml 的依赖中加入一行 log = "0.4.0",然后在 easy-fs/src/lib.rs 中加入一行 extern crate log

你也可以完全在用户态进行调试。仿照 easy-fs-fuse 建立一个在当前操作系统中运行的应用程序,将测试逻辑写在 main 函数中。这个时候就可以将它引用的 easy-fsno_std 去掉并使用 println! 进行调试。

问答作业

实验练习的提交报告要求

  • 简单总结本次实验与上个实验相比你增加的东西。(控制在 5 行以内,不要贴代码)

  • 完成问答问题

  • (optional) 你对本次实验设计及难度的看法。