Skip to content

newfs 文件系统 #289

@Josen-B

Description

@Josen-B

一、问题描述

1.1 当前问题

当前 Axvisor 的文件系统仅支持单一文件系统格式(例如仅支持 ext4 或 vfat),直接使用整个块设备作为根文件系统,无法处理一个磁盘中存在多个分区的情况,即根据不同磁盘 gpt 自动识别分区并挂载对应文件系统。

/// Initializes filesystems by block devices.
pub fn init_filesystems(mut blk_devs: AxDeviceContainer<AxBlockDevice>) {
    info!("Initialize filesystems...");

    let dev = blk_devs.take_one().expect("No block device found!");
    info!("  use block device 0: {:?}", dev.device_name());
    self::root::init_rootfs(self::dev::Disk::new(dev));
}

这在需要同时访问多种文件系统(如一个分区为 FAT32,用于引导;另一个为 EXT4,用于系统文件)的场景中存在限制。需要增加分区设备Partition,以分区为单位建立文件系统,而不是以整个磁盘为单位建立文件系统

pub struct Partition {
    disk: Arc<Mutex<Disk>>,
    start_lba: u64,
    end_lba: u64,
    position: u64,
}

另外,许多硬件平台设备树中往往指定了特定分区作为根文件系统,当前的文件系统也缺乏这一逻辑实现。

chosen {
		stdout-path = "/pl011@9000000";
		bootargs = "root=/dev/sda2 rootfstype=ext4 rootwait  rw";
		rng-seed = <0xc9b1e7eb 0x3bd78eb5 0xd9c14167 0x64201d38 0x710a829d 0x152c658c 0x7ab066 0x4c71eb95>;
		kaslr-seed = <0x57c1b558 0xbe5eb48>;
	};

我们期望在初始化文件系统前,读取设备树的chosen节点,将bootargs的信息传递给文件系统,因此来指定哪个分区的文件系统作为根文件系统。

#[cfg(feature = "fs")]
        axfs::init_filesystems(all_devices.block, axhal::dtb::get_chosen_bootargs());
pub fn init_filesystems(mut blk_devs: AxDeviceContainer<AxBlockDevice>, bootargs: Option<&str>)

还有,不应再继续使用features来指定使用哪种文件系统,而是让系统自己判断。

fn is_fat_filesystem(boot_sector: &[u8; 512]) -> bool 
fn is_ext4_filesystem(disk: &mut Disk, start_lba: u64)

最后,当前的依赖项较多,依赖层数较深,应尽量精简。

1.2 设计目标

为了支持多分区和多文件系统,Axvisor 需要实现以下功能:

  1. 读取整个磁盘的分区表 GPT;
  2. 识别各分区的文件系统格式(ext4、vfat、others);
  3. 为每个分区选择并建立对应的文件系统;
  4. 通过设备树指定作为根文件系统的分区
  5. 精简代码结构,取消不必要的feature和依赖等

二、总体架构

Axvisor 文件系统采用清晰的分层架构,从上到下分为以下几个层次:

Image

2.1 分层设计

1. API 层 (API Layer)

API 层为用户提供了一组高级文件操作接口,包括文件读写、目录操作、文件属性管理等。这一层屏蔽了底层文件系统的差异,提供了一致的编程接口。

2. 虚拟文件系统层 (VFS Layer)

VFS 是文件系统的核心抽象层,负责:

  • 路径解析和名称管理
  • 文件系统挂载和卸载管理
  • 文件描述符管理
  • 统一的文件操作接口转换
  • 缓存管理和优化

3. 具体文件系统层 (Filesystem Implementations)

这一层包含各种具体文件系统的实现,每个文件系统都实现了标准的 VFS 接口:

  • FAT32:支持 FAT12/FAT16/FAT32 文件系统
  • EXT4:支持 Linux 标准的 EXT4 文件系统
  • RAMFS:内存文件系统,用于临时文件存储
  • PROCFS:进程文件系统,提供进程信息访问

4. 存储设备层 (Storage Layer)

存储设备层负责与具体的存储设备进行交互,包括:

  • 设备初始化和驱动加载
  • 分区扫描和管理
  • I/O 操作和调度

2.2 实现代码结构

axfs/
├── src/
│   ├── lib.rs          # 模块入口和初始化
│   ├── api/            # 高级API接口
│   │   ├── mod.rs      # API模块导出
│   │   ├── file.rs     # 文件操作API
│   │   └── dir.rs      # 目录操作API
│   ├── fops.rs         # 底层文件系统操作
│   ├── root.rs         # 根目录和挂载管理
│   ├── mounts.rs       # 文件系统挂载实现
│   ├── dev.rs          # 设备抽象层
│   ├── fs/             # 具体文件系统实现
│   │   ├── mod.rs      # 文件系统模块导出
│   │   ├── fatfs.rs    # FAT文件系统
│   │   └─── ext4fs.rs   # EXT4文件系统
│   └── partition.rs    # 磁盘分区管理
└── Cargo.toml

2.3 核心模块说明

  • lib.rs:文件系统模块的入口点,负责初始化和导出公共接口
  • api/:提供高级文件操作 API,包括文件和目录操作
  • fops.rs:实现底层文件系统操作,是 VFS 和具体文件系统之间的桥梁
  • root.rs:管理根目录和全局挂载点
  • mounts.rs:实现文件系统的挂载和卸载逻辑
  • dev.rs:设备抽象层,提供统一的设备访问接口
  • fs/:包含各种具体文件系统的实现
  • partition.rs:处理磁盘分区识别和管理

2.4 依赖项和features

文件系统的依赖项和features应在原先的基础上进行精简,保留必要的选项,取消不必要的选项,依赖关系不建议超过三层

  • 取消axns和axsync等依赖,使用spin代替原有的一致性功能
  • 取消fatfs,exr4fs,myfs等features,不使用features来指定使用哪种文件系统,而是通过读取gpt来判断分区或磁盘使用哪种文件系统
Image

依赖模块说明

自主依赖库

  • axdriver:提供统一的设备驱动抽象层,为块设备提供标准化的接口,是文件系统与硬件设备之间的桥梁

  • axio:输入输出抽象层,提供统一的I/O操作接口,支持内存分配

  • axfs_vfs:虚拟文件系统层,提供统一的文件系统抽象接口,支持多种文件系统的挂载和操作

  • fatfs:FAT文件系统具体实现,提供对FAT32文件系统的支持

  • lwext4fs:ext4文件系统的具体实现,提供对ext4文件系统的读写等功能的支持

  • axfs_ramfs:内存文件系统,提供基于内存的文件系统实现,用于临时文件存储

  • axerrno:提供统一的错误处理机制,定义文件系统操作中的错误类型和处理方式

