Commit 7d998171 authored by Nifou's avatar Nifou

Rewrite completely the management of processes and the scheduler

- Threads are distinguished of processes
- Create a Rust trait SchedulerAlgo to make the addition of algorithms easier
parent 3beca331
......@@ -31,11 +31,11 @@ use x86_64::structures::paging::page_table::PageTableFlags as Flags;
use crate::memory;
use crate::tasking::{
process::Process,
scheduler,
info::{
ProcessInfo,
AuxVecItem,
},
processor::PROCESSES,
};
#[derive(PartialEq, Debug, Clone, Copy)]
......@@ -112,8 +112,10 @@ impl<'a> ElfLoader<'a> {
}
/// Load the binary in memory
pub fn load(&mut self) -> Process {
let mut process = Process::new(self.binary.entry, self.info());
pub fn load(&mut self) {
let pid = Process::new(self.binary.entry, self.info());
let mut processes = PROCESSES.write();
let process = processes.get_mut(&pid).unwrap();
for ph in self.binary.program_headers.clone() {
if ph.p_type == PT_LOAD {
......@@ -170,11 +172,5 @@ impl<'a> ElfLoader<'a> {
memory::unmap(mappings);
}
}
process
}
/// Add a new task for the binary
pub fn run(&self, process: Process) {
scheduler::spawn(process);
}
}
......@@ -39,8 +39,7 @@ pub fn load(path: String) {
e
);
} else {
let process = loader.load();
loader.run(process);
loader.load();
}
}
Err(e) => {
......
......@@ -19,7 +19,7 @@ use x86_64::structures::idt::{InterruptStackFrame, PageFaultErrorCode};
use super::id::IrqId;
use super::idt::PICS;
use crate::tasking::scheduler::exit;
use crate::tasking::processor::PROCESSOR;
/// Loop infinitely
pub fn stop() -> ! {
......@@ -42,8 +42,11 @@ fn end(id: IrqId) {
pub extern "x86-interrupt" fn timer_interrupt_handler(stack_frame: &mut InterruptStackFrame) {
end(IrqId::TIMER);
// The current process is interrupted, save the intrustion and the stack pointers
exit(Some((stack_frame.instruction_pointer.as_u64(), stack_frame.stack_pointer.as_u64())));
// Use the kernel's page table
crate::memory::switch_to_kernel_page_table();
// The current process is interrupted, save the instruction and the stack pointers
PROCESSOR.tick(Some((stack_frame.instruction_pointer.as_u64(), stack_frame.stack_pointer.as_u64())));
}
/// The keyboard interrupt handler
......
......@@ -24,7 +24,7 @@ const STDIN: usize = 0;
const STDOUT: usize = 1;
const STDERR: usize = 2;
impl<'a> Syscall<'a> {
impl Syscall {
/// Write to the corresponding file descriptor
pub fn write(&mut self, fd: usize, buf: usize, count: usize) -> usize {
match fd {
......
......@@ -18,7 +18,7 @@
use bitflags::bitflags;
use x86_64::structures::paging::page_table::PageTableFlags as Flags;
use crate::tasking::scheduler::SCHEDULER;
use crate::tasking::processor::{PROCESSOR, PROCESSES};
use super::Syscall;
bitflags! {
......@@ -61,7 +61,7 @@ impl From<MmapProt> for Flags {
}
}
impl<'a> Syscall<'a> {
impl Syscall {
/// Map files or devices into memory
pub fn mmap(&self, addr: usize, len: usize, prot: usize, flags: usize, fd: usize, offset: usize)
-> usize {
......@@ -72,12 +72,17 @@ impl<'a> Syscall<'a> {
if flags.contains(MmapFlags::ANONYMOUS) {
if addr == 0 {
let mut processes = PROCESSES.write();
// Use the kernel's page table
crate::memory::switch_to_kernel_page_table();
unsafe { SCHEDULER.force_unlock(); }
SCHEDULER.lock().get_current_mut().memory.map(0x300000, 0x300000 + len as u64, Flags::from(prot), Flags::PRESENT);
// Get the current thread's process
let mut current_process = PROCESSOR.inner().scheduler.current().process;
processes.get_mut(&current_process).unwrap().memory.map(0x300000, 0x300000 + len as u64, Flags::from(prot), Flags::PRESENT);
processes.get_mut(&current_process).unwrap().memory.switch_page_table();
SCHEDULER.lock().get_current_mut().memory.switch_page_table();
0x300000
}
else {
......@@ -95,12 +100,17 @@ impl<'a> Syscall<'a> {
if addr == 0 {
println!("0x10000000");
let mut processes = PROCESSES.write();
// Use the kernel's page table
crate::memory::switch_to_kernel_page_table();
unsafe { SCHEDULER.force_unlock(); }
SCHEDULER.lock().get_current_mut().memory.map(0x10000000, 0x10004000, Flags::PRESENT | Flags::WRITABLE, Flags::PRESENT);
// Get the current thread's process
let mut current_process = PROCESSOR.inner().scheduler.current().process;
processes.get_mut(&current_process).unwrap().memory.map(0x10000000, 0x10004000, Flags::PRESENT | Flags::WRITABLE, Flags::PRESENT);
processes.get_mut(&current_process).unwrap().memory.switch_page_table();
SCHEDULER.lock().get_current_mut().memory.switch_page_table();
0x10000000
}
else {
......
......@@ -22,7 +22,7 @@ use x86_64::{
use super::Syscall;
impl<'a> Syscall<'a> {
impl Syscall {
/// Set og get some architecture specific registers
pub fn arch_prctl(&mut self, code: usize, addr: usize) -> usize {
println!("arch_prctl({:#x}, {:#x}) = 0", code, addr);
......
......@@ -19,11 +19,6 @@
//! * **Filesystem** for syscalls used to manage the filesystem (open, read, write, close, ...)
use x86::msr;
use crate::tasking::{
scheduler::SCHEDULER,
process::Process,
};
pub mod process;
pub mod fs;
pub mod memory;
......@@ -96,9 +91,8 @@ pub unsafe fn init() {
/// * Restores the register's values
pub unsafe extern fn syscall() {
#[inline(never)]
unsafe fn inner(stack: &mut SyscallStack) -> usize {
SCHEDULER.force_unlock();
Syscall::new(stack, SCHEDULER.lock().get_current_mut()).syscall()
unsafe fn inner(stack: &'static mut SyscallStack) -> usize {
Syscall::new(stack).syscall()
}
llvm_asm!("swapgs // GS is now pointing to the TSS
......@@ -160,17 +154,15 @@ const EXIT: usize = 60;
const ARCH_PRCTL: usize = 158;
/// A structure used to call the right handler for each syscall
pub struct Syscall<'a> {
stack: &'a mut SyscallStack,
process: &'a mut Process,
pub struct Syscall {
stack: &'static mut SyscallStack,
}
impl<'a> Syscall<'a> {
impl Syscall {
/// Create a new Syscall
pub fn new(stack: &'a mut SyscallStack, process: &'a mut Process) -> Self {
pub fn new(stack: &'static mut SyscallStack) -> Self {
Self {
stack,
process,
}
}
......
......@@ -15,17 +15,22 @@
* along with this program. If not, see https://www.gnu.org/licenses.
*/
//! Syscalls used for tasking (exit, ...)
use crate::tasking::scheduler::kill;
use crate::tasking::processor::PROCESSOR;
use super::Syscall;
impl<'a> Syscall<'a> {
impl Syscall {
/// Exit from the current process by switching to the next process
pub fn exit(&self, code: usize) -> usize {
match code {
0 => println!("Exit success"),
_ => println!("Error"),
}
kill();
// Use the kernel's page table
crate::memory::switch_to_kernel_page_table();
PROCESSOR.inner().scheduler.remove();
PROCESSOR.tick(None);
0
}
}
/*
* Copyright (C) 2020 Nicolas Fouquet
*
* This program is free software: you can redistribute it and/or modify
* it under the terms of the GNU General Public License as published by
* the Free Software Foundation, either version 3 of the License, or
* (at your option) any later version.
*
* This program is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU General Public License for more details.
*
* You should have received a copy of the GNU General Public License
* along with this program. If not, see https://www.gnu.org/licenses.
*/
use super::thread::Thread;
/// A trait which defines the common functions used in a scheduler algorithm
pub trait SchedulerAlgo {
/// Add a new thread in the list of threads
fn add(&mut self, thread: Thread);
/// Remove the current thread in the list of threads
fn remove(&mut self);
/// Choose the next thread which will be executed
fn choose(&mut self) -> Option<Thread>;
/// Get the current thread
fn current(&self) -> Thread;
/// Get the current mutable thread
fn current_mut(&mut self) -> &mut Thread;
}
......@@ -28,11 +28,11 @@ use super::registers::*;
/// The size of SSE, x87 FPU, and MMX states in memory (in the FX register)
pub const FX_AREA_SIZE: usize = 512;
#[derive(Clone)]
#[derive(Debug, Clone)]
/// The context of a process (its registers and its stack).
pub struct Context {
regs: Registers,
sse_state: Box<[u8; FX_AREA_SIZE]>,
pub sse_state: u64,
}
impl Context {
......@@ -45,7 +45,7 @@ impl Context {
Self {
regs,
sse_state: unsafe {
Box::from_raw(HEAP_ALLOCATOR.alloc(Layout::from_size_align_unchecked(512, 16)) as *mut [u8; 512])
Box::into_raw(Box::from_raw(HEAP_ALLOCATOR.alloc(Layout::from_size_align_unchecked(512, 16)) as *mut [u8; 512])) as u64
},
}
}
......@@ -76,7 +76,7 @@ impl Context {
self.regs.rflags = Rflags::get();
// Save the SSE state
Fx::save(self.sse_state.as_ptr() as u64);
Fx::save(self.sse_state);
}
/// Load the registers of this context to the registers of the CPU
......@@ -103,7 +103,7 @@ impl Context {
if first_time {
Fx::init();
} else {
Fx::restore(self.sse_state.as_ptr() as u64);
Fx::restore(self.sse_state);
}
}
......
......@@ -27,5 +27,8 @@ pub mod context;
pub mod memory;
pub mod process;
pub mod registers;
pub mod scheduler;
pub mod thread;
pub mod info;
pub mod algo;
pub mod scheduler;
pub mod processor;
......@@ -14,13 +14,17 @@
* You should have received a copy of the GNU General Public License
* along with this program. If not, see https://www.gnu.org/licenses.
*/
//! The representation of a process with its memory, its registers, ...
use core::sync::atomic::{AtomicUsize, Ordering};
use alloc::prelude::v1::Vec;
use x86_64::structures::paging::page_table::PageTableFlags as Flags;
use super::context::Context;
use super::memory::ProcessMemory;
use super::info::ProcessInfo;
use crate::memory;
use super::{
memory::ProcessMemory,
info::ProcessInfo,
processor::{PROCESSES, PROCESSOR},
thread::Thread,
};
/// The address of the user stack
pub const STACK_ADDR: u64 = 0xa0000000;
......@@ -28,6 +32,8 @@ pub const STACK_ADDR: u64 = 0xa0000000;
/// The size of a process' stack
pub const STACK_SIZE: usize = 65536;
static NEXT_PID: AtomicUsize = AtomicUsize::new(0);
#[derive(PartialEq, Debug, Clone, Copy)]
/// The state of the process (killed or alive)
pub enum ProcessState {
......@@ -38,11 +44,11 @@ pub enum ProcessState {
Killed,
}
/// The representation of a process with its memory, its registers, ...
#[derive(Clone)]
#[derive(Debug, Clone)]
/// A process which regroups resources and more
pub struct Process {
/// The context of this process
pub ctx: Context,
/// The process ID
pub pid: usize,
/// The state of this process
pub state: ProcessState,
......@@ -53,24 +59,25 @@ pub struct Process {
/// Info about the process (arguments, environment, ...)
pub info: ProcessInfo,
/// Whether or not this process was already chosen by the scheduler
pub first_time: bool,
/// Threads of the process
pub threads: Vec<usize>,
}
impl Process {
/// Create a new process
pub fn new(rip: u64, mut info: ProcessInfo) -> Self {
pub fn new(rip: u64, mut info: ProcessInfo) -> usize {
// We can use a static address for the stack because the content of it will be switched
// with the switch of page tables
let mut processes = PROCESSES.write();
let mappings = memory::map(STACK_ADDR - STACK_SIZE as u64, STACK_ADDR, Flags::PRESENT | Flags::WRITABLE);
let rsp = info.push_at(STACK_ADDR as usize) as u64;
let mut process = Self {
ctx: Context::new(rip, rsp),
pid: NEXT_PID.fetch_add(1, Ordering::Relaxed),
state: ProcessState::Alive,
memory: ProcessMemory::new(),
info,
first_time: true,
threads: vec![],
};
// Add the mappings in the process' page table
......@@ -79,67 +86,18 @@ impl Process {
// Unmap the stack in the kernel's address space
memory::unmap(mappings);
process
}
// Insert the new process
processes.insert(process.pid, process.clone());
/// Kill the process
pub fn kill(&mut self) {
self.state = ProcessState::Killed;
}
// Create a new thread
let thread = Thread::new(process.pid, rip, rsp);
// Insert the new thread
PROCESSOR.inner().scheduler.add(thread.clone());
// Add the new thread in the process' threads list
processes.get_mut(&process.pid).unwrap().threads.push(thread.tid);
/// Jump to this process
pub fn jmp(&self, rip: u64, rsp: u64, arg1: u64, arg2: u64) {
use crate::gdt;
unsafe {
// Create the stack frame
llvm_asm!("push r9
push r10
push r11
push r12
push r13
push r14
push r15"
: // No output
: "{r9}"(gdt::GDT_USER_DATA << 3 | 3), // Data segment
"{r10}"(rsp), // Stack pointer
"{r11}"(1 << 9), // Flags - Set interrupt enable flag
"{r12}"(gdt::GDT_USER_CODE << 3 | 3), // Code segment
"{r13}"(rip), // IP
"{r14}"(arg2), // Argument 2
"{r15}"(arg1) // Argument 1
: // No clobbers
: "intel", "volatile");
// Go to usermode
llvm_asm!("mov ds, r14d
mov es, r14d
mov fs, r14d
mov gs, r14d
xor rax, rax
xor rbx, rbx
xor rcx, rcx
xor rdx, rdx
xor rsi, rsi
xor rdi, rdi
xor rbp, rbp
xor r8, r8
xor r9, r9
xor r10, r10
xor r11, r11
xor r12, r12
xor r13, r13
xor r14, r14
xor r15, r15
fninit
pop rdi
pop rsi
iretq"
: // No output because it never returns
: "{r14}"(gdt::GDT_USER_DATA << 3 | 3) // Data segment
: // No clobbers because it never returns
: "intel", "volatile");
}
unreachable!();
process.pid
}
}
/*
* Copyright (C) 2020 Nicolas Fouquet
*
* This program is free software: you can redistribute it and/or modify
* it under the terms of the GNU General Public License as published by
* the Free Software Foundation, either version 3 of the License, or
* (at your option) any later version.
*
* This program is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU General Public License for more details.
*
* You should have received a copy of the GNU General Public License
* along with this program. If not, see https://www.gnu.org/licenses.
*/
use lazy_static::lazy_static;
use core::cell::UnsafeCell;
use alloc::{
prelude::v1::Box,
collections::btree_map::BTreeMap,
};
use spin::RwLock;
use super::algo::SchedulerAlgo;
use super::process::Process;
use super::scheduler::round_robin::RoundRobinScheduler;
lazy_static! {
pub static ref PROCESSOR: Processor = Processor::new(Box::new(RoundRobinScheduler::new()));
}
lazy_static! {
pub static ref PROCESSES: RwLock<BTreeMap<usize, Process>> = RwLock::new(BTreeMap::new());
}
pub struct ProcessorInner {
/// The scheduler which choose the next thread
pub scheduler: Box<dyn SchedulerAlgo>,
/// Whether or not it is the first time that the processor is running
first_time: bool,
}
pub struct Processor {
pub inner: UnsafeCell<ProcessorInner>,
}
unsafe impl Sync for Processor {}
unsafe impl Send for Processor {}
impl Processor {
pub fn new(scheduler: Box<dyn SchedulerAlgo>) -> Self {
Self {
inner: UnsafeCell::new(ProcessorInner::new(scheduler)),
}
}
pub fn inner(&self) -> &mut ProcessorInner {
unsafe { &mut *self.inner.get() }
}
/// Switch to the next thread
///
/// We assume that we are using the kernel's page table
pub fn tick(&self, saved: Option<(u64, u64)>) {
let processes = PROCESSES.read();
let mut inner = self.inner();
// Don't save the context if this is the first time
if !inner.first_time {
inner.scheduler.current_mut().ctx.save(saved);
} else {
inner.first_time = false;
}
if let Some(current) = inner.scheduler.choose() {
// Load the new context
current.ctx.load(current.first_time);
if current.first_time {
inner.scheduler.current_mut().first_time = false;
}
// Switch to the process' page table
let rip = current.ctx.get_rip();
let rsp = current.ctx.get_rsp();
// Get the current thread's process
let current_process = current.process;
let argc = processes.get(&current_process).unwrap().info.args.len();
let argv = processes.get(&current_process).unwrap().info.first_arg_addr;
processes.get(&current_process).unwrap().memory.switch_page_table();
// Drop the lock because this function will never terminate (see the unreachable!())
drop(processes);
// Jump to the process
self.jmp(rip, rsp, argc as u64, argv);
} else {
// If there is zero thread, run the idle function
self.idle();
}
}
fn idle(&self) {
loop {
unsafe { llvm_asm!("sti; hlt; cli"); }
}
}
/// Jump in usermode to a thread
pub fn jmp(&self, rip: u64, rsp: u64, arg1: u64, arg2: u64) {
use crate::gdt;
unsafe {
// Create the stack frame
llvm_asm!("push r9
push r10
push r11
push r12
push r13
push r14
push r15"
: // No output
: "{r9}"(gdt::GDT_USER_DATA << 3 | 3), // Data segment
"{r10}"(rsp), // Stack pointer
"{r11}"(1 << 9), // Flags - Set interrupt enable flag
"{r12}"(gdt::GDT_USER_CODE << 3 | 3), // Code segment
"{r13}"(rip), // IP
"{r14}"(arg2), // Argument 2
"{r15}"(arg1) // Argument 1
: // No clobbers
: "intel", "volatile");