sOS0x00

2025年11月16日 · 3002 字 · 6 分钟

moss-kernel主要数据结构相关代码概览以及想实现的东西,列给这个记忆力不好的入参考

架构

Moss的硬件抽象层(HAL)设计的非常清晰,严格遵循了接口和架构分离模式,因此迁移到不同的架构上会较容易,只需要重写HAL部分的代码即可

moss把核心逻辑都抽象到libkernel库中,arch目录下只包含与硬件相关的代码,比如中断处理、异常处理、上下文切换等

.
├── Cargo.toml
└── src
    ├── arch 硬件接口契约
    │   ├── arm64
    │   └── mod.rs
    ├── driver.rs
    ├── error
    │   └── syscall_error.rs
    ├── error.rs
    ├── fs 虚拟文件系统VFS的接口
    │   ├── attr.rs
    │   ├── blk
    │   ├── filesystems
    │   ├── mod.rs
    │   ├── pathbuf.rs
    │   └── path.rs
    ├── lib.rs
    ├── memory 定义内存操作的基本单位和类型
    │   ├── address.rs
    │   ├── kbuf.rs
    │   ├── mod.rs
    │   ├── page_alloc.rs
    │   ├── page.rs
    │   ├── permissions.rs
    │   ├── pg_offset.rs
    │   ├── proc_vm
    │   ├── region.rs
    │   └── smalloc.rs
    ├── pod.rs
    ├── proc 进程元数据
    │   ├── ids.rs Pid(进程ID) Tid(线程ID) Uid(用户ID) Gid(组ID)
    │   └── mod.rs
    └── sync
        ├── condvar.rs
        ├── mod.rs
        ├── mpsc.rs
        ├── mutex.rs 互斥锁
        ├── once_lock.rs
        ├── per_cpu.rs
        ├── spinlock.rs 自旋锁
        └── waker_set.rs

12 directories, 30 files

内存管理

moss的内存管理设计特点:

  • MMU与页表管理:开启MMU,使用多级页表进行虚拟内存管理,支持地址空间隔离和内存保护
  • Copy-on-Write (COW):通过COW技术优化fork(),减少不必要的内存复制
  • 用户态/内核态安全拷贝:提供copy_to_user/copy_from_user的async版本,防止内核直接访问用户地址
pub unsafe trait UserCopyable: Copy {}
impl_user_copyable_for_primitives! { u8, i32, ... }
// 这个marker trait限制了拷贝只能操作纯数据类型,编译器会阻止开发者尝试将包含内核指针、互斥锁等复杂结构的类型拷贝给用户态,从根源上消除了内核对象泄漏的安全隐患
  • Buddy Allocator:使用伙伴算法进行物理内存分配,支持高效的内存块分配和回收
  • smalloc:实现小内存分配器,优化小块内存的分配性能
pub struct Smalloc<T: AddressTranslator<()>> {
    pub memory: RegionList, // 可用物理内存段列表
    pub res: RegionList,    // 已占用/保留内存段列表
    // ...
    /// 维护两个列表以跟踪可用和已占用的内存区域,确保分配和释放操作的正确性,分配逻辑:遍历 memory 列表,从中减去 res 列表中的区域,找到空隙进行分配,自我生长,当用于存储列表的数组满时,递归调用自己分配更大的内存来扩容列表本身,解决了内核早期元数据存储空间不足的问题
}
  • 异步缺页处理:Linux处理缺页读取磁盘时,会把当前线程设为 TASK_UNINTERRUPTIBLE然后死等,moss则是返回一个Future,异常处理程序拿到这个 Future -> 挂起当前线程 -> 扔进 Executor -> 调度其他线程 -> 磁盘中断回来 -> 唤醒 Future -> 映射内存 -> 恢复原线程

异步内核

  • moss内核中的许多子系统和功能都采用了async/await语法进行实现,把所有非trivial的syscall都写成async fn,利用编译器生成的状态机来处理阻塞和唤醒逻辑,解决了一些逻辑死锁

进程管理

目前实现的有关syscall包括:

  • fork/clone:创建进程/线程,moss支持完整的clone()参数,意味着可以创建轻量级线程(类似pthread)
  • execve:加载并执行新程序
  • wait4:父进程等待子进程退出
  • signal:支持发送和处理 SIGTERM、SIGKILL 等

文件系统

moss实现了一个虚拟文件系统层,这是支持多种文件系统的前提

  • 虚拟文件系统(VFS):提供统一的文件操作接口

已有驱动:

  • Ramdisk:内存中的块设备,启动时把rootfs打包进去
  • FAT32(只读):能挂载 SD 卡或镜像里的 FAT32 分区
  • devtmpfs:动态生成 /dev 下的设备节点(比如 /dev/ttyS0)

