From 731d6319e43985ca0e9a616ab4ed49230aaa325e Mon Sep 17 00:00:00 2001 From: WangRunji Date: Fri, 18 May 2018 11:49:27 +0800 Subject: [PATCH] Can load user programs from sfs.img (hard linked). --- Cargo.toml | 1 + Makefile | 3 +++ src/fs.rs | 50 +++++++++++++++++++++++++++++++++++++ src/lib.rs | 4 +++ src/process/mod.rs | 52 +++++++++++++++------------------------ src/process/process.rs | 28 ++++++++++----------- src/process/processor.rs | 19 +++++++------- user/hello.o | Bin 40914 -> 0 bytes 8 files changed, 102 insertions(+), 55 deletions(-) create mode 100644 src/fs.rs delete mode 100644 user/hello.o diff --git a/Cargo.toml b/Cargo.toml index ba3cd0d..a27d98d 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -25,6 +25,7 @@ linked_list_allocator = "0.5.0" redox_syscall = "0.1.37" xmas-elf = "0.6" arrayvec = { version = "0.4.7", default-features = false } +simple-filesystem = { path = "../simple-filesystem" } [build-dependencies] cc = "1.0" diff --git a/Makefile b/Makefile index f3c31ae..9db3fe0 100644 --- a/Makefile +++ b/Makefile @@ -59,6 +59,9 @@ clean: run: $(iso) @qemu-system-$(arch) $(qemu_opts) || [ $$? -eq 11 ] # run qemu and assert it exit 11 +debug: $(iso) + @qemu-system-$(arch) $(qemu_opts) -s -S & + iso: $(iso) build: iso diff --git a/src/fs.rs b/src/fs.rs new file mode 100644 index 0000000..e53a2d4 --- /dev/null +++ b/src/fs.rs @@ -0,0 +1,50 @@ +use simple_filesystem::*; +use alloc::boxed::Box; +use process; + +extern { + fn _binary_user_sfs_img_start(); + fn _binary_user_sfs_img_end(); + fn _binary_user_forktest_start(); + fn _binary_user_forktest_end(); +} + +struct MemBuf(&'static [u8]); + +impl MemBuf { + unsafe fn new(begin: unsafe extern fn(), end: unsafe extern fn()) -> Self { + use core::slice; + MemBuf(slice::from_raw_parts(begin as *const u8, end as usize - begin as usize)) + } +} + +impl Device for MemBuf { + fn read_at(&mut self, offset: usize, buf: &mut [u8]) -> Option { + let slice = self.0; + let len = buf.len().min(slice.len() - offset); + buf[..len].copy_from_slice(&slice[offset..offset + len]); + Some(len) + } + fn write_at(&mut self, offset: usize, buf: &[u8]) -> Option { + None + } +} + +pub fn load_sfs() { + let slice = unsafe { MemBuf::new(_binary_user_sfs_img_start, _binary_user_sfs_img_end) }; + let sfs = SimpleFileSystem::open(Box::new(slice)).unwrap(); + let root = sfs.root_inode(); + let files = root.borrow().list().unwrap(); + debug!("Loading programs: {:?}", files); + + for name in files.iter().filter(|&f| f != "." && f != "..") { + static mut BUF: [u8; 64 << 12] = [0; 64 << 12]; + let file = root.borrow().lookup(name.as_str()).unwrap(); + let len = file.borrow().read_at(0, unsafe { &mut BUF }).unwrap(); + process::add_user_process(name.as_str(), unsafe { &BUF[..len] }); + } + + process::add_user_process("forktest", unsafe { MemBuf::new(_binary_user_forktest_start, _binary_user_forktest_end).0 }); + + process::print(); +} \ No newline at end of file diff --git a/src/lib.rs b/src/lib.rs index 919c604..181b54b 100644 --- a/src/lib.rs +++ b/src/lib.rs @@ -34,6 +34,7 @@ extern crate bit_field; extern crate syscall as redox_syscall; extern crate xmas_elf; extern crate arrayvec; +extern crate simple_filesystem; #[macro_use] // print! mod io; @@ -45,6 +46,7 @@ mod macros; mod consts; mod process; mod syscall; +mod fs; #[allow(dead_code)] #[cfg(target_arch = "x86_64")] @@ -81,6 +83,8 @@ pub extern "C" fn rust_main(multiboot_information_address: usize) -> ! { // arch::smp::start_other_cores(&acpi, &mut memory_controller); process::init(memory_controller); + fs::load_sfs(); + unsafe{ arch::interrupt::enable(); } // 直接进入用户态暂不可用:内核代码用户不可访问 diff --git a/src/process/mod.rs b/src/process/mod.rs index a838c6b..15bb749 100644 --- a/src/process/mod.rs +++ b/src/process/mod.rs @@ -1,6 +1,7 @@ use memory::MemoryController; use spin::{Once, Mutex}; use core::slice; +use alloc::String; use self::process::*; use self::processor::*; @@ -15,48 +16,20 @@ mod processor; /// * Debug: 用于Debug输出 use arch::interrupt::TrapFrame; -// TODO: 使用宏来更优雅地导入符号,现在会有编译错误 -// -// #![feature(concat_idents)] -// -// macro_rules! binary_symbol { -// ($name: ident) => { -// extern { -// fn concat_idents!(_binary_user_, $name, _start)(); -// fn concat_idents!(_binary_user_, $name, _end)(); -// } -// }; -// } -// -// binary_symbol!(forktest); - -#[cfg(feature = "link_user_program")] -extern { - fn _binary_user_forktest_start(); - fn _binary_user_forktest_end(); - fn _binary_hello_start(); - fn _binary_hello_end(); -} - - pub fn init(mut mc: MemoryController) { PROCESSOR.call_once(|| {Mutex::new({ let initproc = Process::new_init(&mut mc); let idleproc = Process::new("idle", idle_thread, &mut mc); - #[cfg(feature = "link_user_program")] -// let forktest = Process::new_user(_binary_user_forktest_start as usize, -// _binary_user_forktest_end as usize, &mut mc); - let hello = Process::new_user(_binary_hello_start as usize, - _binary_hello_end as usize, &mut mc); - let mut processor = Processor::new(mc); + let mut processor = Processor::new(); processor.add(initproc); processor.add(idleproc); - processor.add(hello); processor })}); + MC.call_once(|| Mutex::new(mc)); } static PROCESSOR: Once> = Once::new(); +static MC: Once> = Once::new(); /// Called by timer handler in arch /// 设置rsp,指向接下来要执行线程的 内核栈顶 @@ -77,7 +50,10 @@ extern fn idle_thread() { /// Fork the current process pub fn sys_fork(tf: &TrapFrame) -> i32 { - PROCESSOR.try().unwrap().lock().fork(tf); + let mut processor = PROCESSOR.try().unwrap().lock(); + let mut mc = MC.try().unwrap().lock(); + let new = processor.current().fork(tf, &mut mc); + processor.add(new); 0 } @@ -99,4 +75,16 @@ pub fn sys_exit(rsp: &mut usize, error_code: usize) -> i32 { processor.schedule(rsp); processor.exit(pid, error_code); 0 +} + +pub fn add_user_process(name: &str, data: &[u8]) { + let mut processor = PROCESSOR.try().unwrap().lock(); + let mut mc = MC.try().unwrap().lock(); + let mut new = Process::new_user(data, &mut mc); + new.name = String::from(name); + processor.add(new); +} + +pub fn print() { + debug!("{:#x?}", *PROCESSOR.try().unwrap().lock()); } \ No newline at end of file diff --git a/src/process/process.rs b/src/process/process.rs index 03291ef..7dd18d6 100644 --- a/src/process/process.rs +++ b/src/process/process.rs @@ -2,13 +2,12 @@ use super::*; use memory::{self, Stack, InactivePageTable}; use xmas_elf::{ElfFile, program::{Flags, ProgramHeader}, header::HeaderPt2}; use core::slice; -use alloc::rc::Rc; -use rlibc::memcpy; +use alloc::{rc::Rc, String}; #[derive(Debug)] pub struct Process { pub(in process) pid: Pid, - name: &'static str, + pub(in process) name: String, kstack: Stack, pub(in process) memory_set: Option, pub(in process) page_table: Option, @@ -26,14 +25,14 @@ pub enum Status { impl Process { /// Make a new kernel thread - pub fn new(name: &'static str, entry: extern fn(), mc: &mut MemoryController) -> Self { + pub fn new(name: &str, entry: extern fn(), mc: &mut MemoryController) -> Self { let kstack = mc.alloc_stack(7).unwrap(); let tf = TrapFrame::new_kernel_thread(entry, kstack.top()); let rsp = kstack.push_at_top(tf); Process { pid: 0, - name, + name: String::from(name), kstack, memory_set: None, page_table: None, @@ -49,7 +48,7 @@ impl Process { assert_has_not_been_called!(); Process { pid: 0, - name: "init", + name: String::from("init"), kstack: mc.kernel_stack.take().unwrap(), memory_set: None, page_table: None, @@ -62,10 +61,10 @@ impl Process { /// Make a new user thread /// The program elf data is placed at [begin, end) /// uCore x86 32bit program is planned to be supported. - pub fn new_user(begin: usize, end: usize, mc: &mut MemoryController) -> Self { + pub fn new_user(data: &[u8], mc: &mut MemoryController) -> Self { // Parse elf - let slice = unsafe{ slice::from_raw_parts(begin as *const u8, end - begin) }; - let elf = ElfFile::new(slice).expect("failed to read elf"); + let begin = data.as_ptr() as usize; + let elf = ElfFile::new(data).expect("failed to read elf"); let is32 = match elf.header.pt2 { HeaderPt2::Header32(_) => true, HeaderPt2::Header64(_) => false, @@ -83,7 +82,7 @@ impl Process { memory_set.push(MemoryArea::new(user_stack_buttom, user_stack_top, EntryFlags::WRITABLE | EntryFlags::NO_EXECUTE | EntryFlags::USER_ACCESSIBLE, "user_stack")); let page_table = mc.make_page_table(&memory_set); - debug!("{:#x?}", memory_set); +// debug!("{:#x?}", memory_set); let entry_addr = match elf.header.pt2 { HeaderPt2::Header32(header) => header.entry_point as usize, @@ -97,7 +96,8 @@ impl Process { ProgramHeader::Ph32(ph) => (ph.virtual_addr as usize, ph.offset as usize, ph.file_size as usize), ProgramHeader::Ph64(ph) => (ph.virtual_addr as usize, ph.offset as usize, ph.file_size as usize), }; - unsafe { memcpy(virt_addr as *mut u8, (begin + offset) as *mut u8, file_size) }; + let target = unsafe { slice::from_raw_parts_mut(virt_addr as *mut u8, file_size) }; + target.copy_from_slice(&data[offset..offset + file_size]); } if is32 { unsafe { @@ -114,11 +114,11 @@ impl Process { let kstack = mc.alloc_stack(7).unwrap(); let tf = TrapFrame::new_user_thread(entry_addr, user_stack_top, is32); let rsp = kstack.push_at_top(tf); - debug!("rsp = {:#x}", rsp); +// debug!("rsp = {:#x}", rsp); Process { pid: 0, - name: "user", + name: String::new(), kstack, memory_set: Some(memory_set), page_table: Some(page_table), @@ -157,7 +157,7 @@ impl Process { Process { pid: 0, - name: "fork", + name: self.name.clone() + "_fork", kstack, memory_set: Some(memory_set), page_table: Some(page_table), diff --git a/src/process/processor.rs b/src/process/processor.rs index 90cb017..93218fa 100644 --- a/src/process/processor.rs +++ b/src/process/processor.rs @@ -2,17 +2,16 @@ use alloc::BTreeMap; use memory::{ActivePageTable, InactivePageTable}; use super::*; use core::cell::RefCell; +use core::fmt::{Debug, Formatter, Error}; pub struct Processor { - mc: RefCell, procs: BTreeMap, current_pid: Pid, } impl Processor { - pub fn new(mc: MemoryController) -> Self { + pub fn new() -> Self { Processor { - mc: RefCell::new(mc), procs: BTreeMap::::new(), current_pid: 0, } @@ -85,12 +84,6 @@ impl Processor { self.get(self.current_pid) } - /// Fork the current process - pub fn fork(&mut self, tf: &TrapFrame) { - let new = self.current().fork(tf, &mut self.mc.borrow_mut()); - self.add(new); - } - pub fn kill(&mut self, pid: Pid) { let process = self.procs.get_mut(&pid).unwrap(); process.status = Status::Exited; @@ -101,4 +94,12 @@ impl Processor { debug!("Processor: {} exit, code: {}", pid, error_code); self.kill(pid); } +} + +impl Debug for Processor { + fn fmt(&self, f: &mut Formatter) -> Result<(), Error> { + f.debug_map() + .entries(self.procs.iter().map(|(pid, proc0)| { (pid, &proc0.name) })) + .finish() + } } \ No newline at end of file diff --git a/user/hello.o b/user/hello.o deleted file mode 100644 index 4f1d646bdf406431af15f6782e00c2ef1929cd17..0000000000000000000000000000000000000000 GIT binary patch literal 0 HcmV?d00001 literal 40914 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 I@DxS;A1vN`GXMYp