第三方依赖库

  • cap_access:提供能力访问控制,管理文件系统访问权限

  • lazyinit:提供延迟初始化机制,优化系统启动性能,按需初始化

  • spin:提供自旋锁实现,在多核环境下保护共享数据

  • log:提供日志记录功能,记录文件系统操作和调试信息

三、模块划分与职责

3.1.虚拟文件系统层

  • Axfs VFS (Virtual File System) 是 Axvisor 操作系统的虚拟文件系统接口层,为上层应用提供统一的文件系统抽象接口。作为 ArceOS 生态系统的核心组件之一,Axfs VFS 采用 Rust 语言编写,充分利用了 Rust 的内存安全特性和高性能优势,为现代操作系统提供了可靠、高效的文件系统基础设施。

    Axfs VFS 提供了一套完整的文件系统抽象接口,支持文件和目录的创建、读取、写入、删除等基本操作。其核心特性包括:

    • 统一的文件系统接口:通过 VfsOps trait 为不同文件系统提供统一操作接口
    • 灵活的节点操作:通过 VfsNodeOps trait 支持文件和目录的细粒度操作
    • 类型安全:利用 Rust 的类型系统确保操作的安全性
    • 异步友好:接口设计支持异步操作模式
    • 权限管理:内置基于 Unix 权限模型的访问控制
    • 路径规范化:提供跨平台的路径处理功能
    // 核心特性概览
    pub trait VfsOps: Send + Sync {
        fn mount(&self, _path: &str, _mount_point: VfsNodeRef) -> VfsResult;
        fn umount(&self) -> VfsResult;
        fn format(&self) -> VfsResult;
        fn statfs(&self) -> VfsResult<FileSystemInfo>;
        fn root_dir(&self) -> VfsNodeRef;
    }
    
    pub trait VfsNodeOps: Send + Sync {
        // 文件和目录的通用操作
        fn open(&self) -> VfsResult;
        fn release(&self) -> VfsResult;
        fn get_attr(&self) -> VfsResult<VfsNodeAttr>;
        
        // 文件特有操作
        fn read_at(&self, offset: u64, buf: &mut [u8]) -> VfsResult<usize>;
        fn write_at(&self, offset: u64, buf: &[u8]) -> VfsResult<usize>;
        
        // 目录特有操作
        fn lookup(self: Arc<Self>, path: &str) -> VfsResult<VfsNodeRef>;
        fn create(&self, path: &str, ty: VfsNodeType) -> VfsResult;
    }

3.1.1 核心组件

1. VfsOps Trait

文件系统级别的操作接口,负责管理整个文件系统的生命周期。VfsOps trait 定义了文件系统级别的操作,包括挂载、卸载、格式化等高级操作。这些操作通常影响整个文件系统,而不是单个文件或目录。通过将这些操作集中在 VfsOps trait 中,Axfs VFS 实现了关注点分离,使得文件系统管理逻辑与节点操作逻辑相互独立。

pub trait VfsOps: Send + Sync {
    /// 挂载文件系统到指定路径
    fn mount(&self, _path: &str, _mount_point: VfsNodeRef) -> VfsResult {
        Ok(())
    }

    /// 卸载文件系统
    fn umount(&self) -> VfsResult {
        Ok(())
    }

    /// 格式化文件系统
    fn format(&self) -> VfsResult {
        ax_err!(Unsupported)
    }

    /// 获取文件系统属性
    fn statfs(&self) -> VfsResult<FileSystemInfo> {
        ax_err!(Unsupported)
    }

    /// 获取根目录节点
    fn root_dir(&self) -> VfsNodeRef;
}

实现要点:

  • mount()umount() 管理文件系统的生命周期
  • format() 提供文件系统初始化功能
  • statfs() 返回文件系统统计信息
  • root_dir() 是访问文件系统的入口点

2. VfsNodeOps Trait

节点级别的操作接口,处理文件和目录的具体操作。VfsNodeOps trait 是 Axfs VFS 中最核心的接口,它定义了文件系统节点(文件和目录)的所有可能操作。这个 trait 的设计充分考虑了文件和目录的不同特性,将操作分为通用操作、文件特有操作和目录特有操作三类。通过这种方式,Axfs VFS 既保证了接口的完整性,又允许不同类型的节点只实现相关的操作,提高了代码的清晰度和可维护性。

pub trait VfsNodeOps: Send + Sync {
    // 通用操作
    fn open(&self) -> VfsResult { Ok(()) }
    fn release(&self) -> VfsResult { Ok(()) }
    fn get_attr(&self) -> VfsResult<VfsNodeAttr> { ax_err!(Unsupported) }

    // 文件操作
    fn read_at(&self, _offset: u64, _buf: &mut [u8]) -> VfsResult<usize> {
        ax_err!(InvalidInput)
    }
    fn write_at(&self, _offset: u64, _buf: &[u8]) -> VfsResult<usize> {
        ax_err!(InvalidInput)
    }
    fn fsync(&self) -> VfsResult { ax_err!(InvalidInput) }
    fn truncate(&self, _size: u64) -> VfsResult { ax_err!(InvalidInput) }

    // 目录操作
    fn parent(&self) -> Option<VfsNodeRef> { None }
    fn lookup(self: Arc<Self>, _path: &str) -> VfsResult<VfsNodeRef> {
        ax_err!(Unsupported)
    }
    fn create(&self, _path: &str, _ty: VfsNodeType) -> VfsResult {
        ax_err!(Unsupported)
    }
    fn remove(&self, _path: &str) -> VfsResult { ax_err!(Unsupported) }
    fn read_dir(&self, _start_idx: usize, _dirents: &mut [VfsDirEntry]) -> VfsResult<usize> {
        ax_err!(Unsupported)
    }
    fn rename(&self, _src_path: &str, _dst_path: &str) -> VfsResult {
        ax_err!(Unsupported)
    }

    // 类型转换
    fn as_any(&self) -> &dyn core::any::Any { unimplemented!() }
}

3.1.2 数据结构

Axfs VFS 定义了一系列核心数据结构来表示文件系统中的各种概念。这些数据结构经过精心设计,既保持了与 Unix 文件系统的兼容性,又充分利用了 Rust 的类型系统优势。每个数据结构都有明确的职责和语义,共同构成了一个完整、一致的文件系统模型。

VfsNodeAttr - 节点属性

VfsNodeAttr 结构体表示文件系统节点的属性信息,类似于 Unix 系统中的 stat 结构。它包含了权限模式、节点类型、文件大小和占用块数等基本信息。这个结构体设计为 Copy 类型,意味着它可以被廉价地复制,这在频繁访问文件属性的场景中非常有用。

#[derive(Debug, Clone, Copy)]
pub struct VfsNodeAttr {
    mode: VfsNodePerm,    // 权限模式
    ty: VfsNodeType,      // 节点类型
    size: u64,            // 文件大小
    blocks: u64,          // 占用块数
}

VfsNodePerm - 权限管理

