跳转至

ArceOS 文件系统接口

本文介绍了 ArceOS 的文件系统接口设计,主要面向使用 axfs 模块构建操作系统内核的开发者,涉及的内容包括:

  1. 关键结构体与关联函数
  2. std-like 风格的 API
  3. axfs 模块中 Features 配置策略

关键结构体与关联函数

axfs 模块中,主要有以下几个关键结构体:

  • File:具体的文件对象,提供了对文件的打开和读写等操作。
  • Directory:具体的目录对象,提供了对目录的增删改查等操作。
  • DirEntry:目录项,用于表示目录中的文件名和类型等信息。
  • FileAttr:文件属性,包括文件类型、权限、大小等信息。

FILE 文件操作

File 结构体表示打开的文件对象,提供了对文件的读写等操作:

  • node:对应的文件节点的引用,是一个 WithCap 包装的 VfsNodeRef
  • is_append:表示文件是否以追加模式打开。例如 open("/tmp/xxx", "a") 打开文件时,is_appendtrue,并且 offset 指向文件末尾。
  • offset:文件指针,表示文件的读写位置。
1
2
3
4
5
6
/// An opened file object, with open permissions and a cursor.
pub struct File {
    node: WithCap<VfsNodeRef>,
    is_append: bool,
    offset: u64,
}

权限控制 WithCap

WithCap 增加了对文件的访问权限控制的功能,包含可读、可写、可执行(rwx)。具体参考cap_access

File 实现了如下的函数:

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
// modules/axfs/src/fops.rs

/// Opens a file at the path relative to the current directory. 
///
/// Returns a [`File`] object.
pub fn open(path: &str, opts: &OpenOptions) -> AxResult<Self>;

/// Truncates the file to the specified size.
pub fn truncate(&self, size: u64) -> AxResult;

/// Reads the file at the current position. Returns the number of bytes read.
///
/// After the read, the cursor will be advanced by the number of bytes read.
pub fn read(&mut self, buf: &mut [u8]) -> AxResult<usize>;

/// Reads the file at the given position. Returns the number of bytes read.
/// 
/// It does not update the file cursor.
pub fn read_at(&self, offset: u64, buf: &mut [u8]) -> AxResult<usize>;

/// Writes the file at the current position. Returns the number of bytes written.
/// 
/// After the write, the cursor will be advanced by the number of bytes written.
pub fn write(&mut self, buf: &[u8]) -> AxResult<usize>;

/// Writes the file at the given position. Returns the number of bytes written.
/// 
/// It does not update the file cursor.
pub fn write_at(&self, offset: u64, buf: &[u8]) -> AxResult<usize>;

/// Flushes the file, writes all buffered data to the underlying device.
pub fn flush(&self) -> AxResult;

/// Sets the cursor of the file to the specified offset. Returns the new
/// position after the seek.
pub fn seek(&mut self, pos: SeekFrom) -> AxResult<u64>;

/// Gets the file attributes.
pub fn get_attr(&self) -> AxResult<FileAttr>;
函数名 功能
open 相对于当前目录打开文件,返回 File 对象
truncate 将文件截断或扩展到指定大小,若文件小于指定大小,则在文件末尾填充 \0
read 从当前位置读取文件内容到缓冲区,返回读取的字节数,更新文件指针
read_at 从指定位置读取文件内容到缓冲区,返回读取的字节数,不更新文件指针
write 从当前位置写入缓冲区内容到文件,返回写入的字节数,更新文件指针
write_at 从指定位置写入缓冲区内容到文件,返回写入的字节数,不更新文件指针
flush 将文件缓冲区数据刷新到底层设备
seek 设置文件指针到指定位置,返回新的位置
get_attr 获取文件的属性,返回 FileAttr 结构体,包含文件的权限、类型、大小等信息

在这里,我们需要注意的是,由于没有进行更多的封装,axfs 不像标准库一样地智能,所以在使用 write 过后需要调用 flush 函数来确保数据持久化到设备中。

除此之外,只有 Filedrop 触发析构函数,强制刷新文件的缓冲区数据才能够进行同步。

