【招新】自己写一个文件系统并挂载

招新小任务的“自己写一个文件系统并挂载”,如何实现挂载?

附:招新小任务网页
https://chiichen.github.io/posts/DragonOS/DragonOS招新任务.html

有的同学不知道如何完成这个任务,不知道怎么写自己的文件系统并挂载到DragonOS。

本文旨在以20分钟速通的方式讲解流程(注意,本文只是为了能够让代码跑起来,而不是意味着这样抄就能完成任务)

1. 创建MyRamFS

这一步中,我们需要模仿ramfs,去创建一个MyRamFS。为了省事,我这里就直接复制ramfs的代码,并做小改动(注意,招新任务不允许直接复制代码,要自己写。)

1.1 复制ramfs的代码并重命名为MyRamFS

image

并且在filesystem/mod.rs里面添加:
image

接着把myramfs里面的各种数据结构的名字都改为MyRamFS:

举个例子,这个文件里面其他的地方同理。
image
image

1.2 设置FSMaker

FSMaker是DragonOS内核用于处理文件系统挂载操作的一个对象。我们必须正确设置FSMaker,才能让用户程序通过sys_mount系统调用来挂载这个文件系统。

首先添加MyRamFS的magic number,这个只要不重复就行,所以我这里设置成了114514

image

接着设置myramfs的FSMAKER,第一个参数是文件系统的名字,第二个则是用于创建新的myramfs的初始化函数。

1.3 添加一些日志

为了在后面能够看出是否正确挂载到了MyRamFS,因此在myramfs的inode的各个函数里面都加一些打印日志,比如这样:

image

2. 编写测试程序

测试程序可以基于test-mount那个app来修改。把fstype的字符串改为myramfs即可:

3. 测试!

启动DragonOS之后,使用test-mount命令,就能把myramfs挂载到/mnt/tmp/目录下面了。


上图是能够看到myramfs里面我们添加的日志的。

然后我们cd进去,并且ls或者touch,或者使用held编辑器去写文件,能看到内核输出的日志就代表挂载成功了,接下来就是要去做一些功能的正确性的测试(这就不属于本文的范畴了)。

1 个赞

上述代码我推送到我的仓库的my-ramfs-demo分支了

DragonOS 招新任务:重写 ramfs 并挂载

添加 mod

kernel/src/filesystem/myramfs 下添加 mod.rs 文件

kernel/src/filesystem/mod.rs 中添加 pub mod myramfs;

结构体定义

mod.rs 中定义如下结构体:

pub struct MyRamFs {
    /// MyRamFs的root inode
    root_inode: Arc<LockedMyRamFsInode>,
}

pub struct MyRamFsInode {
    /// 指向父Inode的弱引用
    parent: Weak<LockedMyRamFsInode>,
    /// 指向自身的弱引用
    self_ref: Weak<LockedMyRamFsInode>,
    /// 子Inode的B树
    children: BTreeMap<String, Arc<LockedMyRamFsInode>>,
    /// 当前inode的数据部分
    data: Vec<u8>,
    /// 当前inode的元数据
    metadata: Metadata,
    /// 指向inode所在的文件系统对象的指针
    fs: Weak<MyRamFs>,
    /// 指向特殊节点
    special_node: Option<SpecialNodeData>,
}

/// 加锁的MyRamFsInode
pub struct LockedMyRamFsInode(SpinLock<MyRamFsInode>);

trait定义

myramfs/mod.rs 中实现 Filesystem trait:

impl FileSystem for MyRamFs {
    fn root_inode(&self) -> Arc<dyn IndexNode>;
    fn info(&self) -> FsInfo;
    fn as_any_ref(&self) -> &dyn Any;
}

实现 IndexNode trait:

impl IndexNode for MyRamFsInode {
        fn read_at(
        &self,
        offset: usize,
        len: usize,
        buf: &mut [u8],
        _data: &mut FilePrivateData,
    ) -> Result<usize, SystemError>;
    fn write_at(...);
    fn metadata(...);
    fn set_metadata(...);
    fn link(...);
    fn unlink(...);
    fn find(...);
    fn find_mut(...);
    fn children(...);
    fn as_any_ref(...);
    ...
}

挂载