VfsNodePerm 使用 Rust 的 bitflags crate 实现了基于 Unix 权限模型的权限管理系统。它支持传统的用户、组和其他三类权限,每类包含读、写、执行三种权限。这种设计不仅与 Unix 系统保持兼容,还提供了类型安全的权限操作方法,避免了传统 C 语言中容易出现的位操作错误。

bitflags::bitflags! {
    pub struct VfsNodePerm: u16 {
        const OWNER_READ = 0o400;
        const OWNER_WRITE = 0o200;
        const OWNER_EXEC = 0o100;
        const GROUP_READ = 0o40;
        const GROUP_WRITE = 0o20;
        const GROUP_EXEC = 0o10;
        const OTHER_READ = 0o4;
        const OTHER_WRITE = 0o2;
        const OTHER_EXEC = 0o1;
    }
}

VfsNodeType - 节点类型

VfsNodeType 枚举定义了文件系统支持的所有节点类型,包括普通文件、目录、字符设备、块设备、命名管道、符号链接和套接字。这个枚举使用 repr(u8) 属性,确保每个类型都有明确的数值表示,便于与底层系统交互。同时,它提供了丰富的辅助方法,如 is_file()is_dir() 等,使类型检查更加直观和安全。

#[repr(u8)]
#[derive(Debug, Clone, Copy, Eq, PartialEq)]
pub enum VfsNodeType {
    Fifo = 0o1,           // 命名管道
    CharDevice = 0o2,     // 字符设备
    Dir = 0o4,            // 目录
    BlockDevice = 0o6,    // 块设备
    File = 0o10,          // 普通文件
    SymLink = 0o12,       // 符号链接
    Socket = 0o14,        // 套接字
}

3.1.3 路径管理模块

路径管理是文件系统中的重要组成部分,Axfs VFS 提供了强大的路径规范化功能。路径规范化处理能够将包含相对路径、冗余分隔符等复杂路径转换为标准形式,这对于文件系统的安全性和一致性至关重要。Axfs VFS 的路径处理模块不仅支持 Unix 风格的路径,还考虑了跨平台兼容性,为上层应用提供了统一的路径操作接口。

pub fn canonicalize(path: &str) -> String {
    let mut buf = String::new();
    let is_absolute = path.starts_with('/');
    
    for part in path.split('/') {
        match part {
            "" | "." => continue,
            ".." => {
                // 处理上级目录
                while !buf.is_empty() {
                    if buf == "/" { break; }
                    let c = buf.pop().unwrap();
                    if c == '/' { break; }
                }
            }
            _ => {
                // 添加路径组件
                if !buf.is_empty() && !buf.ends_with('/') {
                    buf.push('/');
                }
                buf.push_str(part);
            }
        }
    }
    
    if is_absolute && buf.is_empty() {
        buf.push('/');
    }
    buf
}

3.1.4 API 接口详解

Axfs VFS 提供了丰富的 API 接口,涵盖了文件系统的所有核心功能。这些接口设计遵循 Rust 的最佳实践,既保证了易用性,又确保了安全性。每个接口都有明确的语义和错误处理机制,使得开发者可以轻松地构建可靠的文件系统应用。

1. 文件系统操作接口

文件系统操作接口主要涉及文件系统的生命周期管理,包括挂载、卸载、格式化等操作。这些操作通常需要管理员权限,并且会影响整个文件系统的状态。Axfs VFS 通过 VfsOps trait 提供了这些高级操作的统一接口。

挂载操作

挂载操作是将文件系统集成到系统目录树中的过程。Axfs VFS 的挂载接口设计得非常灵活,支持将文件系统挂载到任意目录路径。挂载过程包括路径验证、挂载点检查、资源分配等多个步骤,确保挂载操作的安全性和可靠性。

impl MyFileSystem {
    fn mount(&self, path: &str, mount_point: VfsNodeRef) -> VfsResult {
        // 1. 验证挂载路径
        if !path.starts_with('/') {
            return ax_err!(InvalidInput);
        }
        
        // 2. 检查挂载点是否存在
        if mount_point.get_attr()?.is_file() {
            return ax_err!(NotADirectory);
        }
        
        // 3. 执行挂载逻辑
        self.mount_points.lock().insert(path.to_string(), mount_point);
        
        // 4. 初始化文件系统
        self.initialize()?;
        
        Ok(())
    }
}
卸载操作

卸载操作是从系统目录树中移除文件系统的过程。这是一个需要谨慎执行的操作,因为卸载时必须确保没有进程正在使用该文件系统。Axfs VFS 的卸载接口包含了完整的检查流程,包括打开文件计数检查、缓存数据同步、资源清理等步骤,确保卸载操作的安全性和数据完整性。

fn umount(&self) -> VfsResult {
    // 1. 检查是否有正在使用的文件
    if self.open_files_count() > 0 {
        return ax_err!(Busy);
    }
    
    // 2. 同步缓存数据
    self.sync_all()?;
    
    // 3. 清理资源
    self.cleanup()?;
    
    Ok(())
}

2. 节点操作接口

节点操作接口是 Axfs VFS 的核心,提供了对文件和目录进行细粒度操作的能力。这些接口通过 VfsNodeOps trait 定义,涵盖了文件和目录的所有基本操作。节点操作接口的设计充分考虑了不同类型节点的特性,提供了类型安全的操作方法,并确保了在多线程环境下的安全性。

文件读取

文件读取是文件系统中最基本也是最重要的操作之一。Axfs VFS 提供了基于偏移量的随机读取接口,支持从文件的任意位置开始读取指定长度的数据。这种设计既支持顺序读取,也支持随机访问,满足了不同应用场景的需求。读取操作包含了完整的参数验证、边界检查和性能优化,确保操作的安全性和效率。

fn read_at(&self, offset: u64, buf: &mut [u8]) -> VfsResult<usize> {
    // 1. 参数验证
    if offset >= self.size {
        return Ok(0); // EOF
    }
    
    // 2. 计算实际读取长度
    let to_read = core::cmp::min(buf.len(), (self.size - offset) as usize);
    
    // 3. 执行读取操作
    let data = self.data.lock();
    let read_end = offset as usize + to_read;
    buf[..to_read].copy_from_slice(&data[offset as usize..read_end]);
    
    // 4. 更新访问时间
    self.update_access_time();
    
    Ok(to_read)
}
文件写入

文件写入操作允许应用程序向文件中写入数据。Axfs VFS 提供了基于偏移量的写入接口,支持在文件的任意位置进行写入操作。写入接口包含了权限检查、空间扩展、数据写入和属性更新等完整流程,确保写入操作的安全性和数据一致性。特别是在需要扩展文件大小时,系统会自动处理空间分配,简化了上层应用的开发。