下面是一个简单的使用示例:

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
/// 创建一个 OpenOptions 对象
fn options(opt: &str) -> fops::OpenOptions {
    let mut opts = fops::OpenOptions::new();
    opt.find("r").map(|_| opts.read(true));
    opt.find("w").map(|_| opts.write(true));
    opt.find("x").map(|_| opts.execute(true));
    opt.find("a").map(|_| opts.append(true));
    opt.find("t").map(|_| opts.truncate(true));
    opt.find("c").map(|_| opts.create(true));
    opt.find("n").map(|_| opts.create_new(true));
    opt.find("d").map(|_| opts.directory(true));
    opts
}

fn file() -> AxResult {
    let s = String::from("hello world");
    let s_append = String::from(" arceOS!");
    let file_path = "/test.txt";

    // write: 写入文件
    let mut file = fops::File::open(file_path, &options("rwc"))?;
    file.write(s.as_bytes())?;
    drop(file); // 触发析构函数,刷新文件缓冲区数据
    // file.flush()?;

    // read: 读取文件
    let mut file = fops::File::open(file_path, &options("r"))?;
    let mut buf = [0u8; 64];
    let len = file.read(&mut buf)?;
    assert_eq!(&buf[0..len], s.as_bytes());

    // write_append: 追加文件
    let mut file = fops::File::open(file_path, &options("rwa"))?;
    file.write(s_append.as_bytes())?;
    drop(file); // 触发析构函数,刷新文件缓冲区数据
    // file.flush()?;

    // seek: 定位文件指针
    let mut file = fops::File::open(file_path, &options("r"))?;
    file.seek(SeekFrom::Start(6))?;
    let mut buf = [0u8; 64];
    let len = file.read(&mut buf)?;
    assert_eq!(buf[0..len], format!("{}{}", s, s_append).as_bytes()[6..]);

    // get_attr: 获取文件的元数据
    let metadata = file.get_attr()?;
    assert_eq!(metadata.file_type(), fops::FileType::File);
    assert_eq!(metadata.size(), 19);

    // 删除文件
    let dir = fops::Directory::open_dir("/", &options("r"))?;   
    dir.remove_file(file_path)?;

    Ok(())
}

Directory 目录操作

Directory 结构体表示打开的目录对象,提供了对目录的增删改查等操作:

  • node:对应的目录节点的引用,是一个 WithCap 包装的 VfsNodeRef
  • entry_idx:表示当前目录项的索引位置。
1
2
3
4
5
6
/// An opened directory object, with open permissions and a cursor for
/// [`read_dir`](Directory::read_dir).
pub struct Directory {
    node: WithCap<VfsNodeRef>,
    entry_idx: usize,
}

Directory 实现了如下的函数:

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
// modules/axfs/src/fops.rs

/// Opens a directory at the path relative to the current directory.
/// Returns a [`Directory`] object.
pub fn open_dir(path: &str, opts: &OpenOptions) -> AxResult<Self>;

/// Opens a directory at the path relative to this directory. 
/// Returns a [`Directory`] object.
pub fn open_dir_at(&self, path: &str, opts: &OpenOptions) -> AxResult<Self>;

/// Opens a file at the path relative to this directory. 
/// Returns a [`File`] object.
pub fn open_file_at(&self, path: &str, opts: &OpenOptions) -> AxResult<File>;

/// Creates an empty file at the path relative to this directory.
pub fn create_file(&self, path: &str) -> AxResult<VfsNodeRef>;

/// Creates an empty directory at the path relative to this directory.
pub fn create_dir(&self, path: &str) -> AxResult;

/// Removes a file at the path relative to this directory.
pub fn remove_file(&self, path: &str) -> AxResult;

/// Removes a directory at the path relative to this directory.
pub fn remove_dir(&self, path: &str) -> AxResult;

/// Reads directory entries starts from the current position into the
/// given buffer. Returns the number of entries read.
///
/// After the read, the cursor will be advanced by the number of entries
/// read.
pub fn read_dir(&mut self, dirents: &mut [DirEntry]) -> AxResult<usize>;

