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