fn write_at(&self, offset: u64, buf: &[u8]) -> VfsResult<usize> {
    // 1. 检查写入权限
    if !self.is_writable() {
        return ax_err!(PermissionDenied);
    }
    
    // 2. 扩展文件空间(如果需要)
    let write_end = offset + buf.len() as u64;
    if write_end > self.size {
        self.resize(write_end)?;
    }
    
    // 3. 执行写入操作
    let mut data = self.data.lock();
    let start = offset as usize;
    let end = start + buf.len();
    data[start..end].copy_from_slice(buf);
    
    // 4. 更新修改时间和大小
    self.update_modify_time();
    self.size = write_end;
    
    Ok(buf.len())
}
目录遍历

目录遍历是访问目录内容的基本操作。Axfs VFS 提供了基于索引的目录条目读取接口,支持分批读取目录内容,这对于包含大量文件的目录特别有用。目录遍历接口设计得非常灵活,支持从任意位置开始读取,并且能够处理各种边界情况,如空目录、索引超出范围等。

fn read_dir(&self, start_idx: usize, dirents: &mut [VfsDirEntry]) -> VfsResult<usize> {
    // 1. 获取目录条目
    let entries = self.entries.lock();
    
    // 2. 检查起始索引
    if start_idx >= entries.len() {
        return Ok(0);
    }
    
    // 3. 填充目录条目
    let mut count = 0;
    for (i, entry) in entries.iter().skip(start_idx).enumerate() {
        if i >= dirents.len() {
            break;
        }
        
        dirents[i] = VfsDirEntry::new(&entry.name, entry.ty);
        count += 1;
    }
    
    Ok(count)
}

3.2 文件系统实现层

3.2.1 fat32文件系统

主要特性

  • 全面的 FAT 支持:支持 FAT12、FAT16 和 FAT32 文件系统
  • 长文件名支持:支持 LFN (Long File Name) 扩展,完全兼容 Windows 长文件名规范
  • 标准 I/O 接口:实现了标准的 Read/Write traits,与 Rust 生态系统无缝集成
  • 目录操作:支持创建、删除、重命名文件和目录
  • 时间戳管理:支持读写文件时间戳(启用 chrono 功能时自动更新)
  • 格式化功能:支持格式化卷,可自定义各种参数
  • no_std 支持:基本支持 no_std 环境,适用于嵌入式开发
  • 可配置日志:支持通过 cargo 功能在编译时配置日志级别
  • 错误处理:提供详细的错误类型和处理机制

核心模块

  1. 文件系统核心 (fs.rs)

    • FileSystem 结构体:文件系统的主要接口
    • FatType 枚举:定义 FAT 类型(FAT12/FAT16/FAT32)
    • FsStatusFlags 结构体:文件系统状态标志
    • FormatVolumeOptions 结构体:格式化选项,可自定义文件系统参数
    • 文件系统初始化和管理功能
  2. 目录管理 (dir.rs)

    • Dir 结构体:目录操作接口
    • DirRawStream 枚举:目录底层数据流
    • DirEditor 结构体:目录编辑器,用于修改目录项
    • 目录遍历、创建和删除功能
  3. 文件操作 (file.rs)

    • File 结构体:文件操作接口
    • Extent 结构体:文件在磁盘上的数据范围
    • FileEditor 结构体:文件元数据编辑器
    • 文件读写、截断和扩展功能
  4. 目录项管理 (dir_entry.rs)

    • DirEntry 结构体:目录项接口,提供文件和目录的元数据访问
    • DirEntryData 枚举:目录项数据,区分文件、目录和长文件名项
    • DirFileEntryData 结构体:文件目录项的具体数据
    • DirLfnEntryData 结构体:长文件名项的具体数据
    • ShortName 结构体:短文件名(8.3 格式)处理
    • 短文件名和长文件名处理
  5. 引导扇区 (boot_sector.rs)

    • BootSector 结构体:引导扇区解析,包含文件系统关键信息
    • BiosParameterBlock 结构体:BIOS 参数块,包含文件系统几何信息
    • FsInfo 结构体:FAT32 文件系统信息结构
    • 文件系统元数据解析和验证
    • 引导代码和签名验证
  6. 错误处理 (error.rs)

    • Error 枚举:定义各种错误类型
    • 统一的错误处理机制
    • 错误上下文和详细信息
  7. I/O 抽象 (io.rs)

    • IoBase trait:基础 I/O 操作
    • ReadWriteSeek traits:标准 I/O 接口
    • ReadWriteSeek trait:组合 trait,简化类型签名
    • StdIoWrapper 结构体:标准 I/O 类型的包装器
    • IntoStorage trait:类型转换 trait,简化 API 使用
  8. 时间管理 (time.rs)

    • TimeProvider trait:时间提供者接口,支持自定义时间源
    • DateTimeDateTime 结构体:时间表示,与 FAT 时间格式兼容
    • DefaultTimeProvider 结构体:默认时间提供者
    • ChronoTimeProvider 结构体:基于 chrono 库的时间提供者
    • NullTimeProvider 结构体:空时间提供者,用于测试或无时间环境
    • 时间格式转换和时区处理.
  9. FAT 表管理 (table.rs)

    • ClusterIterator 结构体:簇链迭代器,用于遍历文件占用的所有簇
    • FAT 表读写操作
    • 簇分配和释放算法
    • 坏簇检测和处理
    • FAT 表缓存和优化

高级功能

  1. 文件系统格式化
use fatfs::{format_volume, FormatVolumeOptions, FsOptions, MediaType};

// 基本格式化
let options = FormatVolumeOptions::new();
let formatted_img = format_volume(&mut img_file, options)?;

// 自定义格式化选项
let options = FormatVolumeOptions::new()
    .format_fat_type(fatfs::FatType::Fat32)  // 指定 FAT 类型
    .volume_label("MYVOLUME")                 // 设置卷标
    .bytes_per_cluster(4096)                  // 设置每簇字节数
    .media(MediaType::FixedDisk);             // 设置媒体类型

let formatted_img = format_volume(&mut img_file, options)?;
  1. 时间戳管理
use fatfs::{FileSystem, FsOptions};
use chrono::{Utc, DateTime};

// 创建文件系统时启用时间戳
let fs = FileSystem::new(buf_stream, FsOptions::new())?;

// 获取文件时间戳
let file = root_dir.open_file("example.txt")?;
let modified_time = file.modified();
let created_time = file.created();
let accessed_time = file.accessed();
  1. 文件扩展信息
// 获取文件在磁盘上的物理位置
let mut file = root_dir.open_file("large_file.bin")?;
for extent in file.extents() {
    let extent = extent?;
    println!("Offset: {}, Size: {}", extent.offset, extent.size);
}
  1. 文件系统状态检查
// 检查文件系统状态
let status_flags = fs.status_flags();
if status_flags.dirty() {
    println!("文件系统标记为脏,可能未正确卸载");
}
if status_flags.io_error() {
    println!("文件系统检测到 I/O 错误");
}