/// Rename a file or directory to a new name.
/// Delete the original file if `old` already exists.
///
/// This only works then the new path is in the same mounted fs.
pub fn rename(&self, old: &str, new: &str) -> AxResult;
函数名 功能
open_dir 相对于当前目录打开目录,返回 Directory 对象
open_dir_at 相对于此目录打开目录,返回 Directory 对象
open_file_at 相对于此目录打开文件,返回 File 对象
create_file 在此目录下创建空文件
create_dir 在此目录下创建空目录
remove_file 移除在此目录下的文件
remove_dir 移除在此目录下的指定目录
read_dir 从当前位置开始读取目录条目到缓冲区,返回读取的条目数,更新目录指针
rename 重命名文件或目录,若新路径已存在则删除原文件,要求新旧路径必须在同一文件系统中

在这里,我们需要注意的是,rename 函数不是基于 self 的路径,而是基于当前目录的路径进行重命名操作,当前路径可以调用 api::current_dir() 获取。

下面是一个简单的使用示例:

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
/// 检测文件/目录是否存在
fn check_exsist(path: &str, assertion: bool) {
    debug!("{} exsist: {}", path, assertion);
    assert_eq!(api::absolute_path_exists(path), assertion);
}

/// 测试目录 API
fn direction() -> AxResult {
    let root_dir = fops::Directory::open_dir("/", &options("r"))?;

    // create_dir: 创建文件夹 /test
    root_dir.create_dir("test")?; check_exsist("/test", true);

    let dir = fops::Directory::open_dir("/test", &options("r"))?;

    // create_file: 创建文件 /test/test_a.txt /test/test_b.txt
    dir.create_file("test_a.txt")?; check_exsist("/test/test_a.txt", true);
    dir.create_file("test_b.txt")?; check_exsist("/test/test_b.txt", true);

    // rename: 修改文件名 /test/test_a.txt -> /test/test_c.txt
    api::set_current_dir("/test").expect("set current dir failed");
    dir.rename("test_a.txt", "test_c.txt")?;
    check_exsist("test_a.txt", false);
    check_exsist("test_c.txt", true);
    api::set_current_dir("/").expect("set current dir failed");

    // remove_file: 删除文件 /test/test_b.txt /test/test_c.txt 
    dir.remove_file("test_b.txt")?;
    check_exsist("/test/test_b.txt", false);
    dir.remove_file("test_c.txt")?;
    check_exsist("/test/test_c.txt", false);

    // remove_dir 删除文件夹 /test
    root_dir.remove_dir("test")?;
    check_exsist("/test", false);

    Ok(())
}

DirEntry 目录项

DirEntry 结构体表示目录项,其中包含了文件名和文件属性等信息。它是 axfs_vfs::VfsDirEntry 的别名。

目前内部记录两个字段:

  • d_type:表示文件类型,为 VfsNodeType 枚举类型。
  • d_name:表示文件名,使用一个长度为 63 的字节数组存储文件名。
1
2
3
4
5
6
7
8
/// Alias of [`axfs_vfs::VfsDirEntry`].
pub type DirEntry = axfs_vfs::VfsDirEntry;

/// Directory entry.
pub struct VfsDirEntry {
    d_type: VfsNodeType,
    d_name: [u8; 63],
}

文件名长度限制

目前 Arceos 中的文件名长度限制为 63 字节,实际使用中可能会遇到一些问题,例如 lwext4 文件系统中,文件名长度限制为 255 字节,因此在使用 lwext4 文件系统时,可能会遇到文件名过长的问题。

OpenOptions 打开选项

OpenOptions 结构体用于指定打开文件或目录时的选项。类似于 Rust 标准库中的 std::fs::OpenOptions。不过,考虑到 ArceOS 的特殊性,OpenOptions 结构体提供了更多的选项来配置文件的打开方式。

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
/// Options and flags which can be used to configure how a file is opened.
#[derive(Default, Clone)]
pub struct OpenOptions {
    // generic
    read    : bool,         
    write   : bool,        
    execute : bool,      
    append  : bool,       
    truncate: bool,     
    create  : bool,       
    create_new: bool,   
    directory: bool,    
    // system-specific
    _custom_flags: i32,
    _mode: u32,
}