架构概述

应用层
   ↓
File (有状态的文件句柄)
   ↓
Inode (无状态的文件系统对象)
   ↓
Filesystem (文件系统实例)
   ↓
BlockBuffer (字节级缓冲层)
   ↓
BlockDevice (块设备抽象)
   ↓
硬件设备

核心数据结构

struct InodeId(u64, u64)  // (文件系统ID, inode编号)
  • 类似linux的(dev_t, ino_t)组合,唯一标识一个文件系统内的文件,确保跨文件系统的唯一性
bitflags! {
    const O_RDONLY    = 0b000;  // 只读
    const O_WRONLY    = 0b001;  // 只写
    const O_RDWR      = 0b010;  // 读写
    const O_CREAT     = 0o100;  // 创建文件
    const O_TRUNC     = 0o1000; // 截断文件
    // ...
}
  • 文件打开标志,类似POSIX标准
enum FileType {
    File,           // 普通文件
    Directory,      // 目录
    Symlink,        // 符号链接
    BlockDevice,    // 块设备
    CharDevice,     // 字符设备
    Fifo,           // 命名管道
    Socket,         // 套接字
}
  • 统一抽象不同类型的文件系统对象
trait BlockDevice {
    async fn read(&self, block_id: u64, buf: &mut [u8]) -> Result<()>;
    async fn write(&self, block_id: u64, buf: &[u8]) -> Result<()>;
    fn block_size(&self) -> usize;
    async fn sync(&self) -> Result<()>;
}
  • 块对齐要求,**block_size()**返回设备的块大小,读写操作必须以块为单位进行
  • 异步读写,支持DMA等高性能I/O操作
impl RamdiskBlkDev {
    fn new(region: PhysMemoryRegion, base: VA) -> Result<Self> {
        // 将物理内存映射到内核虚拟地址空间
        kern_addr_spc.map_normal(region, ...);
        // 直接内存访问,无需实际 I/O
    }
}
pub struct BlockBuffer {
    dev: Box<dyn BlockDevice>,
    block_size: usize,
}
  • 块缓冲层,提供按字节读写的接口,内部进行块对齐处理
async fn read_at(&self, offset: u64, buf: &mut [u8]) -> Result<()> {
    // 1. 计算涉及的块范围
    let start_block = offset / block_size;
    let end_block = (offset + len - 1) / block_size;
    
    // 2. 读取所有涉及的块到临时缓冲区
    let mut temp_buf = vec![0; num_blocks * block_size];
    self.dev.read(start_block, &mut temp_buf).await?;
    
    // 3. 提取所需字节
    buf.copy_from_slice(&temp_buf[start_offset..end_offset]);
}
  • 非对齐访问处理
  • 读-修改-写:write_at 先读取完整块,修改部分字节,再写回
trait Filesystem {
    async fn root_inode(&self) -> Result<Arc<dyn Inode>>;
    fn id(&self) -> u64;
    async fn sync(&self) -> Result<()>;
}
  • 创建根Inode
  • 唯一文件系统ID
  • 同步元数据到存储设备
trait Inode {
    fn id(&self) -> InodeId;
    async fn read_at(&self, offset: u64, buf: &mut [u8]) -> Result<usize>;
    async fn write_at(&self, offset: u64, buf: &[u8]) -> Result<usize>;
    async fn lookup(&self, name: &str) -> Result<Arc<dyn Inode>>;
    async fn create(&self, name: &str, ...) -> Result<Arc<dyn Inode>>;
    async fn readdir(&self, start_offset: u64) -> Result<Box<dyn DirStream>>;
}
  • 显示传递offset,支持多线程读写
  • 目录操作:lookup、create、readdir
  • 异步接口,避免阻塞内核线程
pub struct Fat32Filesystem {
    dev: BlockBuffer,              // 底层块设备
    bpb: BiosParameterBlock,       // 启动参数块(元数据)
    fat: Fat,                      // 文件分配表
    id: u64,                       // 文件系统实例ID
    this: Weak<Self>,              // 弱引用,避免循环引用
}
struct Sector(u32);   // 扇区号
struct Cluster(u32);  // 簇号