// 获取文件系统统计信息
let total_clusters = fs.total_clusters();
let free_clusters = fs.free_clusters()?;
let used_clusters = total_clusters - free_clusters;
println!("使用率: {:.1}%", (used_clusters as f64 / total_clusters as f64) * 100.0);
  1. 文件属性操作
use fatfs::FileAttributes;

let file = root_dir.open_file("important.txt")?;
let mut attrs = file.attributes();

// 检查属性
if attrs.contains(FileAttributes::READ_ONLY) {
    println!("文件为只读");
}

// 修改属性
attrs.set(FileAttributes::HIDDEN, true);  // 设置隐藏属性
attrs.set(FileAttributes::ARCHIVE, false); // 清除归档属性
file.set_attributes(attrs)?;

实现细节

FAT 类型检测

根据簇的数量自动确定 FAT 类型:

  • FAT12: 簇数量 < 4085
  • FAT16: 4085 ≤ 簇数量 < 65525
  • FAT32: 簇数量 ≥ 65525
长文件名处理

长文件名通过多个连续的目录项实现,每个长文件名项可以存储最多 13 个 Unicode 字符。自动处理长文件名和短文件名(8.3 格式)之间的转换。

簇链管理

FAT 文件系统使用簇链来管理文件和目录的存储。提供了完整的簇链操作功能,包括分配、释放和遍历。

缓存策略

建议使用缓冲流(如 fscommon::BufStream)来提高性能,减少底层 I/O 操作次数。

缓存层次结构:

  1. 应用层缓存:使用 BufStream 缓冲 I/O 操作
  2. FAT 表缓存:缓存频繁访问的 FAT 表项
  3. 目录项缓存:缓存最近访问的目录项
  4. 数据缓存:缓存文件数据块

缓存一致性:

  • 实现了写回策略确保数据一致性
  • 支持强制刷新机制
  • 处理缓存失效和更新

3.2.2 ext4文件系统

主要特性

  • 完整 Ext4 实现:支持 Ext4 文件系统的所有核心特性,包括动态 inode 大小、extent 树、块组管理等
  • Extent 树支持:使用 extent 树替代传统块映射,提高大文件存储效率,支持连续块分配
  • 日志系统 (JBD2):实现 Journaling Block Device 2,支持 ordered 模式,保证文件系统一致性
  • 块设备抽象:通过 BlockDevice trait 抽象底层存储,提供统一的块读写接口
  • 多级缓存系统:包括位图缓存、inode 表缓存、数据块缓存,支持 LRU 淘汰策略
  • 目录操作:支持目录创建、遍历、硬链接、符号链接等
  • 文件操作:支持文件的创建、读取、写入、截断、删除等,支持基于偏移量的随机访问
  • 元数据校验:支持元数据校验和功能(通过 feature flag 控制)

核心组件

Ext4FileSystem

文件系统的核心结构体,管理整个文件系统的状态:

pub struct Ext4FileSystem {
    pub superblock: Ext4Superblock,           // 超级块
    pub group_descs: Vec<Ext4GroupDesc>,      // 块组描述符数组
    pub block_allocator: BlockAllocator,      // 块分配器
    pub inode_allocator: InodeAllocator,      // Inode分配器
    pub bitmap_cache: BitmapCache,            // 位图缓存
    pub inodetable_cahce: InodeCache,         // Inode表缓存
    pub datablock_cache: DataBlockCache,      // 数据块缓存
    pub root_inode: u32,                      // 根目录inode号
    pub group_count: u32,                     // 块组数量
    pub mounted: bool,                        // 是否已挂载
    pub journal_sb_block_start: Option<u32>,  // Journal超级块位置
}

该结构体封装了文件系统的所有核心状态,是文件系统操作的中心枢纽。超级块包含全局文件系统信息,块组描述符管理各个块组的元数据,各种分配器负责资源管理,缓存系统提高性能。挂载时初始化所有组件,卸载时确保数据持久化并清理资源。

BlockDevice Trait

抽象底层块设备的接口,提供统一的块读写操作:

pub trait BlockDevice {
    fn read(&mut self, buffer: &mut [u8], block_id: u32, count: u32) -> BlockDevResult<()>;
    fn write(&mut self, buffer: &[u8], block_id: u32, count: u32) -> BlockDevResult<()>;
    fn open(&mut self) -> BlockDevResult<()>;
    fn close(&mut self) -> BlockDevResult<()>;
    fn total_blocks(&self) -> u64;
    fn block_size(&self) -> u32;
}

这个 trait 定义了块设备的基本操作接口,通过抽象层屏蔽了底层存储的差异。readwrite 方法支持多块连续操作,提高了 I/O 效率。openclose 方法允许设备进行初始化和清理工作。total_blocksblock_size 提供了设备的基本信息。这种设计使得 rsext4 可以轻松适配不同的存储介质,如传统的磁盘驱动器、RAM 磁盘、SSD 或甚至是网络存储。通过实现这个 trait,开发者可以为 rsext4 添加新的存储后端支持。

Jbd2Dev

日志系统的包装器,为块设备添加日志功能:

pub struct Jbd2Dev<B: BlockDevice> {
    block_dev: B,
    journal_system: Option<JBD2DEVSYSTEM>,
    use_journal: bool,
}

Jbd2Dev 在 BlockDevice 基础上增加了事务日志支持,确保文件系统操作的原子性和一致性。它维护了一个日志系统状态,可以动态启用或禁用日志功能。journal_system 字段存储日志系统的内部状态,use_journal 标志控制是否实际使用日志。这种设计允许在性能和安全性之间进行权衡:在格式化或某些特殊操作时可以临时关闭日志以提高性能,而在正常运行时启用日志保证数据一致性。Jbd2Dev 还处理日志的重放,确保系统崩溃后能够恢复到一致状态。

缓存系统
BitmapCache

管理块位图和 inode 位图的缓存,支持按需加载和 LRU 淘汰:

/// 位图缓存管理器
pub struct BitmapCache {
    /// 缓存的位图
    cache: BTreeMap<CacheKey, CachedBitmap>,
    /// 最大缓存条目数(LRU淘汰)
    max_entries: usize,
    /// 访问计数器(用于LRU)
    access_counter: u64,
}

/// 使用闭包修改指定位图,并自动标记为脏
pub fn modify<B, F>(
    &mut self,
    block_dev: &mut Jbd2Dev<B>,
    key: CacheKey,
    block_num: u64,
    f: F,
) -> BlockDevResult<()>
where
    B: BlockDevice,
    F: FnOnce(&mut [u8]),
{
    let bitmap = self.get_or_load_mut(block_dev, key, block_num)?;
    f(&mut bitmap.data);
    bitmap.mark_dirty();
    Ok(())
}