OpenOptions 结构体与 Rust 标准库中的 std::fs::OpenOptions 类似,但为了满足 axfs 的需要,内部提供了更多的选项来配置文件的打开方式。它包含以下字段:

  • read:是否可读
  • write:是否可写
  • execute:是否可执行
  • append:是否以追加模式打开,若为 true,写入操作将追加到文件末尾而非覆盖原有内容。
  • truncate:是否截断文件,若为 true,打开文件时将清空其内容(文件大小置为 0)。
  • create:是否创建文件,即使文件已存在也不会返回错误
  • create_new:是否创建新文件,若文件已存在则返回错误
  • directory:是否打开目录,若为 true,尝试将路径作为目录打开(而非文件)
  • _custom_flags:系统特定的自定义标志,暂未使用
  • _mode:文件权限模式,暂未使用

元数据

axfs 模块中,文件和目录的元数据的接口是通过 FileAttr 结构体来实现的。内部包含了文件的权限、类型、大小和分配的块数等信息。它是 axfs_vfs::VfsNodeAttr 的别名。内部维护了以下字段:

  1. mode:文件权限模式,使用 FilePerm 类型表示。描述了文件的访问权限,包括所有者、组和其他用户的读、写和执行权限。
  2. ty:文件类型,使用 FileType 类型表示。
  3. size:文件的总大小,以字节为单位。
  4. blocks:分配的块数,以 512 字节为单位。
 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
/// Alias of [`axfs_vfs::VfsNodeAttr`].
pub type FileAttr = axfs_vfs::VfsNodeAttr;
/// Alias of [`axfs_vfs::VfsNodePerm`].
pub type FilePerm = axfs_vfs::VfsNodePerm;
/// Alias of [`axfs_vfs::VfsNodeType`].
pub type FileType = axfs_vfs::VfsNodeType;

/// Node (file/directory) attributes.
pub struct VfsNodeAttr {
    /// File permission mode.
    mode: VfsNodePerm,
    /// File type.
    ty: VfsNodeType,
    /// Total size, in bytes.
    size: u64,
    /// Number of 512B blocks allocated.
    blocks: u64,
}

pub enum VfsNodeType {
    /// FIFO (named pipe)
    Fifo = 0o1,
    /// Character device
    CharDevice = 0o2,
    /// Directory
    Dir = 0o4,
    /// Block device
    BlockDevice = 0o6,
    /// Regular file
    File = 0o10,
    /// Symbolic link
    SymLink = 0o12,
    /// Socket
    Socket = 0o14,
}

bitflags::bitflags! {
    /// Node (file/directory) permission mode.
    #[derive(Debug, Clone, Copy)]
    pub struct VfsNodePerm: u16 {
        /// Owner has read permission.
        const OWNER_READ = 0o400;
        /// Owner has write permission.
        const OWNER_WRITE = 0o200;
        /// Owner has execute permission.
        const OWNER_EXEC = 0o100;

        /// Group has read permission.
        const GROUP_READ = 0o40;
        /// Group has write permission.
        const GROUP_WRITE = 0o20;
        /// Group has execute permission.
        const GROUP_EXEC = 0o10;

        /// Others have read permission.
        const OTHER_READ = 0o4;
        /// Others have write permission.
        const OTHER_WRITE = 0o2;
        /// Others have execute permission.
        const OTHER_EXEC = 0o1;
    }
}

axfs 模块提供了对文件、目录进行直接操作的接口,现在只需要将根目录 ROOT 暴露给开发者就可以得到一个完整的文件系统 API 了。

std-like API

除了上一节中操作文件/目录 API 外,ArceOS 还提供了一套类似于 Rust 标准库的文件系统 API,这样也简化了开发者在 ArceOS 基础上实现 rust 标准库的工作。 参考 rust 官方 std::fs

本质上,std-like API 是对下层模块的进一步封装。

