11.1. 命令行——输出

本章代码对应 commit :8b2476d3892ea5689420cbb1460210ba29bee6bc

打包程序

创建 usr/rust/src/bin/shell.rs

#![no_std]
#![no_main]

#[macro_use]
extern crate rust;

// IMPORTANT: Must define main() like this
#[no_mangle]
pub fn main() -> i32 {
    println!("Rust user shell");
    loop {}
}

修改 Makefile,将用户程序和 shell 打包成 img :

# in Makefile

usr_path := usr

export SFSIMG = $(usr_path)/rcore32.img


# in usr/Makefile
.PHONY: all clean rust build
all: rust build
build: $(out_img)

$(out_img): rust
    @rcore-fs-fuse $@ $(out_dir) zip

rcore-fs-fuse:
ifeq ($(shell which rcore-fs-fuse),)
    @echo Installing rcore-fs-fuse
    @cargo install rcore-fs-fuse --git https://github.com/rcore-os/rcore-fs --rev c611248
endif

执行 make 生成的 rcore32.img 就是我们的目标文件。

解析文件

rcore32.img 里面目前只包含了 shell ,以后可以添加更多的文件。现在我们需要将其中的 shell 解析出来并且加入到线程中:

// in process/mod.rs

use crate::fs::ROOT_INODE;
use crate::fs::INodeExt;

pub fn kmain() {
    CPU.run();
}

pub fn init() {
    println!("+------ now to initialize process ------+");
    let scheduler = Scheduler::new(1);
    let thread_pool = ThreadPool::new(100, scheduler);
    CPU.init(Thread::new_idle(), Box::new(thread_pool));
    let data = ROOT_INODE
        .lookup("rust/shell")
        .unwrap()
        .read_as_vec()
        .unwrap();
    println!("size of program {:#x}", data.len());
    let user = unsafe{ Thread::new_user(data.as_slice()) };
    CPU.add_thread(user);
}

// in init.rs

use crate::process::{ init as process_init, kmain };
use crate::fs::init as fs_init;

#[no_mangle]
pub extern "C" fn rust_main(hartid: usize, dtb: usize) -> ! {
    interrupt_init();
    println!("Hello RISCV ! in hartid {}, dtb @ {:#x} ", hartid, dtb);
    memory_init(dtb);
    fs_init();
    clock_init();
    process_init();
    kmain();
    loop {}
}

// in lib.rs

mod fs;

这样文件的解析和加载就完成了。看似简单,但是其中最复杂的 fs crate 我们尚未实现。现在我们来填这个坑吧。

文件系统

实现命令行的过程其实包含了实现一个简单的文件系统,创建目录 fs :

// in fs/mod.rs

use lazy_static::*;
use rcore_fs::vfs::*;
use rcore_fs_sfs::SimpleFileSystem;
use alloc::{ sync::Arc, vec::Vec };

mod device;

lazy_static! {
    /// The root of file system
    pub static ref ROOT_INODE: Arc<INode> = {

        let device = {
            extern {
                fn _user_img_start();
                fn _user_img_end();
            }
            // 将存储磁盘文件的内存范围初始化为虚拟磁盘 Membuf
            Arc::new(unsafe { device::MemBuf::new(_user_img_start, _user_img_end) })
        };

        let sfs = SimpleFileSystem::open(device).expect("failed to open SFS");
        sfs.root_inode()
    };
}

pub trait INodeExt {
    fn read_as_vec(&self) -> Result<Vec<u8>>;
}

impl INodeExt for INode {
    fn read_as_vec(&self) -> Result<Vec<u8>> {
        let size = self.metadata()?.size;
        let mut buf = Vec::with_capacity(size);
        unsafe {
            buf.set_len(size);
        }
        self.read_at(0, buf.as_mut_slice())?;
        Ok(buf)
    }
}

pub fn init() {
    // 打印当前目录下的所有项的名字
    let mut id = 0;
    while let Ok(name) = ROOT_INODE.get_entry(id) {
        id += 1;
        println!("{}", name);
    }
}

// in fs/device.rs

use spin::RwLock;
use rcore_fs::dev::*;

pub struct MemBuf(RwLock<&'static mut [u8]>);

impl MemBuf {
    pub unsafe fn new(begin: unsafe extern "C" fn(), end: unsafe extern "C" fn()) -> Self {
        use core::slice;
        MemBuf(RwLock::new(slice::from_raw_parts_mut(
            begin as *mut u8,
            end as usize - begin as usize,
        )))
    }
}

impl Device for MemBuf {
    fn read_at(&self, offset: usize, buf: &mut [u8]) -> Result<usize> {
        let slice = self.0.read();
        let len = buf.len().min(slice.len() - offset);  // 取磁盘剩余长度和 buf 大小的较小值
        buf[..len].copy_from_slice(&slice[offset..offset + len]);
        Ok(len)
    }

    fn write_at(&self, offset: usize, buf: &[u8]) -> Result<usize> {
        let mut slice = self.0.write();
        let len = buf.len().min(slice.len() - offset);
        slice[offset..offset + len].copy_from_slice(&buf[..len]);
        Ok(len)
    }

    fn sync(&self) -> Result<()> {
        Ok(())
    }
}

// in Cargo.toml

rcore-fs = { path = "crate/rcore-fs" }
rcore-fs-sfs = { path = "crate/rcore-fs-sfs" }

// in init.rs

use crate::process::{ init as process_init, kmain };
use crate::fs::init as fs_init;

#[no_mangle]
pub extern "C" fn rust_main(hartid: usize, dtb: usize) -> ! {
    interrupt_init();
    println!("Hello RISCV ! in hartid {}, dtb @ {:#x} ", hartid, dtb);
    memory_init(dtb);
    fs_init();
    clock_init();
    process_init();
    kmain();
    loop {}
}

可以将之前生成的 rcore32.img 理解为一块磁盘,里面包含了我们所需要的程序(ELF 格式)。这里将存储磁盘文件的内存范围初始化为虚拟磁盘(Membuf),然后通过 look_up 函数查找虚拟磁盘中的目标内容的起始位置。找到后利用其元数据(metadata)确定长度,最后将该范围内的 ELF 程序读取,最后加载运行。

执行 make run ,可以看到:

...
tid to alloc: 0
Rust user shell
thread 0 ran just now
thread 0 ran just now
thread 0 ran just now
thread 0 ran just now
thread 0 ran just now
...

此时命令行已经可以正常输出文本了,但是我们目前还不能输入任何信息,而这将是我们下一章的工作。

Last updated