BitmapCache 通过 LRU 策略管理位图缓存,减少磁盘 I/O,提高分配性能。

InodeCache

缓存 inode 表数据,避免频繁磁盘访问:

/// Inode缓存管理器
pub struct InodeCache {
    /// 缓存的inode
    cache: BTreeMap<InodeCacheKey, CachedInode>,
    /// 最大缓存条目数
    max_entries: usize,
    /// 访问计数器
    access_counter: u64,
    /// 每个inode的大小
    inode_size: usize,
}

/// 使用闭包修改指定inode
pub fn modify<B, F>(
    &mut self,
    block_dev: &mut Jbd2Dev<B>,
    inode_num: u64,
    block_num: u64,
    offset: usize,
    f: F,
) -> BlockDevResult<()>
where
    B: BlockDevice,
    F: FnOnce(&mut Ext4Inode),
{
    let cached = self.get_or_load_mut(block_dev, inode_num, block_num, offset)?;
    f(&mut cached.inode);
    cached.mark_dirty();
    Ok(())
}

InodeCache 缓存 inode 结构,支持延迟写回,减少元数据访问延迟。

DataBlockCache

缓存文件数据块,支持大文件的高效访问:

/// 数据块缓存管理器
pub struct DataBlockCache {
    /// 缓存的数据块
    cache: BTreeMap<BlockCacheKey, CachedBlock>,
    /// 最大缓存条目数
    max_entries: usize,
    /// 访问计数器(用于LRU)
    access_counter: u64,
    /// 块大小
    block_size: usize,
}

/// 获取数据块(如果不存在则从磁盘加载)
pub fn get_or_load<B: BlockDevice>(
    &mut self,
    block_dev: &mut Jbd2Dev<B>,
    block_num: u64,
) -> BlockDevResult<&CachedBlock> {
    if !self.cache.contains_key(&block_num) {
        if self.cache.len() >= self.max_entries {
            self.evict_lru(block_dev)?;
        }
        // 加载块数据...
    }
    // 返回缓存的块
    self.cache.get(&block_num).ok_or(BlockDevError::Corrupted)
}

DataBlockCache 缓存文件内容块,支持随机访问和顺序访问的性能优化。

分配器
BlockAllocator

负责数据块的分配和释放,支持连续块分配:

/// 块分配器
pub struct BlockAllocator {
    blocks_per_group: u32,
    first_data_block: u32,
}

/// 在指定块组中分配连续的多个块
pub fn alloc_contiguous_blocks(
    &self,
    bitmap_data: &mut [u8],
    group_idx: u32,
    count: u32,
) -> Result<BlockAlloc, AllocError> {
    let mut bitmap = BlockBitmapMut::new(bitmap_data, self.blocks_per_group);
    let block_in_group = self
        .find_contiguous_free_blocks(&bitmap, count)?
        .ok_or(AllocError::NoSpace)?;
    bitmap.allocate_range(block_in_group, count)?;
    let global_block = self.block_to_global(group_idx, block_in_group);
    Ok(BlockAlloc {
        group_idx,
        block_in_group,
        global_block,
    })
}

BlockAllocator 管理数据块的分配和释放,支持连续块分配以减少碎片,提高 I/O 性能。连续分配算法通过扫描位图找到指定数量的连续空闲块,这种策略能够显著减少文件碎片,提高文件访问的局部性。

InodeAllocator

负责 inode 的分配和释放:

/// Inode分配器
pub struct InodeAllocator {
    inodes_per_group: u32,
    first_inode: u32,
}

/// 在指定块组中分配一个inode
pub fn alloc_inode_in_group(
    &self,
    bitmap_data: &mut [u8],
    group_idx: u32,
    group_desc: &Ext4GroupDesc,
) -> Result<InodeAlloc, AllocError> {
    if group_desc.free_inodes_count() == 0 {
        return Err(AllocError::NoSpace);
    }
    let mut bitmap = InodeBitmapMut::new(bitmap_data, self.inodes_per_group);
    let inode_in_group = self.find_free_inode(&bitmap)?.ok_or(AllocError::NoSpace)?;
    bitmap.allocate(inode_in_group)?;
    let global_inode = self.inode_to_global(group_idx, inode_in_group);
    Ok(InodeAlloc {
        group_idx,
        inode_in_group,
        global_inode,
    })
}

InodeAllocator 管理 inode 分配,确保文件和目录的元数据空间。每个 inode 代表文件系统中的一个文件或目录,分配器维护 inode 位图以跟踪使用情况。

内部细节

文件系统布局

Ext4 文件系统按块组组织,每个块组包含:

  • 超级块:文件系统全局元数据
  • 块组描述符:块组元数据
  • 块位图:标记块使用情况
  • inode 位图:标记 inode 使用情况
  • inode 表:存储 inode 结构
  • 数据块:实际存储文件数据

超级块结构定义:

#[repr(C)]
pub struct Ext4Superblock {
    // 基本信息
    pub s_inodes_count: u32,         // Inode总数
    pub s_blocks_count_lo: u32,      // 块总数(低32位)
    pub s_free_blocks_count_lo: u32, // 空闲块数(低32位)
    pub s_free_inodes_count: u32,    // 空闲inode数
    pub s_first_data_block: u32,     // 第一个数据块
    pub s_log_block_size: u32,       // 块大小 = 1024 << s_log_block_size
    pub s_blocks_per_group: u32,     // 每个块组的块数
    pub s_inodes_per_group: u32,     // 每个块组的inode数
    
    // 状态和特性
    pub s_magic: u16,                // 魔数 0xEF53
    pub s_state: u16,                // 文件系统状态
    pub s_feature_compat: u32,       // 兼容特性标志
    pub s_feature_incompat: u32,     // 不兼容特性标志
    pub s_uuid: [u8; 16],            // 128位UUID
    // ... 更多字段
}

块组描述符结构:

#[repr(C)]
pub struct Ext4GroupDesc {
    // 基本信息(32字节)
    pub bg_block_bitmap_lo: u32,     // 块位图块号(低32位)
    pub bg_inode_bitmap_lo: u32,     // Inode位图块号(低32位)
    pub bg_inode_table_lo: u32,      // Inode表起始块号(低32位)
    pub bg_free_blocks_count_lo: u16, // 空闲块数(低16位)
    pub bg_free_inodes_count_lo: u16, // 空闲inode数(低16位)
    pub bg_used_dirs_count_lo: u16,   // 目录数(低16位)
    pub bg_flags: u16,                // 标志
    // ... 更多字段
}

文件系统布局提供了高效的元数据管理和数据访问机制。块组结构将相关元数据集中存储,减少了磁盘寻道时间,提高了 I/O 性能。

挂载过程

挂载过程涉及读取和验证文件系统元数据,初始化各种组件:

/// 打开Ext4文件系统
pub fn mount<B: BlockDevice>(block_dev: &mut Jbd2Dev<B>) -> Result<Self, RSEXT4Error> {
    // 1. 读取超级块
    let superblock = read_superblock(block_dev).map_err(|_| RSEXT4Error::IoError)?;
    
    // 2. 验证魔数
    if superblock.s_magic != EXT4_SUPER_MAGIC {
        return Err(RSEXT4Error::InvalidMagic);
    }
    
    // 3. 计算块组数量并读取块组描述符
    let group_count = superblock.block_groups_count();
    let group_descs = Self::load_group_descriptors(block_dev, group_count)?;
    
    // 4. 初始化分配器和缓存
    let block_allocator = BlockAllocator::new(&superblock);
    let inode_allocator = InodeAllocator::new(&superblock);
    let bitmap_cache = BitmapCache::default();
    let inode_cache = InodeCache::new(INODE_CACHE_MAX, inode_size);
    let datablock_cache = DataBlockCache::new(DATABLOCK_CACHE_MAX, BLOCK_SIZE);
    
    // 5. 构造文件系统实例
    let mut fs = Self {
        superblock,
        group_descs,
        block_allocator,
        inode_allocator,
        bitmap_cache,
        inodetable_cahce: inode_cache,
        datablock_cache,
        root_inode: 2,
        group_count,
        mounted: true,
        journal_sb_block_start: None,
    };
    
    // 6. 检查和创建根目录
    let root_inode = fs.get_root(block_dev)?;
    if root_inode.i_mode == 0 || !root_inode.is_dir() {
        fs.create_root_dir(block_dev)?;
    }
    
    Ok(fs)
}

挂载过程确保文件系统处于一致状态,并初始化所有必要的组件。

文件操作流程
文件读取

文件读取涉及路径解析、extent映射和数据缓存:

///读取整个文件内容
pub fn read<B: BlockDevice>(
    dev: &mut Jbd2Dev<B>,
    fs: &mut Ext4FileSystem,
    path: &str,
) -> BlockDevResult<Option<Vec<u8>>> {
    read_file(dev, fs, path)
}

/// read_at 计算文件offset后读取
pub fn read_at<B: BlockDevice>(
    dev: &mut Jbd2Dev<B>,
    fs: &mut Ext4FileSystem,
    file: &mut OpenFile,
    len: usize,
) -> BlockDevResult<Vec<u8>> {
    refresh_open_file_inode(dev, fs, file)?;
    let file_size = file.inode.size() as u64;
    if file.offset >= file_size {
        return Ok(Vec::new());
    }
    
    let extent_map = resolve_inode_block_allextend(fs, dev, &mut file.inode)?;
    // 解析extent树获取块映射,然后读取数据块缓存
    // ...
}

文件读取通过extent树高效定位数据块,支持随机访问。extent树将逻辑块号映射到物理块号,减少了间接块的开销。对于大文件,这种映射方式特别高效。

文件写入

文件写入涉及块分配、extent更新和缓存管理:

///写入文件:基于当前offset追加写入
pub fn write_at<B: BlockDevice>(
    dev: &mut Jbd2Dev<B>,
    fs: &mut Ext4FileSystem,
    file: &mut OpenFile,
    data: &[u8],
) -> BlockDevResult<()> {
    write_file(dev, fs, &file.path, file.offset, data)?;
    file.offset = file.offset.saturating_add(data.len() as u64);
    refresh_open_file_inode(dev, fs, file)?;
    Ok(())
}

文件写入通过extent树管理块分配,支持动态扩展。写入操作首先检查是否有足够的空闲块,然后分配块并更新extent树,最后将数据写入缓存。

Extent 树机制

Extent 树是 Ext4 的核心特性,用于高效管理大文件的块映射。传统的块映射使用间接块,随着文件增大,间接块层次会增加,导致访问效率降低。Extent 树通过记录连续块范围解决了这个问题,每个 extent 条目表示一段连续的块映射,大大减少了元数据开销。

/// 内存中的 extent 树节点表示
pub enum ExtentNode {
    /// 叶子节点:存储实际的块映射
    Leaf {
        header: Ext4ExtentHeader,
        entries: Vec<Ext4Extent>,
    },
    /// 内部节点:存储子节点的块号
    Index {
        header: Ext4ExtentHeader,
        entries: Vec<Ext4ExtentIdx>,
    },
}

/// 绑定到单个 inode 的 extent 树视图
pub struct ExtentTree<'a> {
    pub inode: &'a mut Ext4Inode,
}

Extent 树支持连续块范围的映射,减少元数据开销,提高大文件性能。

日志系统 (JBD2)

JBD2 保证文件系统一致性:

///提交事务
pub fn commit_transaction<B: BlockDevice>(
    &mut self,
    block_dev: &mut B,
) -> Result<bool, ()> {
    let tid = self.sequence;
    // 写描述符块
    let mut desc_buffer = vec![0; BLOCK_SIZE];
    let mut new_jbd_header = JournalHeaderS::default();
    new_jbd_header.h_blocktype = 1; // Descriptor
    new_jbd_header.h_sequence = tid;
    new_jbd_header.to_disk_bytes(&mut desc_buffer[0..JournalHeaderS::disk_size()]);
    
    // 写数据块标签和数据
    for (idx, update) in self.commit_queue.iter().enumerate() {
        let mut tag = JournalBlockTagS {
            t_blocknr: update.0 as u32,
            t_checksum: 0,
            t_flags: 0,
        };
        // 处理最后一个标签和逃逸标记
        if idx == self.commit_queue.len() - 1 {
            tag.t_flags |= JBD2_FLAG_LAST_TAG;
        }
        // 写入标签和数据块
    }
    Ok(true)
}

JBD2 通过预写日志确保操作的原子性,防止文件系统损坏。日志记录了元数据变更,在系统崩溃时可以重放日志恢复一致性。

缓存策略

缓存策略包括按需加载、LRU淘汰和延迟写入:

/// 获取位图(如果不存在则从磁盘加载)
pub fn get_or_load<B: BlockDevice>(
    &mut self,
    block_dev: &mut Jbd2Dev<B>,
    key: CacheKey,
    block_num: u64,
) -> BlockDevResult<&CachedBitmap> {
    if !self.cache.contains_key(&key) {
        if self.cache.len() >= self.max_entries {
            self.evict_lru(block_dev)?;
        }
        block_dev.read_block(block_num as u32)?;
        let buffer = block_dev.buffer();
        let data = buffer.to_vec();
        let bitmap = CachedBitmap::new(data, block_num);
        self.cache.insert(key, bitmap);
    }
    self.access_counter += 1;
    // 更新访问时间戳
    self.cache.get(&key).ok_or(BlockDevError::Corrupted)
}

缓存策略平衡内存使用和I/O性能。

错误处理

错误处理使用Result类型和错误传播:

