-
Notifications
You must be signed in to change notification settings - Fork 52
Description
一、问题描述
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 需要实现以下功能:
- 读取整个磁盘的分区表 GPT;
- 识别各分区的文件系统格式(ext4、vfat、others);
- 为每个分区选择并建立对应的文件系统;
- 通过设备树指定作为根文件系统的分区
- 精简代码结构,取消不必要的feature和依赖等
二、总体架构
Axvisor 文件系统采用清晰的分层架构,从上到下分为以下几个层次:
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来判断分区或磁盘使用哪种文件系统
依赖模块说明
自主依赖库
-
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 提供了一套完整的文件系统抽象接口,支持文件和目录的创建、读取、写入、删除等基本操作。其核心特性包括:
- 统一的文件系统接口:通过
VfsOpstrait 为不同文件系统提供统一操作接口 - 灵活的节点操作:通过
VfsNodeOpstrait 支持文件和目录的细粒度操作 - 类型安全:利用 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 功能在编译时配置日志级别
- 错误处理:提供详细的错误类型和处理机制
核心模块
-
文件系统核心 (
fs.rs)FileSystem结构体:文件系统的主要接口FatType枚举:定义 FAT 类型(FAT12/FAT16/FAT32)FsStatusFlags结构体:文件系统状态标志FormatVolumeOptions结构体:格式化选项,可自定义文件系统参数- 文件系统初始化和管理功能
-
目录管理 (
dir.rs)Dir结构体:目录操作接口DirRawStream枚举:目录底层数据流DirEditor结构体:目录编辑器,用于修改目录项- 目录遍历、创建和删除功能
-
文件操作 (
file.rs)File结构体:文件操作接口Extent结构体:文件在磁盘上的数据范围FileEditor结构体:文件元数据编辑器- 文件读写、截断和扩展功能
-
目录项管理 (
dir_entry.rs)DirEntry结构体:目录项接口,提供文件和目录的元数据访问DirEntryData枚举:目录项数据,区分文件、目录和长文件名项DirFileEntryData结构体:文件目录项的具体数据DirLfnEntryData结构体:长文件名项的具体数据ShortName结构体:短文件名(8.3 格式)处理- 短文件名和长文件名处理
-
引导扇区 (
boot_sector.rs)BootSector结构体:引导扇区解析,包含文件系统关键信息BiosParameterBlock结构体:BIOS 参数块,包含文件系统几何信息FsInfo结构体:FAT32 文件系统信息结构- 文件系统元数据解析和验证
- 引导代码和签名验证
-
错误处理 (
error.rs)Error枚举:定义各种错误类型- 统一的错误处理机制
- 错误上下文和详细信息
-
I/O 抽象 (
io.rs)IoBasetrait:基础 I/O 操作Read、Write、Seektraits:标准 I/O 接口ReadWriteSeektrait:组合 trait,简化类型签名StdIoWrapper结构体:标准 I/O 类型的包装器IntoStoragetrait:类型转换 trait,简化 API 使用
-
时间管理 (
time.rs)TimeProvidertrait:时间提供者接口,支持自定义时间源DateTime、Date、Time结构体:时间表示,与 FAT 时间格式兼容DefaultTimeProvider结构体:默认时间提供者ChronoTimeProvider结构体:基于 chrono 库的时间提供者NullTimeProvider结构体:空时间提供者,用于测试或无时间环境- 时间格式转换和时区处理.
-
FAT 表管理 (
table.rs)ClusterIterator结构体:簇链迭代器,用于遍历文件占用的所有簇- FAT 表读写操作
- 簇分配和释放算法
- 坏簇检测和处理
- FAT 表缓存和优化
高级功能
- 文件系统格式化
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)?;- 时间戳管理
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();- 文件扩展信息
// 获取文件在磁盘上的物理位置
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);
}- 文件系统状态检查
// 检查文件系统状态
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);- 文件属性操作
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 操作次数。
缓存层次结构:
- 应用层缓存:使用
BufStream缓冲 I/O 操作 - FAT 表缓存:缓存频繁访问的 FAT 表项
- 目录项缓存:缓存最近访问的目录项
- 数据缓存:缓存文件数据块
缓存一致性:
- 实现了写回策略确保数据一致性
- 支持强制刷新机制
- 处理缓存失效和更新
3.2.2 ext4文件系统
主要特性
- 完整 Ext4 实现:支持 Ext4 文件系统的所有核心特性,包括动态 inode 大小、extent 树、块组管理等
- Extent 树支持:使用 extent 树替代传统块映射,提高大文件存储效率,支持连续块分配
- 日志系统 (JBD2):实现 Journaling Block Device 2,支持 ordered 模式,保证文件系统一致性
- 块设备抽象:通过
BlockDevicetrait 抽象底层存储,提供统一的块读写接口 - 多级缓存系统:包括位图缓存、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 定义了块设备的基本操作接口,通过抽象层屏蔽了底层存储的差异。read 和 write 方法支持多块连续操作,提高了 I/O 效率。open 和 close 方法允许设备进行初始化和清理工作。total_blocks 和 block_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解析过程如下:
- 读取LBA 1处的GPT头
- 验证签名("EFI PART")
- 解析分区项位置和数量
- 读取分区项数组
- 提取分区信息
3.3.2 分区扫描流程
分区扫描是系统启动时的关键步骤,流程如下:
- 初始化块设备:创建
Disk对象,获取设备信息 - 尝试GPT解析:读取GPT头,验证签名
- 解析分区项:读取分区项数组,提取分区信息
- 全盘处理:如果都没有分区表,将整个磁盘作为单个分区
- 文件系统检测:对每个分区进行文件系统类型检测
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
}检测过程:
- 读取分区开始的512字节(引导扇区)
- 检查特定偏移处的签名
- 确定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
}检测过程:
- 定位到超级块位置
- 读取超级块数据
- 验证魔数
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 文件系统创建
对于检测到的文件系统,创建对应的文件系统实例:
- 创建分区包装器:将分区包装为文件系统可访问的设备
- 初始化文件系统:调用文件系统的初始化函数
- 创建根节点:创建文件系统的根目录节点
- 注册到VFS:将文件系统注册到虚拟文件系统
3.3.7 根文件系统选择策略
支持多种根文件系统选择策略:
-
启动参数指定:通过
root=参数指定root=/dev/sdaX:按设备路径指定root=PARTUUID=xxx:按分区GUID指定root=UUID=xxx:按文件系统UUID指定
-
默认策略:如果没有指定,使用第一个支持的文件系统分区
-
回退策略:如果没有支持的文件系统,使用ramfs作为根文件系统
四、实现阶段
第一阶段:实现磁盘分区识别,通过dtb指定分区作为根文件系统,其他分区系统自动挂载到根文件系统✅
第二阶段:暂时使用lwext4,在qemu中axvisor通过fs启动客户机✅
第三阶段:在硬件平台(rk3568,飞腾派等)调试启动 ✅
第四阶段:RVlwext4完善后,接入axvisor,替换掉lwext4✅
第五阶段:考虑多磁盘的情况,且不依赖arceos的block_dev,同时增加块设备缓存 ✨