Commit 9e2a0ba8 authored by Nifou's avatar Nifou

Implement the `fork` and `execve` syscalls. ObsidianOS can now execute subprocesses!

parent ed6cd1a6
Pipeline #164712186 passed with stages
in 8 minutes and 38 seconds
......@@ -30,12 +30,11 @@ use x86_64::structures::paging::page_table::PageTableFlags as Flags;
use crate::memory;
use crate::tasking::{
process::Process,
process::ProcessBuilder,
info::{
ProcessInfo,
AuxVecItem,
},
scheduler::PROCESSES,
memory::MemoryArea,
};
......@@ -60,6 +59,9 @@ pub struct ElfLoader<'a> {
/// The path of the binary
path: String,
/// The process which is created during the loading of the binary
process: Option<ProcessBuilder>,
}
/// The ELF magic
......@@ -68,7 +70,7 @@ static MAGIC: [u8; 4] = [0x7f, 'E' as u8, 'L' as u8, 'F' as u8];
impl<'a> ElfLoader<'a> {
/// Create a new ElfLoader with the specified binary
pub fn new(binary: Elf<'a>, data: &'a [u8], path: String) -> Self {
Self { binary, data, path}
Self { binary, data, path, process: None }
}
/// Check the binary
......@@ -78,7 +80,7 @@ impl<'a> ElfLoader<'a> {
return Err(ElfCheckError::InvalidMagic);
}
// Verify that all segment are above the limit
// Verify that all segment are under the limit
for ph in &self.binary.program_headers {
if ph.p_type == PT_LOAD {
let end = ph.p_vaddr + ph.p_memsz as u64;
......@@ -114,9 +116,7 @@ impl<'a> ElfLoader<'a> {
/// Load the binary in memory
pub fn load(&mut self) {
let pid = Process::new(self.binary.entry as usize, self.info());
let mut processes = PROCESSES.write();
let process = processes.get_mut(&pid).unwrap();
self.process = Some(ProcessBuilder::new(self.binary.entry as usize, self.info()));
for ph in self.binary.program_headers.clone() {
if ph.p_type == PT_LOAD {
......@@ -174,11 +174,25 @@ impl<'a> ElfLoader<'a> {
area.mappings(mappings.clone());
// Add the mappings in the process' page table
process.memory.map(area);
self.process.as_mut().unwrap().memory().map(area);
// Unmap the binary in the kernel's address space
memory::unmap(mappings);
}
}
}
/// Get the created process
pub fn get_process(&self) -> ProcessBuilder {
if let Some(process) = self.process.clone() {
process
} else {
panic!("You must call `ElfLoader::load` before getting the process!");
}
}
/// Add the created process to the list of processes
pub fn add(&self) {
self.process.as_ref().unwrap().build();
}
}
......@@ -40,6 +40,7 @@ pub fn load(path: String) {
);
} else {
loader.load();
loader.add();
}
}
Err(e) => {
......
......@@ -15,12 +15,40 @@
* along with this program. If not, see https://www.gnu.org/licenses.
*/
//! Functions which are called when an interrupt or an exception happens
use x86_64::structures::idt::{InterruptStackFrame, PageFaultErrorCode};
use core::sync::atomic::Ordering;
use x86_64::{
VirtAddr,
structures::{
idt::{
InterruptStackFrame,
PageFaultErrorCode
},
paging::{
Page, Size4KiB,
page_table::PageTableFlags as Flags,
FrameAllocator,
Mapper,
},
},
};
use core::{
sync::atomic::Ordering,
intrinsics::copy_nonoverlapping,
};
use super::id::IrqId;
use super::idt::PICS;
use crate::tasking::scheduler::{FIRST_TIME, SCHEDULER};
use crate::{
tasking::{
memory::MemoryArea,
scheduler::{
FIRST_TIME, SCHEDULER, PROCESSES
},
},
memory::{
TMP_PAGE_ADDR,
table::TemporaryPage,
},
};
/// Loop infinitely
pub fn stop() -> ! {
......@@ -59,7 +87,7 @@ pub extern "x86-interrupt" fn timer_interrupt_handler(_: &mut InterruptStackFram
// Save the current context
if !FIRST_TIME.load(Ordering::Relaxed) {
// Use a closure to avoid messing up the stack (with the stack frame)
let get_kstack = || { SCHEDULER.get().current().unwrap().kstack};
let get_kstack = || { SCHEDULER.get().current().unwrap().kstack };
unsafe {
llvm_asm!("
......@@ -223,19 +251,100 @@ pub extern "x86-interrupt" fn general_protection_fault_handler(
/// A page fault handler which prints the address which would be accessed, the error code and the
/// stack frame
pub extern "x86-interrupt" fn page_fault_handler(
stack_frame: &mut InterruptStackFrame,
error_code: PageFaultErrorCode,
) {
///
/// Handler(s):
/// * Copy-on-write
pub extern "x86-interrupt" fn page_fault_handler(stack_frame: &mut InterruptStackFrame, error_code: PageFaultErrorCode) {
use x86_64::registers::control::Cr2;
// Use the kernel's page table
crate::memory::switch_to_kernel_page_table();
let mut handled = false;
let addr = Cr2::read();
println!("EXCEPTION: PAGE FAULT");
println!("Accessed Address: {:?}", addr);
println!("Error Code: {:?}", error_code);
println!("{:#?}", stack_frame);
let page = Page::<Size4KiB>::containing_address(addr);
let mut processes = PROCESSES.write();
let current_thread = SCHEDULER.get().current().unwrap().clone();
if let Some(area) = processes.get(&current_thread.process).expect("Could not find the current process").memory.get_area(addr.as_u64()) {
let mut flags = area.flags;
if flags.contains(Flags::BIT_9)
&& error_code.contains(PageFaultErrorCode::CAUSED_BY_WRITE) {
// --- Copy-on-write handler ---
// Allocate a new frame which will be the clone of the frame
let frame = crate::memory::FRAME_ALLOCATOR
.r#try()
.unwrap()
.lock()
.allocate_frame()
.unwrap();
// Create a temporary page to copy data between the two frames
let mut tmp = TemporaryPage::new(Page::containing_address(VirtAddr::new(TMP_PAGE_ADDR)));
// TODO: Add the kernel heap in the process' memory to avoid this ugly and slow line
// (because we would be in the process' memory space and not in the kernel's memory space)
let mut dest_frame = None;
processes.get_mut(&current_thread.process).expect("Could not find the current process").memory.table.with(|mapper| {
dest_frame = mapper.translate_page(page).ok();
});
unsafe {
crate::memory::KERNEL_MAPPER.r#try().unwrap().lock().map_to(
page,
dest_frame.unwrap(),
Flags::PRESENT | Flags::NO_EXECUTE,
&mut *crate::memory::FRAME_ALLOCATOR.r#try().unwrap().lock()
).unwrap().flush();
}
// ---
{
let src = (addr.as_u64() - (addr.as_u64() % 4096)) as *const u8;
let dest = tmp.map(frame.clone(), Flags::PRESENT | Flags::WRITABLE | Flags::NO_EXECUTE) as *mut u8;
unsafe { copy_nonoverlapping(src, dest, 4096 /* TODO: Constant? */) };
}
tmp.unmap();
// TODO: Add the kernel heap in the process' memory to avoid this ugly and slow line
// (because we would be in the process' memory space and not in the kernel's memory space)
crate::memory::KERNEL_MAPPER.r#try().unwrap().lock().unmap(
page
).unwrap().1.flush();
// ---
// Change the page's frame
processes.get_mut(&current_thread.process).expect("Could not find the current process")
.memory.table.unmap(page);
// Add the WRITABLE flag because the page is now cloned
flags.insert(Flags::WRITABLE);
flags.remove(Flags::BIT_9);
// FIXME: Map with ProcessPageTable::map and change the frame in the areas
let mut area = MemoryArea::new(
page.start_address().as_u64(),
page.start_address().as_u64() + 4096,
flags
);
area.mappings(vec![(frame, page)]);
processes.get_mut(&current_thread.process).expect("Could not find the current process")
.memory.map(area);
handled = true;
}
}
stop();
if handled == false {
println!("@ Error: Page fault at {:#x} | {:?} | -> {:#x}", stack_frame.instruction_pointer.as_u64(), error_code, addr.as_u64());
stop();
}
processes.get(&current_thread.process).expect("Could not find the current process").memory.switch_page_table();
drop(processes);
}
/// A double fault exception handler
......
......@@ -46,7 +46,8 @@ use frame_allocator::NextFrameAllocator;
use self::table::ProcessPageTable;
use self::heap::{HEAP_START_VIRT, HEAP_SIZE};
const TMP_PAGE_ADDR: u64 = HEAP_START_VIRT + HEAP_SIZE as u64 + 4096;
/// The address of the temporary page
pub const TMP_PAGE_ADDR: u64 = HEAP_START_VIRT as u64 + HEAP_SIZE as u64 + 4096;
/// The start of the kernel in the Higher Half part (Virtual)
pub const KERNEL_START_VIRT: u64 = 0xc0000000;
......@@ -140,19 +141,16 @@ fn remap_kernel(frame: PhysFrame) -> RecursivePageTable<'static> {
}
// Map the kernel heap
for (i, page) in Page::<Size4KiB>::range_inclusive(
for page in Page::<Size4KiB>::range_inclusive(
Page::containing_address(VirtAddr::new(HEAP_START_VIRT)),
Page::containing_address(VirtAddr::new(HEAP_START_VIRT + HEAP_SIZE as u64))
).enumerate() {
) {
let frame = FRAME_ALLOCATOR
.r#try()
.unwrap()
.lock()
.allocate_frame()
.expect("cannot allocate frame");
if i == 0 {
println!("{:#?}", frame);
}
unsafe { mapper.map_to(page, frame, Flags::PRESENT | Flags::WRITABLE, &mut *FRAME_ALLOCATOR.r#try().unwrap().lock()).unwrap().ignore(); }
}
......
......@@ -112,7 +112,9 @@ impl ProcessPageTable {
}
/// Map all pages between two addresses in the process' page table
pub unsafe fn map(&self, start: u64, end: u64, mut flags: Flags, mut table_flags: Flags) {
pub unsafe fn map(&self, start: u64, end: u64, mut flags: Flags, mut table_flags: Flags) -> Mappings {
let mut mappings = Vec::new();
// Add the USER_ACCESSIBLE flag to make sure that the content can be accessed in
// userspace
table_flags.set(Flags::USER_ACCESSIBLE, true);
......@@ -135,6 +137,8 @@ impl ProcessPageTable {
.allocate_frame()
.expect("cannot allocate frame");
mappings.push((frame, page));
mapper.map_to(
page,
frame,
......@@ -145,6 +149,7 @@ impl ProcessPageTable {
.ignore(); // Ignore mapping because we will flush all mappings after
}
});
mappings
}
/// Unmap a page
......@@ -152,6 +157,13 @@ impl ProcessPageTable {
self.with(|mapper| mapper.unmap(page).unwrap().1.flush());
}
/// Change the flags of an already mapped page
pub unsafe fn update_flags(&mut self, page: Page, flags: Flags) {
self.with(|mapper| {
mapper.update_flags(page, flags).unwrap().flush();
});
}
/// Get the process' page table
pub unsafe fn table(&self) -> &'static mut PageTable {
to_page_table(self.p4_frame.start_address().as_u64())
......
......@@ -18,6 +18,9 @@
/// An error happens during a syscall
pub enum Error {
/// No such file or directory
ENOENT = 2,
/// Invalid value
EINVAL = 22,
......
......@@ -18,13 +18,25 @@
use core::str::from_utf8;
use core::slice::from_raw_parts;
use super::{Syscall, error::Result};
use super::{Syscall, error::{Error, Result}};
const STDIN: usize = 0;
const STDOUT: usize = 1;
const STDERR: usize = 2;
#[derive(Debug)]
struct PipePair {
fd: [u32; 2],
}
impl Syscall {
/// Read a file descriptor
pub fn read(&self, fd: usize, buf: usize, count: usize) -> Result {
/* TODO: Read syscall */
println!("read({}, {:#x}, {})", fd, buf, count);
Ok(0)
}
/// Write to the corresponding file descriptor
pub fn write(&mut self, fd: usize, buf: usize, count: usize) -> Result {
match fd {
......@@ -33,9 +45,36 @@ impl Syscall {
.expect("cannot transform to utf8");
print!("{}", content);
Ok(count)
},
_ => {
println!("write({}, {:#x}, {})", fd, buf, count);
println!("We can't write in files for now. Sorry!");
Err(Error::ENOSYS)
},
_ => println!("We can't write in files for now. Sorry!"),
}
Ok(count)
}
/// Close a file descriptor
pub fn close(&self, fd: usize) -> Result {
/* TODO: Close syscall */
println!("close({})", fd);
Ok(0)
}
/// Create a pipe between two processes
pub fn pipe2(&self, pipefd: usize, flags: usize) -> Result {
/* TODO: Pipe2 syscall */
let pair;
unsafe {
pair = &mut *(pipefd as *mut PipePair);
}
pair.fd[0] = 3;
pair.fd[1] = 4;
println!("pipe2({:?}, {}) = 0", pair, flags);
Ok(0)
}
}
......@@ -19,7 +19,9 @@
//! * **Filesystem** for syscalls used to manage the filesystem (open, read, write, close, ...)
//! * **Memory** for syscalls used to manage the memory (mmap, brk, ...)
//! * **Misc** for the others
use alloc::prelude::v1::String;
use x86::msr;
use core::slice::from_raw_parts;
use crate::tasking::context::Registers;
use self::error::Error;
......@@ -120,15 +122,23 @@ pub unsafe extern fn syscall() {
}
// --- Syscall numbers ---
const WRITE: usize = 1;
const MMAP: usize = 9;
const BRK: usize = 12;
const SIGACTION: usize = 13;
const SIGPROCMASK: usize = 14;
const EXIT: usize = 60;
const ARCH_PRCTL: usize = 158;
const TKILL: usize = 200;
const EXIT_GROUP: usize = 231;
const READ: usize = 0;
const WRITE: usize = 1;
const CLOSE: usize = 3;
const MMAP: usize = 9;
const BRK: usize = 12;
const SIGACTION: usize = 13;
const SIGPROCMASK: usize = 14;
const FORK: usize = 57;
const EXECVE: usize = 59;
const EXIT: usize = 60;
const WAIT4: usize = 61;
const ARCH_PRCTL: usize = 158;
const GETTID: usize = 186;
const TKILL: usize = 200;
const SET_TID_ADDR: usize = 218;
const EXIT_GROUP: usize = 231;
const PIPE2: usize = 293;
/// A structure used to call the right handler for each syscall
pub struct Syscall {
......@@ -149,15 +159,23 @@ impl Syscall {
let args = [self.stack.rdi, self.stack.rsi, self.stack.rdx, self.stack.r10, self.stack.r8, 0];
let result = match num {
READ => self.read(args[0], args[1], args[2]),
WRITE => self.write(args[0], args[1], args[2]),
CLOSE => self.close(args[0]),
MMAP => self.mmap(args[0], args[1], args[2], args[3], args[4], args[5]),
BRK => self.brk(args[0]),
EXIT => self.exit(args[0]),
SIGACTION => self.sigaction(args[0], args[1], args[2]),
SIGPROCMASK => self.sigprocmask(args[0], args[1], args[2], args[3]),
FORK => self.fork(),
EXECVE => self.execve(args[0], args[1], args[2]),
EXIT => self.exit(args[0]),
WAIT4 => self.wait4(args[0], args[1], args[2], args[3]),
ARCH_PRCTL => self.arch_prctl(args[0], args[1]),
GETTID => self.gettid(),
TKILL => self.tkill(args[0], args[1]),
SET_TID_ADDR => self.set_tid_addr(args[0]),
EXIT_GROUP => self.exit_group(args[0]),
PIPE2 => self.pipe2(args[0], args[1]),
_ => {
println!("Unknown syscall: {}", num);
Err(Error::ENOSYS)
......@@ -170,3 +188,29 @@ impl Syscall {
}
}
}
/// Get a string from an address
///
/// Safety:
/// * The caller must give a right address
pub unsafe fn get_string(addr: usize) -> Option<String> {
// Get the count of bytes by iterating the memory and by breaking when '\0' is found
let pointer = addr as *const u8;
let mut count = 0;
// Send a timeout at 10_000_000
for _ in 0..10_000_000 {
let data = *pointer.offset(count);
if data != 0 {
count += 1;
} else {
break;
}
}
if count != 0 {
Some(String::from_utf8_lossy(from_raw_parts(pointer, count as usize)).into_owned())
} else {
None
}
}
......@@ -15,8 +15,19 @@
* along with this program. If not, see https://www.gnu.org/licenses.
*/
//! Syscalls used for tasking (exit, ...)
use crate::tasking::scheduler::exit;
use super::{Syscall, error::{Result, Error}};
use core::sync::atomic::Ordering;
use alloc::prelude::v1::ToString;
use goblin::elf::Elf;
use crate::tasking::{
process::{NEXT_PID, Pid},
scheduler::{SCHEDULER, PROCESSES, exit},
thread::Thread,
memory::ProcessMemory,
};
use super::{Syscall, error::{Result, Error}, get_string};
use crate::initfs;
use crate::elf::loader::ElfLoader;
// --- Signals (from libc) ---
......@@ -92,9 +103,168 @@ impl Syscall {
Err(Error::EINVAL)
}
/// Get the TID of the current thread
pub fn gettid(&self) -> Result {
print!("gettid() = ");
// Use the kernel's page table
crate::memory::switch_to_kernel_page_table();
let tid = SCHEDULER.get().current().unwrap().tid.0;
println!("{}", tid);
Ok(tid)
}
/// Set the function which will be executed when the signal `num` will be handled
pub fn sigaction(&self, _num: usize, _action: usize, _oldaction: usize) -> Result { /* TODO: Set signal action*/ Ok(0) }
pub fn sigaction(&self, num: usize, action: usize, oldaction: usize) -> Result {
/* TODO: Set signal action*/
println!("sigaction({}, {:#x}, {:#x}) = 0", num, action, oldaction);
Ok(0)
}
/// Change the mask of blocked signals
pub fn sigprocmask(&self, _how: usize, _set: usize, _oldset: usize, _size: usize) -> Result { /* TODO: Set signal mask*/ Ok(0) }
pub fn sigprocmask(&self, how: usize, set: usize, oldset: usize, size: usize) -> Result {
/* TODO: Set signal mask*/
println!("sigprocmask({}, {:#x}, {:#x}, {}) = 0", how, set, oldset, size);
Ok(0)
}
/// Wait for a process to change state
pub fn wait4(&self, pid: usize, status: usize, opts: usize, usage: usize) -> Result {
/* TODO: Wait4 */
println!("wait4({}, {}, {:#x}, {}) = 0", pid, status, opts, usage);
Ok(0)
}
/// Wait for a process to change state
pub fn set_tid_addr(&self, tidptr: usize) -> Result {
/* TODO: Set tid addr */
println!("set_tid_addr({:#x}) = ?", tidptr);
// Use the kernel's page table
crate::memory::switch_to_kernel_page_table();
Ok(SCHEDULER.get().current().unwrap().tid.0)
}
/// Create a new child process from the current
pub fn fork(&self) -> Result {
print!("fork() = ");
// Use the kernel's page table
crate::memory::switch_to_kernel_page_table();
// List of processes
let mut processes = PROCESSES.write();
// Get the current process
let parent_process = SCHEDULER.get().current().unwrap().process;
// Get the PID of the new process
let pid = NEXT_PID.fetch_add(1, Ordering::Relaxed);
// Clone the current process (thank you Rust's traits for this awesome implementation ;-))
let mut child_process = processes.get(&parent_process).unwrap().clone();
// Set the PID of the new process
child_process.pid = Pid(pid);
// Set the parent of this process
child_process.parent = Some(parent_process);
// Remove all threads in the new process
child_process.threads.clear();
// Set copy-on-write memory
let (parent_memory, child_memory) = ProcessMemory::copy_on_write(processes.get(&parent_process).unwrap().memory.clone());
processes.get_mut(&parent_process).unwrap().memory = parent_memory;
child_process.memory = child_memory;
// Create a new thread for the new process