/// 块设备错误类型
pub enum BlockDevError {
    IoError,
    BufferTooSmall { provided: usize, required: usize },
    InvalidInput,
    Corrupted,
    NoSpace,
    Unsupported,
    WriteError,
}

所有操作返回Result,确保错误被正确处理和传播。

性能优化

性能优化包括连续I/O、批量操作和零拷贝:

  • 连续I/O:extent 树减少随机访问
  • 批量操作:缓存减少磁盘I/O次数
  • 零拷贝:直接操作缓存缓冲区,避免数据拷贝
  • 内存池:复用内存分配,提高效率

这些优化使得 rsext4 在嵌入式和性能敏感场景下具备更好的表现。

3.3 分区识别与文件系统探测

3.3.1 分区识别机制

目前支持两种主流的分区表格式:

GPT(GUID分区表)

GPT是现代系统广泛使用的分区表格式,具有以下特点:

  • 支持128个主分区
  • 使用128位GUID唯一标识分区类型
  • 支持分区名称(UTF-16LE编码)
  • 包含备份分区表
  • 支持CRC32校验

GPT解析过程如下:

  1. 读取LBA 1处的GPT头
  2. 验证签名("EFI PART")
  3. 解析分区项位置和数量
  4. 读取分区项数组
  5. 提取分区信息

3.3.2 分区扫描流程

分区扫描是系统启动时的关键步骤,流程如下:

  1. 初始化块设备:创建Disk对象,获取设备信息
  2. 尝试GPT解析:读取GPT头,验证签名
  3. 解析分区项:读取分区项数组,提取分区信息
  4. 全盘处理:如果都没有分区表,将整个磁盘作为单个分区
  5. 文件系统检测:对每个分区进行文件系统类型检测

3.3.3 分区信息结构

分区信息通过PartitionInfo结构体表示:

pub struct PartitionInfo {
    pub index: u32,                    // 分区索引(0-based)
    pub name: String,                   // 分区名称
    pub partition_type_guid: [u8; 16],  // 分区类型GUID
    pub unique_partition_guid: [u8; 16], // 唯一分区GUID
    pub filesystem_uuid: Option<String>, // 文件系统UUID
    pub starting_lba: u64,              // 起始LBA
    pub ending_lba: u64,                // 结束LBA
    pub size_bytes: u64,                // 分区大小(字节)
    pub filesystem_type: Option<FilesystemType>, // 文件系统类型
}

这种结构设计包含了分区的所有关键信息,便于上层文件系统使用。

3.3.4 文件系统探测机制

文件系统类型检测是通过分析分区开始的特定数据结构实现的,目前支持对fat32和ext4文件系统格式的识别

FAT文件系统检测

FAT文件系统(FAT12/FAT16/FAT32)的检测基于以下特征:

  • FAT12/FAT16:在偏移0x36处有"FAT"签名
  • FAT32:在偏移0x52处有"FAT32"签名
fn is_fat_filesystem(boot_sector: &[u8; 512]) -> bool {
    // 检查FAT12/FAT16/FAT32签名
    if boot_sector.len() >= 0x36 + 3 {
        let fat_sig = &boot_sector[0x36..0x36 + 3];
        if fat_sig == b"FAT" {
            return true;
        }
    }

    if boot_sector.len() >= 0x52 + 5 {
        let fat32_sig = &boot_sector[0x52..0x52 + 5];
        if fat32_sig == b"FAT32" {
            return true;
        }
    }

    false
}

检测过程:

  1. 读取分区开始的512字节(引导扇区)
  2. 检查特定偏移处的签名
  3. 确定FAT类型

ext4文件系统检测

ext4文件系统的检测基于超级块特征:

  • 超级块位于分区偏移1024字节处
  • 魔数0xEF53位于超级块偏移1080字节处
fn is_ext4_filesystem(disk: &mut Disk, start_lba: u64) -> bool {
    // ext4超级块位于分区偏移1024字节处
    let superblock_offset = start_lba * 512 + 1024;
    let mut superblock = [0u8; 2048];

    // 保存当前位置
    let pos = disk.position();

    // 设置位置读取超级块
    disk.set_position(superblock_offset);

    let result = if let Err(_) = read_exact(disk, &mut superblock) {
        warn!("Failed to read ext4 superblock at offset {}", superblock_offset);
        false
    } else {
        // 检查ext4魔数(0xEF53)位于超级块偏移1080字节处
        // 由于我们从1024字节处开始读取,魔数位于索引56处
        if superblock.len() >= 58 {
            let magic = u16::from_le_bytes([superblock[56], superblock[57]]);
            magic == 0xEF53
        } else {
            false
        }
    };

    // 恢复位置
    disk.set_position(pos);

    result
}

检测过程:

  1. 定位到超级块位置
  2. 读取超级块数据
  3. 验证魔数

3.3.5 文件系统UUID读取

为了支持通过UUID挂载文件系统,实现了从文件系统超级块读取UUID的功能:

ext4 UUID读取

ext4的UUID位于超级块偏移0x68处,共16字节。UUID的存储格式为:

  • 前3个字段:小端序
  • 后2个字段:大端序

读取后转换为标准UUID格式(8-4-4-4-12)。

FAT32 UUID读取

FAT32没有标准UUID,但有卷标ID(Volume ID),位于引导扇区偏移0x43处,共4字节。读取后格式化为8字符十六进制字符串。

3.3.6 文件系统创建

对于检测到的文件系统,创建对应的文件系统实例:

  1. 创建分区包装器:将分区包装为文件系统可访问的设备
  2. 初始化文件系统:调用文件系统的初始化函数
  3. 创建根节点:创建文件系统的根目录节点
  4. 注册到VFS:将文件系统注册到虚拟文件系统

3.3.7 根文件系统选择策略

支持多种根文件系统选择策略:

  1. 启动参数指定:通过root=参数指定

    • root=/dev/sdaX:按设备路径指定
    • root=PARTUUID=xxx:按分区GUID指定
    • root=UUID=xxx:按文件系统UUID指定
  2. 默认策略:如果没有指定,使用第一个支持的文件系统分区

  3. 回退策略:如果没有支持的文件系统,使用ramfs作为根文件系统

四、实现阶段

第一阶段:实现磁盘分区识别,通过dtb指定分区作为根文件系统,其他分区系统自动挂载到根文件系统✅

第二阶段:暂时使用lwext4,在qemu中axvisor通过fs启动客户机✅

第三阶段:在硬件平台(rk3568,飞腾派等)调试启动 ✅

第四阶段:RVlwext4完善后,接入axvisor,替换掉lwext4✅

第五阶段:考虑多磁盘的情况,且不依赖arceos的block_dev,同时增加块设备缓存 ✨

Sub-issues

Metadata

Metadata

Assignees

Labels

No labels
No labels

Type

No type

Projects

No projects

Relationships

None yet

Development

No branches or pull requests

Issue actions