文件系统 API

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
// modules/axfs/src/api/mod.rs

/// Returns an iterator over the entries within a directory.
pub fn read_dir(path: &str) -> io::Result<ReadDir>;

/// Returns the canonical, absolute form of a path with all intermediate
/// components normalized.
pub fn canonicalize(path: &str) -> io::Result<String>;

/// Returns the current working directory as a [`String`].
pub fn current_dir() -> io::Result<String>;

/// Changes the current working directory to the specified path.
pub fn set_current_dir(path: &str) -> io::Result<()>;

/// Read the entire contents of a file into a bytes vector.
pub fn read(path: &str) -> io::Result<Vec<u8>>;

/// Read the entire contents of a file into a string.
pub fn read_to_string(path: &str) -> io::Result<String>;

/// Write a slice as the entire contents of a file.
pub fn write<C: AsRef<[u8]>>(path: &str, contents: C) -> io::Result<()>;

/// Given a path, query the file system to get information about a file,
/// directory, etc.
pub fn metadata(path: &str) -> io::Result<Metadata>;

/// Creates a new, empty directory at the provided path.
pub fn create_dir(path: &str) -> io::Result<()>;

/// Recursively create a directory and all of its parent components if they
/// are missing.
pub fn create_dir_all(path: &str) -> io::Result<()>;

/// Removes an empty directory.
pub fn remove_dir(path: &str) -> io::Result<()>;

/// Removes a file from the filesystem.
pub fn remove_file(path: &str) -> io::Result<()>;

/// Rename a file or directory to a new name.
/// Delete the original file if `old` already exists.
///
/// This only works then the new path is in the same mounted fs.
pub fn rename(old: &str, new: &str) -> io::Result<()>;

/// check whether absolute path exists.
pub fn absolute_path_exists(path: &str) -> bool;

上面的 API 可以分为以下三个部分,目前除了 create_dir_all 之外都已经实现了:

  1. 文件操作:readread_to_stringwriteremove_file
  2. 目录操作:read_dircreate_dircreate_dir_allremove_dir
  3. 其他操作:metadatacanonicalizeabsolute_path_existscurrent_dirset_current_dirrename
函数名 功能
read_dir 返回一个目录项迭代器
canonicalize 返回规范化的绝对路径
current_dir 返回当前工作目录
set_current_dir 更改当前工作目录
read 将文件的全部内容读入字节向量
read_to_string 将文件的全部内容读入字符串
write 将切片内容全部写入文件中
metadata 查询文件系统获取文件、目录等的信息
create_dir 创建一个空目录
create_dir_all 递归创建目录及其所有父目录(目前不支持)
remove_dir 删除指定空目录
remove_file 删除文件
rename 重命名文件或目录,若新路径已存在则删除原文件
absolute_path_exists 检查路径是否存在

下面是一个简单的使用示例:

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
/// 输出目录下的文件列表
fn do_ls(dir_path: &str) {
    info!("do_ls in {}", dir_path);
    api::read_dir(dir_path)
        .expect("read dir failed")
        .enumerate()
        .for_each(|(i, entry)| {
            let entry = entry.expect("read dir failed");
            info!("{} {}", i, entry.file_name());
        });
}

fn std_like_api() -> axio::Result {
    do_ls("/"); // 输出根目录下的文件列表

    let file_name = "test.txt";
    let s = String::from("hello world");

    // canonicalize: 路径规范化
    assert_eq!(api::canonicalize("/path/./to//foo")?, "/path/to/foo");

    // current_dir: 获取当前目录
    let current_dir = api::current_dir()?;
    assert_eq!(current_dir, "/");

    // create_dir: 创建目录
    api::create_dir("/test")?;
    check_exsist("/test", true);

    // // create_dir_all: 递归创建目录(暂时不支持)
    // api::create_dir_all("/test/b/c/d")?;
    // check_exsist("/test/b/c/d", true);

    // set_current_dir: 设置当前目录
    api::set_current_dir("/test")?;
    api::current_dir().map(|dir| assert_eq!(dir, "/test/"))?;

    // write: 写入文件
    api::write(file_name, &s)?;
    check_exsist("/test/test.txt", true);

    // read: 读取文件
    let res = api::read(file_name)?;
    assert_eq!(res, s.as_bytes());

    // read_to_string: 读取文件到字符串
    let res = api::read_to_string(file_name)?;
    assert_eq!(res, s);

    // rename: 重命名文件
    api::rename(file_name, "test_renamed.txt")?;
    check_exsist("/test/test.txt", false);
    check_exsist("/test/test_renamed.txt", true);

    // read_dir: 查看当前目录下的文件列表
    do_ls("/test");

    // remove_file: 删除文件
    api::remove_file("test_renamed.txt")?;
    check_exsist("/test/test_renamed.txt", false);

    // remove_dir: 删除目录
    api::set_current_dir("/")?;
    api::remove_dir("test")?;
    check_exsist("/test", false);

    Ok(())
}