impl Cluster {
    fn from_high_low(high: u16, low: u16) -> Self {
        // FAT32 目录项存储为两个 16 位字段
        Cluster((high as u32) << 16 | low as u32)
    }
}
async fn new(dev: BlockBuffer, id: u64) -> Result<Arc<Self>> {
    // 1. 读取启动扇区,解析 BPB
    let bpb = BiosParameterBlock::new(&dev).await?;
    
    // 2. 读取主 FAT 表
    let fat = Fat::read_fat(&dev, &bpb, 0).await?;
    
    // 3. 验证所有 FAT 副本一致性
    for fat_num in 1..bpb.num_fats {
        let other_fat = Fat::read_fat(&dev, &bpb, fat_num).await?;
        if other_fat != fat {
            return Err(FsError::InvalidFs.into());
        }
    }
    
    // 4. 使用 Arc::new_cyclic 解决循环引用
    Ok(Arc::new_cyclic(|weak| Self { this: weak.clone(), ... }))
}
  • 读取并解析FAT32文件系统的关键元数据
  • 使用Arc::new_cyclic允许构造时获取自身的弱引用,用于Inode与Filesystem之间的关联,避免循环引用导致内存泄漏
trait Fat32Operations {
    async fn read_sector(&self, sector: Sector, offset: usize, buf: &mut [u8]) 
        -> Result<usize>;
    fn cluster_to_sectors(&self, cluster: Cluster) 
        -> Result<impl Iterator<Item = Sector>>;
    fn iter_clusters(&self, root: Cluster) 
        -> impl Iterator<Item = Result<Cluster>>;
}
async fn read_sector(&self, sector: Sector, offset: usize, buf: &mut [u8]) 
    -> Result<usize> 
{
    let bytes_left = sector_size - offset;
    let read_sz = min(buf.len(), bytes_left);
    
    // 转换为字节偏移并读取
    self.dev.read_at(
        self.bpb.sector_offset(sector) + offset as u64,
        &mut buf[..read_sz]
    ).await?;
}
  • 支持从扇区内任意偏移读取数据
  • 边界检查,防止跨扇区读取
  • 懒加载簇链:通过迭代器按需读取文件数据,减少内存占用
trait Driver {
    fn name(&self) -> &'static str;
    fn as_interrupt_manager(self: Arc<Self>) -> Option<...>;
    fn as_filesystem_driver(self: Arc<Self>) -> Option<...>;
}

trait CharDriver {
    fn get_device(&self, minor: u64) -> Option<Arc<dyn OpenableDevice>>;
}

trait OpenableDevice {
    fn open(&self, flags: OpenFlags) -> Result<Arc<OpenFile>>;
}
  • 主次设备号,主设备号标识驱动类型,次设备号标识具体设备实例
  • 类型转换,实现向下转换为特定驱动接口
pub struct DriverManager {
    active_drivers: Vec<Arc<dyn Driver>>,
    char_drivers: BTreeMap<u64, Arc<dyn CharDriver>>,  // major -> driver
}
fn register_char_driver(&mut self, major: u64, driver: Arc<dyn CharDriver>) 
    -> Result<()> 
{
    match self.char_drivers.entry(major) {
        Entry::Vacant(e) => { e.insert(driver); Ok(()) }
        Entry::Occupied(_) => Err(KernelError::InUse),
    }
}
  • 驱动管理器,维护已注册驱动列表
  • 注册字符设备驱动,确保主设备号唯一
  • 类似于linux的cdev_add
struct ConsoleDev {}

impl OpenableDevice for ConsoleDev {
    fn open(&self, flags: OpenFlags) -> Result<Arc<OpenFile>> {
        // 1. 获取实际控制台设备描述符
        let char_dev_desc = match *CONSOLE.lock_save_irq() {
            ConsoleState::Device(_, desc) => desc,
            _ => return Err(FsError::NoDevice.into()),
        };
        
        // 2. 查找对应的字符驱动
        let driver = DM.lock_save_irq()
            .find_char_driver(char_dev_desc.major)
            .ok_or(FsError::NoDevice)?;
        
        // 3. 打开具体设备
        driver.get_device(char_dev_desc.minor)?.open(flags)
    }
}
  • /dev/console 伪设备实现
  • 实际指向具体控制台设备(如串口、显示器等)
  • 通过主次设备号查找对应驱动并打开
impl OpenableDevice for TtyDev {
    fn open(&self, _args: OpenFlags) -> Result<Arc<OpenFile>> {
        // 返回当前进程的控制终端(通常是 fd 0)
        current_task().fd_table.lock_save_irq().get(Fd(0))
    }
}
  • /dev/tty 总是指向当前进程的控制终端,实现了进程级别的终端抽象

想实现的东西

  • 首先是readme想实现的ext2/4文件系统驱动
  • 架构支持for RISC-V、x86
  • 沙箱
  • 重写/完善一些unsafe块
  • 一些功能完善/优化
  • 有时间的情况下可以试试实现网络协议栈