From 37a78ad8ae83a4c67b6c2f762d68db588eed725a Mon Sep 17 00:00:00 2001 From: Mark Poliakov Date: Thu, 4 Nov 2021 11:26:15 +0200 Subject: [PATCH] feature: fork() --- init/src/main.rs | 63 ++++++++++++++++++---------- kernel/src/arch/aarch64/context.S | 42 +++++++++++++++++++ kernel/src/arch/aarch64/context.rs | 52 +++++++++++++++++++++++ kernel/src/arch/aarch64/exception.rs | 24 ++++++++--- kernel/src/mem/phys/manager.rs | 36 +++++++++++++--- kernel/src/mem/phys/mod.rs | 5 +++ kernel/src/mem/virt/table.rs | 46 ++++++++++++++++++++ kernel/src/proc/process.rs | 40 ++++++++++++++++++ kernel/src/syscall.rs | 9 +++- syscall/src/abi.rs | 1 + syscall/src/calls.rs | 10 +++++ 11 files changed, 292 insertions(+), 36 deletions(-) diff --git a/init/src/main.rs b/init/src/main.rs index 6a51a6e..0035ef2 100644 --- a/init/src/main.rs +++ b/init/src/main.rs @@ -5,37 +5,54 @@ #[macro_use] extern crate libusr; -use libusr::sys::{OpenFlags, AT_FDCWD}; use libusr::io; +use libusr::sys::{OpenFlags, AT_FDCWD}; #[no_mangle] fn main() -> i32 { - let mut buf = [0; 128]; - - print!("\x1B[2J\x1B[1;1H"); - println!("Hello!"); - loop { - print!("> "); + let pid = unsafe { libusr::sys::sys_fork() }; - let count = unsafe { - libusr::sys::sys_read(0, buf.as_mut_ptr(), buf.len()) - }; - if count < 0 { - trace!("Read from stdio failed"); - break; - } - let count = count as usize; - - if let Ok(s) = core::str::from_utf8(&buf[..count]) { - println!("Got string {:?}", s); - - if s == "quit" { - break; + if pid == 0 { + trace!("Hello!"); + unsafe { + libusr::sys::sys_ex_nanosleep(3_000_000_000, core::ptr::null_mut()); } + trace!("Exiting"); + return 0; } else { - println!("Got string (non-utf8) {:?}", &buf[..count]); + trace!("Spawned {}", pid); + unsafe { + libusr::sys::sys_ex_nanosleep(5_000_000_000, core::ptr::null_mut()); + } } } - -1 + //let mut buf = [0; 128]; + + // print!("\x1B[2J\x1B[1;1H"); + // println!("Hello!"); + + // loop { + // print!("> "); + + // let count = unsafe { + // libusr::sys::sys_read(0, buf.as_mut_ptr(), buf.len()) + // }; + // if count < 0 { + // trace!("Read from stdio failed"); + // break; + // } + // let count = count as usize; + + // if let Ok(s) = core::str::from_utf8(&buf[..count]) { + // println!("Got string {:?}", s); + + // if s == "quit" { + // break; + // } + // } else { + // println!("Got string (non-utf8) {:?}", &buf[..count]); + // } + // } + // -1 } diff --git a/kernel/src/arch/aarch64/context.S b/kernel/src/arch/aarch64/context.S index 1a3f16e..b022526 100644 --- a/kernel/src/arch/aarch64/context.S +++ b/kernel/src/arch/aarch64/context.S @@ -2,6 +2,7 @@ .global __aa64_ctx_switch .global __aa64_ctx_switch_to .global __aa64_ctx_enter_kernel +.global __aa64_ctx_enter_from_fork .set PT_REGS_SIZE, 16 * 7 @@ -30,6 +31,47 @@ __aa64_ctx_enter_kernel: mov x1, xzr eret +__aa64_ctx_enter_from_fork: + // stack.push(frame.x[18]); + // stack.push(frame.x[17]); + // stack.push(frame.x[16]); + // stack.push(frame.x[15]); + // stack.push(frame.x[14]); + // stack.push(frame.x[13]); + // stack.push(frame.x[12]); + // stack.push(frame.x[11]); + // stack.push(frame.x[10]); + // stack.push(frame.x[9]); + // stack.push(frame.x[8]); + // stack.push(frame.x[7]); + // stack.push(frame.x[6]); + // stack.push(frame.x[5]); + // stack.push(frame.x[4]); + // stack.push(frame.x[3]); + // stack.push(frame.x[2]); + // stack.push(frame.x[1]); + + // stack.push(frame.elr_el1 as usize); + // stack.push(frame.sp_el0 as usize); + ldp x0, x1, [sp, #16 * 0] + msr sp_el0, x0 + msr elr_el1, x1 + msr spsr_el1, xzr + + ldp x1, x2, [sp, #16 * 1] + ldp x3, x4, [sp, #16 * 2] + ldp x5, x6, [sp, #16 * 3] + ldp x7, x8, [sp, #16 * 4] + ldp x9, x10, [sp, #16 * 5] + ldp x11, x12, [sp, #16 * 6] + ldp x13, x14, [sp, #16 * 7] + ldp x15, x16, [sp, #16 * 8] + ldp x17, x18, [sp, #16 * 9] + + mov x0, xzr + + eret + __aa64_ctx_switch: sub sp, sp, #PT_REGS_SIZE diff --git a/kernel/src/arch/aarch64/context.rs b/kernel/src/arch/aarch64/context.rs index d396314..94b868d 100644 --- a/kernel/src/arch/aarch64/context.rs +++ b/kernel/src/arch/aarch64/context.rs @@ -4,6 +4,7 @@ use crate::mem::{ self, phys::{self, PageUsage}, }; +use crate::arch::aarch64::exception::ExceptionFrame; use core::mem::size_of; struct Stack { @@ -39,6 +40,56 @@ impl Context { } } + /// + pub fn fork(frame: &ExceptionFrame, ttbr0: usize) -> Self { + let mut stack = Stack::new(8); + + stack.push(frame.x[18]); + stack.push(frame.x[17]); + stack.push(frame.x[16]); + stack.push(frame.x[15]); + stack.push(frame.x[14]); + stack.push(frame.x[13]); + stack.push(frame.x[12]); + stack.push(frame.x[11]); + stack.push(frame.x[10]); + stack.push(frame.x[9]); + stack.push(frame.x[8]); + stack.push(frame.x[7]); + stack.push(frame.x[6]); + stack.push(frame.x[5]); + stack.push(frame.x[4]); + stack.push(frame.x[3]); + stack.push(frame.x[2]); + stack.push(frame.x[1]); + + stack.push(frame.elr_el1 as usize); + stack.push(frame.sp_el0 as usize); + + // Setup common + stack.push(0); + stack.push(ttbr0); + stack.push(__aa64_ctx_enter_from_fork as usize); // x30/lr + stack.push(frame.x[29]); // x29 + stack.push(frame.x[28]); // x28 + stack.push(frame.x[27]); // x27 + stack.push(frame.x[26]); // x26 + stack.push(frame.x[25]); // x25 + stack.push(frame.x[24]); // x24 + stack.push(frame.x[23]); // x23 + stack.push(frame.x[22]); // x22 + stack.push(frame.x[21]); // x21 + stack.push(frame.x[20]); // x20 + stack.push(frame.x[19]); // x19 + + Self { + k_sp: stack.sp, + + stack_base_phys: stack.bp, + stack_page_count: 8 + } + } + /// Constructs a new user-space thread context pub fn user(entry: usize, arg: usize, ttbr0: usize, ustack: usize) -> Self { let mut stack = Stack::new(8); @@ -119,6 +170,7 @@ impl Stack { } extern "C" { + fn __aa64_ctx_enter_from_fork(); fn __aa64_ctx_enter_kernel(); fn __aa64_ctx_enter_user(); fn __aa64_ctx_switch(dst: *mut Context, src: *mut Context); diff --git a/kernel/src/arch/aarch64/exception.rs b/kernel/src/arch/aarch64/exception.rs index e52cd90..607d5b4 100644 --- a/kernel/src/arch/aarch64/exception.rs +++ b/kernel/src/arch/aarch64/exception.rs @@ -6,6 +6,7 @@ use crate::dev::irq::{IntController, IrqContext}; use crate::syscall; use cortex_a::registers::{ESR_EL1, FAR_EL1}; use tock_registers::interfaces::Readable; +use ::syscall::abi; /// Trapped SIMD/FP functionality pub const EC_FP_TRAP: u64 = 0b000111; @@ -16,14 +17,15 @@ pub const EC_DATA_ABORT_EL0: u64 = 0b100100; /// SVC instruction in AA64 state pub const EC_SVC_AA64: u64 = 0b010101; +#[allow(missing_docs)] #[derive(Debug)] #[repr(C)] -struct ExceptionFrame { - x: [usize; 32], - spsr_el1: u64, - elr_el1: u64, - sp_el0: u64, - ttbr0_el1: u64, +pub struct ExceptionFrame { + pub x: [usize; 32], + pub spsr_el1: u64, + pub elr_el1: u64, + pub sp_el0: u64, + pub ttbr0_el1: u64, } #[inline(always)] @@ -83,6 +85,16 @@ extern "C" fn __aa64_exc_sync_handler(exc: &mut ExceptionFrame) { } EC_SVC_AA64 => { unsafe { + if exc.x[8] == abi::SYS_FORK { + match syscall::sys_fork(exc) { + Ok(pid) => exc.x[0] = pid.value() as usize, + Err(err) => { + todo!() + }, + } + return; + } + match syscall::syscall(exc.x[8], &exc.x[..6]) { Ok(val) => exc.x[0] = val, Err(err) => { diff --git a/kernel/src/mem/phys/manager.rs b/kernel/src/mem/phys/manager.rs index 2698138..0a7301f 100644 --- a/kernel/src/mem/phys/manager.rs +++ b/kernel/src/mem/phys/manager.rs @@ -1,5 +1,5 @@ use super::{PageInfo, PageUsage}; -use crate::mem::{virtualize, PAGE_SIZE}; +use crate::mem::{memcpy, virtualize, PAGE_SIZE}; use crate::sync::IrqSafeSpinLock; use core::mem; use error::Errno; @@ -8,6 +8,7 @@ pub unsafe trait Manager { fn alloc_page(&mut self, pu: PageUsage) -> Result; fn alloc_contiguous_pages(&mut self, pu: PageUsage, count: usize) -> Result; fn free_page(&mut self, page: usize) -> Result<(), Errno>; + fn clone_page(&mut self, src: usize) -> Result; // TODO status() } pub struct SimpleManager { @@ -34,23 +35,33 @@ impl SimpleManager { } } pub(super) unsafe fn add_page(&mut self, addr: usize) { - let page = &mut self.pages[addr / PAGE_SIZE - self.base_index]; + let page = &mut self.pages[self.page_index(addr)]; assert!(page.refcount == 0 && page.usage == PageUsage::Reserved); page.usage = PageUsage::Available; } -} -unsafe impl Manager for SimpleManager { - fn alloc_page(&mut self, pu: PageUsage) -> Result { + + fn page_index(&self, page: usize) -> usize { + page / PAGE_SIZE - self.base_index + } + + fn alloc_single_index(&mut self, pu: PageUsage) -> Result { for index in 0..self.pages.len() { let page = &mut self.pages[index]; if page.usage == PageUsage::Available { page.usage = pu; page.refcount = 1; - return Ok((self.base_index + index) * PAGE_SIZE); + return Ok(index); } } Err(Errno::OutOfMemory) } +} +unsafe impl Manager for SimpleManager { + fn alloc_page(&mut self, pu: PageUsage) -> Result { + self.alloc_single_index(pu) + .map(|r| (self.base_index + r) * PAGE_SIZE) + //return Ok((self.base_index + index) * PAGE_SIZE); + } fn alloc_contiguous_pages(&mut self, pu: PageUsage, count: usize) -> Result { 'l0: for i in 0..self.pages.len() { for j in 0..count { @@ -71,6 +82,19 @@ unsafe impl Manager for SimpleManager { fn free_page(&mut self, _page: usize) -> Result<(), Errno> { todo!() } + + fn clone_page(&mut self, src: usize) -> Result { + let src_index = self.page_index(src); + let src_page = &self.pages[src_index]; + assert_eq!(src_page.refcount, 1); + assert!(src_page.usage != PageUsage::Available && src_page.usage != PageUsage::Reserved); + let dst_index = self.alloc_single_index(src_page.usage)?; + let dst = (self.base_index + dst_index) * PAGE_SIZE; + unsafe { + memcpy(virtualize(dst) as *mut u8, virtualize(src) as *mut u8, 4096); + } + Ok(dst) + } } pub(super) static MANAGER: IrqSafeSpinLock> = IrqSafeSpinLock::new(None); diff --git a/kernel/src/mem/phys/mod.rs b/kernel/src/mem/phys/mod.rs index d1b678b..04df029 100644 --- a/kernel/src/mem/phys/mod.rs +++ b/kernel/src/mem/phys/mod.rs @@ -87,6 +87,11 @@ pub unsafe fn free_page(page: usize) -> Result<(), Errno> { MANAGER.lock().as_mut().unwrap().free_page(page) } +/// +pub fn clone_page(src: usize) -> Result { + MANAGER.lock().as_mut().unwrap().clone_page(src) +} + fn find_contiguous>(iter: T, count: usize) -> Option { for region in iter { let mut collected = 0; diff --git a/kernel/src/mem/virt/table.rs b/kernel/src/mem/virt/table.rs index c2998dd..e2256ef 100644 --- a/kernel/src/mem/virt/table.rs +++ b/kernel/src/mem/virt/table.rs @@ -74,6 +74,20 @@ impl Table { } } + /// + pub fn next_level_table(&mut self, index: usize) -> Option<&'static mut Table> { + let entry = self[index]; + if entry.is_present() { + if !entry.is_table() { + panic!("Entry is not a table: idx={}", index); + } + + Some(unsafe { &mut *(mem::virtualize(entry.address_unchecked()) as *mut _) }) + } else { + None + } + } + /// Constructs and fills a [Table] with non-present mappings pub const fn empty() -> Table { Table { @@ -135,6 +149,10 @@ impl Entry { pub const unsafe fn address_unchecked(self) -> usize { (self.0 & Self::PHYS_MASK) as usize } + + unsafe fn fork_flags(self) -> MapAttributes { + MapAttributes::from_bits_unchecked(self.0 & !Self::PHYS_MASK) + } } impl Space { @@ -166,4 +184,32 @@ impl Space { Ok(()) } } + + /// + pub fn fork(&mut self) -> Result<&'static mut Self, Errno> { + let mut res = Self::alloc_empty()?; + for l0i in 0..512 { + if let Some(l1_table) = self.0.next_level_table(l0i) { + for l1i in 0..512 { + if let Some(l2_table) = l1_table.next_level_table(l1i) { + for l2i in 0..512 { + let entry = l2_table[l2i]; + + // TODO copy-on-write + if entry.is_present() { + assert!(entry.is_table()); + let src_phys = unsafe { entry.address_unchecked() }; + let virt_addr = (l0i << 30) | (l1i << 21) | (l2i << 12); + debugln!("Fork page {:#x}:{:#x}", virt_addr, src_phys); + let dst_phys = phys::clone_page(src_phys)?; + + res.map(virt_addr, dst_phys, unsafe { entry.fork_flags() })?; + } + } + } + } + } + } + Ok(res) + } } diff --git a/kernel/src/proc/process.rs b/kernel/src/proc/process.rs index b4cdfaa..e9eab3f 100644 --- a/kernel/src/proc/process.rs +++ b/kernel/src/proc/process.rs @@ -4,6 +4,7 @@ use crate::mem::{ phys::{self, PageUsage}, virt::{MapAttributes, Space}, }; +use crate::arch::aarch64::exception::ExceptionFrame; use crate::proc::{PROCESSES, SCHED}; use crate::sync::IrqSafeSpinLock; use alloc::{rc::Rc, collections::BTreeMap}; @@ -123,6 +124,11 @@ impl Pid { assert!(!self.is_kernel()); self.0 as u8 } + + /// + pub const fn value(self) -> u32 { + self.0 + } } impl fmt::Display for Pid { @@ -137,6 +143,12 @@ impl fmt::Display for Pid { } impl ProcessIo { + /// + pub fn fork(&self) -> Result { + // TODO + Ok(Self::new()) + } + /// pub fn file(&mut self, idx: usize) -> Result<&mut File, Errno> { self.files.get_mut(&idx).ok_or(Errno::InvalidFile) @@ -285,6 +297,34 @@ impl Process { Ok(res) } + /// + pub fn fork(&self, frame: &mut ExceptionFrame) -> Result { + let src_io = self.io.lock(); + let mut src_inner = self.inner.lock(); + + let dst_id = Pid::new_user(); + let dst_space = src_inner.space.as_mut().unwrap().fork()?; + let dst_space_phys = (dst_space as *mut _ as usize) - mem::KERNEL_OFFSET; + let dst_ttbr0 = dst_space_phys | ((dst_id.asid() as usize) << 48); + + let dst = Rc::new(Self { + ctx: UnsafeCell::new(Context::fork(frame, dst_ttbr0)), + io: IrqSafeSpinLock::new(src_io.fork()?), + inner: IrqSafeSpinLock::new(ProcessInner { + id: dst_id, + exit: None, + space: Some(dst_space), + state: State::Ready, + wait_flag: false + }) + }); + debugln!("Process {} forked into {}", src_inner.id, dst_id); + assert!(PROCESSES.lock().insert(dst_id, dst.clone()).is_none()); + SCHED.enqueue(dst_id); + + Ok(dst_id) + } + /// Terminates a process. pub fn exit>(&self, status: I) { let status = status.into(); diff --git a/kernel/src/syscall.rs b/kernel/src/syscall.rs index 15ba5de..91242ea 100644 --- a/kernel/src/syscall.rs +++ b/kernel/src/syscall.rs @@ -1,12 +1,13 @@ use crate::debug::Level; use crate::mem; -use crate::proc::{wait, Process}; +use crate::proc::{wait, Process, Pid}; use core::mem::size_of; use core::time::Duration; use error::Errno; use libcommon::{Read, Write}; use syscall::{abi, stat::AT_FDCWD}; use vfs::{FileMode, OpenFlags, Stat}; +use crate::arch::platform::exception::ExceptionFrame; fn translate(virt: usize) -> Option { let mut res: usize; @@ -106,6 +107,12 @@ fn validate_user_str<'a>(base: usize, limit: usize) -> Result<&'a str, Errno> { }) } +pub unsafe fn sys_fork(regs: &mut ExceptionFrame) -> Result { + let proc = Process::current(); + let res = proc.fork(regs); + res +} + pub unsafe fn syscall(num: usize, args: &[usize]) -> Result { match num { // Process management system calls diff --git a/syscall/src/abi.rs b/syscall/src/abi.rs index 5dd734d..cc08598 100644 --- a/syscall/src/abi.rs +++ b/syscall/src/abi.rs @@ -9,3 +9,4 @@ pub const SYS_WRITE: usize = 3; pub const SYS_OPENAT: usize = 4; pub const SYS_FSTATAT: usize = 5; pub const SYS_CLOSE: usize = 6; +pub const SYS_FORK: usize = 7; diff --git a/syscall/src/calls.rs b/syscall/src/calls.rs index 25e1e93..16dc49d 100644 --- a/syscall/src/calls.rs +++ b/syscall/src/calls.rs @@ -2,6 +2,11 @@ use crate::abi; use crate::stat::Stat; macro_rules! syscall { + ($num:expr) => {{ + let mut res: usize; + asm!("svc #0", out("x0") res, in("x8") $num, options(nostack)); + res + }}; ($num:expr, $a0:expr) => {{ let mut res: usize = $a0; asm!("svc #0", @@ -84,3 +89,8 @@ pub unsafe fn sys_fstatat(at: i32, pathname: *const u8, statbuf: *mut Stat, flag flags as usize ) as i32 } + +#[inline(always)] +pub unsafe fn sys_fork() -> i32 { + syscall!(abi::SYS_FORK) as i32 +}