编写一个 pub fn my_ram_fs_init() 函数,用于挂载 MyRamFs 文件系统:

pub fn my_ram_fs_init() -> Result<(), SystemError> {
    static INIT: Once = Once::new();
    let mut result = None;
    INIT.call_once(|| {
        kinfo!("Initializing MyRamFs...");
        // 创建 myMyRamFs 实例
        let my_ram_fs: Arc<MyRamFs> = MyRamFs::new();

        // myMyRamFs 挂载
        let _t = ROOT_INODE()
            .find("myramfs")
            .expect("Cannot find /myramfs")
            .mount(my_ram_fs)
            .expect("Failed to mount myramfs");
        kinfo!("MyRamFs mounted.");
        result = Some(Ok(()));
    });

    return result.unwrap();
}

在加载文件系统时调用 my_ram_fs_init() 函数。

#[inline(never)]
pub fn vfs_init() -> Result<(), SystemError> {
    // 使用Ramfs作为默认的根文件系统
    let ramfs = RamFS::new();
    let mount_fs = MountFS::new(ramfs, None);
    let root_inode = mount_fs.root_inode();

    // 创建文件夹
    ...
    // 其他文件系统初始化
    ...
    // 加入初始化MyRamFs的代码
    my_ram_fs_init().expect("Failed to initialize myramfs");
    ...
    return Ok(());
}

vfs_init() 函数中,我们创建了一个 MountFS 实例,并将 RamFS 挂载到根目录下。然后创建了 /proc/dev/sys/myramfs 四个文件夹。最后调用 my_ram_fs_init() 函数挂载 MyRamFs 文件系统。
MountFS 是一个虚拟文件系统,用于挂载其他文件系统。RamFS 是一个简单的内存文件系统,用于挂载到根目录下。

在最新版本的DragonOS,有些代码有所变动,这个方法可能不适用。

调试

在最初编写完成时,发现写入文件时追加部分数据会出现错误,经过调试发现是由于 write_at 函数中未更新文件大小导致的。在 write_atread_at 函数中添加如下代码:

fn write_at(
    &self,
    offset: usize,
    len: usize,
    buf: &[u8],
    _data: &mut FilePrivateData,
) -> Result<usize, SystemError> {
    ...
    kdebug!("write_at: offset: {}, len: {}, buf.len: {}, inode.data.len(): {}", offset, len, buf.len(), inode.data.len());

    // 如果offset + len > inode.data.len(),则需要扩容
    if offset + len > inode.data.len() {
        inode.data.resize(offset + len, 0);
        kdebug!("write_at(): resize to: {}", offset + len);
        inode.metadata.size = (offset + len) as i64; // 更新文件大小,影响到下一次调用的offset
        kdebug!("write_at(): inode.metadata.size: {}", inode.metadata.size);
    }

    inode.data[offset..offset + len].copy_from_slice(&buf[..len]);
    return Ok(len);
}

fn read_at(
    &self,
    offset: usize,
    len: usize,
    buf: &mut [u8],
    _data: &mut FilePrivateData,
) -> Result<usize, SystemError> {
    ...
    kdebug!("read_at: offset: {}, len: {}, buf.len: {}, inode.data.len(): {}", offset, len, buf.len(), inode.data.len());
    let start = offset.min(inode.data.len());
    let end = (offset + len).min(inode.data.len());
    let read_len = end - start;
    buf[..read_len].copy_from_slice(&inode.data[start..end]);
    Ok(read_len)
}

虽然 kdebug! 宏在最新版本的DragonOS中已经被移除,但是通过打印调试信息,我们可以更好地理解代码的执行流程,这是一种重要的思想。

总结

本次文件系统实验难度适中。关键在于理解已有的文件系统框架,然后根据需求实现相应的trait。在实现过程中,我们需要注意文件系统的数据结构设计,以及文件系统的挂载与初始化。在调试过程中,我们可以通过打印调试信息来帮助我们理解代码的执行流程。最后,我们需要注意代码的风格与规范,以便于他人阅读与维护。

本来在学期第13周已经完成了这次实验,但是拖到放假才写了这个帖子,实在不好意思。今后会更加努力学习进一步的知识,比如从用户态到内核态的过程和挂载机制的实现。