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块
- 一些功能完善/优化
- 有时间的情况下可以试试实现网络协议栈