基本元件 axio

这里的 io 是 axio,它类似于 Rust 标准库的 std::io,提供了在 no_std 环境下基本 IO 操作的接口。 axio 属于 ArceOS 的基本元件之一,作为一个独立的 crate 发布,类似的元件还有 axfs_crates, axmm_crates 等。体现了 ArceOS 的模块化设计理念。

Features 配置策略

从 ArceOS 的设计理念来看,文件系统的选择是一个重要的设计决策,因此在模块层的 axfs 中,为用户内核提供了 features 配置,静态选择所需的文件系统,进行初始化并挂载。

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
# modules/axfs/Cargo.toml
[features]
devfs = ["dep:axfs_devfs"]
ramfs = ["dep:axfs_ramfs"]
procfs = ["dep:axfs_ramfs"]
sysfs = ["dep:axfs_ramfs"]
lwext4_rs = ["dep:lwext4_rust"]
fatfs = ["dep:fatfs"]
myfs = ["dep:crate_interface"]
use-ramdisk = []

default = ["devfs", "ramfs", "fatfs", "procfs", "sysfs"]

特殊的文件系统

ArceOS 是一个兼容 linux 接口的组件化内核,因此需要挂载 devfsprocfssysfs 三个特殊的文件系统:

  1. devfs:设备文件系统,提供了对设备的访问接口,目前的 devfs 仅实现了 /dev/null/dev/zero 设备文件,具体参考axfs_vfs::axfs_devfs
  2. procfs:是一个基于内存的文件系统,提供了对进程信息的访问接口,目前的实现有待开发中。
  3. sysfs:系统文件系统,提供了对系统信息的访问接口,目前有待开发中。

这些文件系统是内核的一部分,提供了对内核数据结构的访问接口,用于在内核和用户空间之间传递特定信息或实现特定功能。

如果在 Arceos 中启用了 axfs 模块的 feature,那么初始化文件系统时,会在根目录中对它们进行挂载。

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
pub(crate) fn init_rootfs(disk: crate::dev::Disk) {
    // ...

    #[cfg(feature = "devfs")]
    root_dir
        .mount("/dev", mounts::devfs())
        .expect("failed to mount devfs at /dev");

    #[cfg(feature = "ramfs")]
    root_dir
        .mount("/tmp", mounts::ramfs())
        .expect("failed to mount ramfs at /tmp");

    // Mount another ramfs as procfs
    #[cfg(feature = "procfs")]
    root_dir // should not fail
        .mount("/proc", mounts::procfs().unwrap())
        .expect("fail to mount procfs at /proc");

    // Mount another ramfs as sysfs
    #[cfg(feature = "sysfs")]
    root_dir // should not fail
        .mount("/sys", mounts::sysfs().unwrap())
        .expect("fail to mount sysfs at /sys");

    // ...
}

根文件系统

根文件系统是操作系统的基础文件系统,其他文件系统都需要挂载在根文件系统上。

在 ArceOS 中,开发者通过改变 feature 选择具体的文件系统类型作为根文件系统。默认情况下,根文件系统选择的优先级顺序依次为 myfslwext4_rsfatfs。如果没有选择任何一个文件系统的 feature,则编译错误。

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
pub(crate) fn init_rootfs(disk: crate::dev::Disk) {
    cfg_if::cfg_if! {
        if #[cfg(feature = "myfs")] { // override the default filesystem
            let main_fs = fs::myfs::new_myfs(disk);
        } else if #[cfg(feature = "lwext4_rs")] {
            static EXT4_FS: LazyInit<Arc<fs::lwext4_rust::Ext4FileSystem>> = LazyInit::new();
            EXT4_FS.init_once(Arc::new(fs::lwext4_rust::Ext4FileSystem::new(disk)));
            let main_fs = EXT4_FS.clone();
        } else if #[cfg(feature = "fatfs")] {
            static FAT_FS: LazyInit<Arc<fs::fatfs::FatFileSystem>> = LazyInit::new();
            FAT_FS.init_once(Arc::new(fs::fatfs::FatFileSystem::new(disk)));
            FAT_FS.init();
            let main_fs = FAT_FS.clone();
        }
    }

    let root_dir = RootDirectory::new(main_fs);

    // ...
}

使用自定义的文件系统

ArceOS 允许开发者在用户内核中使用自定义的文件系统,只需要完成两个步骤:

  1. 在用户应用的 Cargo.toml 中添加对 myfs 的 feature,一般通过对 axstd/myfs 的依赖来实现。
  2. 实现 MyFileSystemIfImpl 接口
 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
/// The interface to define custom filesystems in user apps.
#[crate_interface::def_interface]
pub trait MyFileSystemIf {
    /// Creates a new instance of the filesystem with initialization.
    ///
    /// TODO: use generic disk type
    fn new_myfs(disk: Disk) -> Arc<dyn VfsOps>;
}

pub(crate) fn new_myfs(disk: Disk) -> Arc<dyn VfsOps> {
    crate_interface::call_interface!(MyFileSystemIf::new_myfs(disk))
}

这里 #[crate_interface::def_interface]crate_interface 提供的宏,通过在链接时通过符号将接口的定义和实现连接起来实现了依赖注入。最后,从用户视角看 modules/axfs/src/fs/myfs.rs 会成功转换为用户内核中定义的具体实现。

下面以 ArceOS 项目下的 examples/shell 为例,演示如何使用自定义的文件系统。

  1. 添加对 myfs 的依赖

     1
     2
     3
     4
     5
     6
     7
     8
     9
    10
    11
    # examples/shell/Cargo.toml
    
    [features]
    use-ramfs = ["axstd/myfs", "dep:axfs_vfs", "dep:axfs_ramfs", "dep:crate_interface"]
    default = []
    
    [dependencies]
    axfs_vfs = { version = "0.1", optional = true }
    axfs_ramfs = { version = "0.1", optional = true }
    crate_interface = { version = "0.1", optional = true }
    axstd = { workspace = true, features = ["alloc", "fs"], optional = true }
    

  2. 实现 MyFileSystemIfImpl 接口

     1
     2
     3
     4
     5
     6
     7
     8
     9
    10
    // examples/shell/src/ramfs.rs
    
    struct MyFileSystemIfImpl;
    
    #[crate_interface::impl_interface]
    impl MyFileSystemIf for MyFileSystemIfImpl {
        fn new_myfs(_disk: AxDisk) -> Arc<dyn VfsOps> {
            Arc::new(RamFileSystem::new())
        }
    }
    

随后,当 ArceOS 启动时,会自动调用 MyFileSystemIfImpl::new_myfs 函数来创建文件系统实例,并将其挂载到根目录上。

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
pub(crate) fn init_rootfs(disk: crate::dev::Disk) {
    cfg_if::cfg_if! {
        if #[cfg(feature = "myfs")] { // override the default filesystem
            let main_fs = fs::myfs::new_myfs(disk);
        } else if #[cfg(feature = "lwext4_rs")] {
            // ...
        } else if #[cfg(feature = "fatfs")] {
            // ...
        }
    }

    let root_dir = RootDirectory::new(main_fs);
}

评论

作者: LinkWanna (99.86%), Youjie Zheng (0.14%)