Commit c2ed9c5f authored by Nifou's avatar Nifou

Use a kernel stack for each process and save its context on it. Remove

the stack overflow during the timer interrupt handler.
parent 2d2ed341
......@@ -175,7 +175,7 @@ pub unsafe fn init() {
GDT[GDT_TSS].set_limit(mem::size_of::<TaskStateSegment>() as u32);
// Set the stack pointer when coming back from userspace
TSS.rsp[0] = x86::bits64::registers::rsp();
set_stack(x86::bits64::registers::rsp());
// Load the GDT
lgdt(&GDTR);
......@@ -191,3 +191,8 @@ pub unsafe fn init() {
// Load the task register
load_tr(SegmentSelector::new(GDT_TSS as u16, Ring0));
}
/// Set the kernel's stack pointer in the TSS
pub unsafe fn set_stack(addr: u64) {
TSS.rsp[0] = addr;
}
......@@ -16,10 +16,11 @@
*/
//! Functions which are called when an interrupt or an exception happens
use x86_64::structures::idt::{InterruptStackFrame, PageFaultErrorCode};
use core::sync::atomic::Ordering;
use super::id::IrqId;
use super::idt::PICS;
use crate::tasking::processor::PROCESSOR;
use crate::tasking::processor::{FIRST_TIME, PROCESSOR};
/// Loop infinitely
pub fn stop() -> ! {
......@@ -39,14 +40,84 @@ fn end(id: IrqId) {
// --- Interrupts ---
/// The timer interrupt handler
///
/// This function does several things:
/// - Save the current context (registers)
/// - Choose the next thread (with the scheduler)
/// - Load the new context
#[naked]
pub extern "x86-interrupt" fn timer_interrupt_handler(stack_frame: &mut InterruptStackFrame) {
end(IrqId::TIMER);
// 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() as usize, stack_frame.stack_pointer.as_u64() as usize)));
// Save the current context
if !FIRST_TIME.load(Ordering::Relaxed) {
unsafe {
// Save the stack frame
llvm_asm!("push r14 // Stack segment
push r12 // Stack pointer
push r11 // Rflags
push r15 // Code segment
push r13 // Instruction pointer"
:
: "{r13}"(stack_frame.instruction_pointer),
"{r12}"(stack_frame.stack_pointer),
"{r14}"(stack_frame.stack_segment),
"{r15}"(stack_frame.code_segment)
:
: "intel", "volatile");
// Save registers
llvm_asm!("push rax
push rbx
push rcx
push rdx
push rdi
push rsi
push r8
push r9
push r10
push r11
push r15
mov r11, cr3
push r11"
: : : : "intel", "volatile");
}
}
else {
FIRST_TIME.store(false, Ordering::Relaxed);
}
// Signal the end of the interrupt
end(IrqId::TIMER);
// Choose the next thread
if let Some(thread) = PROCESSOR.inner().scheduler.choose() {
// Change the stack to use the new context
unsafe { llvm_asm!("mov rsp, rax" : : "{rax}"(thread.kstack) : : "intel", "volatile"); }
// Load the new context
unsafe {
llvm_asm!("pop r11
mov cr3, r11
pop r11
pop r10
pop r9
pop r8
pop rsi
pop rdi
pop rdx
pop rcx
pop rbx
pop rax"
: : : : "intel", "volatile");
}
}
else {
loop {
unsafe { llvm_asm!("sti; hlt; cli"); }
}
}
}
/// The keyboard interrupt handler
......
......@@ -32,11 +32,7 @@ impl Syscall {
let content = unsafe { from_utf8(from_raw_parts(buf as *const u8, count)) }
.expect("cannot transform to utf8");
print!("{}{}",
if fd == STDIN { "Input: " }
else if fd == STDOUT { "" }
else { "Error: " },
content);
print!("{}", content);
},
_ => println!("We can't write in files for now. Sorry!"),
}
......
......@@ -77,6 +77,8 @@ pub unsafe extern fn syscall() {
push r8
push r9
push r10
push r11
mov r11, cr3
push r11"
: : : : "intel", "volatile");
......@@ -92,6 +94,8 @@ pub unsafe extern fn syscall() {
// Interrupt return
llvm_asm!("pop r11
mov cr3, r11
pop r11
pop r10
pop r9
pop r8
......
......@@ -30,7 +30,34 @@ impl Syscall {
crate::memory::switch_to_kernel_page_table();
PROCESSOR.inner().scheduler.remove();
PROCESSOR.tick(None);
if let Some(thread) = PROCESSOR.inner().scheduler.choose() {
// Change the stack to use the new context
unsafe { llvm_asm!("mov rsp, rax" : : "{rax}"(thread.kstack) : : "intel", "volatile"); }
// Load the new context
unsafe {
llvm_asm!("pop r11
mov cr3, r11
pop r11
pop r10
pop r9
pop r8
pop rsi
pop rdi
pop rdx
pop rcx
pop rbx
pop rax
iretq"
: : : : "intel", "volatile");
}
}
else {
loop {
unsafe { llvm_asm!("sti; hlt; cli"); }
}
}
0
}
}
......@@ -28,7 +28,7 @@ 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(Debug, Clone)]
#[derive(Clone)]
/// The context of a process (its registers and its stack).
pub struct Context {
regs: Registers,
......@@ -111,9 +111,12 @@ impl Context {
#[allow(dead_code)]
#[repr(packed)]
#[derive(PartialEq, Clone, Copy, Debug)]
#[derive(Clone, Copy)]
/// A sructure which contains all registers of the CPU
pub struct Registers {
/// The CR3 register (which contains the page table)
pub cr3: usize,
/// The R11 register
pub r11: usize,
......@@ -163,21 +166,22 @@ pub struct Registers {
impl core::default::Default for Registers {
fn default() -> Self {
Self {
r11: 0x0,
r10: 0x0,
r9: 0x0,
r8: 0x0,
rsi: 0x0,
rdi: 0x0,
rdx: 0x0,
rcx: 0x0,
rbx: 0x0,
rax: 0x0,
rip: 0x0,
cs: 0x0,
cr3: 0x0,
r11: 0x0,
r10: 0x0,
r9: 0x0,
r8: 0x0,
rsi: 0x0,
rdi: 0x0,
rdx: 0x0,
rcx: 0x0,
rbx: 0x0,
rax: 0x0,
rip: 0x0,
cs: 0x0,
rflags: 0x0,
rsp: 0x0,
ds: 0x0,
rsp: 0x0,
ds: 0x0,
}
}
}
......@@ -105,12 +105,15 @@ impl ProcessInfo {
}
}
struct StackWriter {
sp: usize,
/// Helper struct to write on the stack
pub struct StackWriter {
/// The stack pointer
pub sp: usize,
}
impl StackWriter {
fn push_slice<T: Copy>(&mut self, vs: &[T]) {
/// Write a slice on the stack
pub fn push_slice<T: Copy>(&mut self, vs: &[T]) {
use core::{
mem::{align_of, size_of},
slice,
......@@ -119,7 +122,9 @@ impl StackWriter {
self.sp -= self.sp % align_of::<T>();
unsafe { slice::from_raw_parts_mut(self.sp as *mut T, vs.len()) }.copy_from_slice(vs);
}
fn push_str(&mut self, s: &str) {
/// Write a string on the stack
pub fn push_str(&mut self, s: &str) {
self.push_slice(&[b'\0']);
self.push_slice(s.as_bytes());
}
......
......@@ -81,9 +81,10 @@ impl Process {
pid: Pid(NEXT_PID.fetch_add(1, Ordering::Relaxed)),
state: ProcessState::Alive,
memory: ProcessMemory::new(),
info,
info: info.clone(),
threads: vec![],
};
let cr3 = process.memory.table.frame().start_address().as_u64() as usize;
// Add the mappings in the process' page table
process.memory.mappings(mappings.clone(), Flags::PRESENT | Flags::WRITABLE, Flags::PRESENT | Flags::USER_ACCESSIBLE);
......@@ -95,13 +96,14 @@ impl Process {
processes.insert(process.pid, process.clone());
// Create a new thread
let thread = Thread::new(process.pid, rip, rsp);
let (thread, mappings) = Thread::new(process.pid, rip, rsp, cr3, info.args.len(), info.first_arg_addr);
// 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);
processes.get_mut(&process.pid).unwrap().memory.mappings(mappings, Flags::PRESENT | Flags::WRITABLE | Flags::NO_EXECUTE, Flags::PRESENT | Flags::USER_ACCESSIBLE);
process.pid
}
......
......@@ -20,7 +20,10 @@
//! directory, to implement the trait `SchedulerAlgo` on a struct and to create a feature to switch
//! between your scheduler algorithm and others.
use lazy_static::lazy_static;
use core::cell::UnsafeCell;
use core::{
cell::UnsafeCell,
sync::atomic::AtomicBool,
};
use alloc::{
prelude::v1::Box,
collections::btree_map::BTreeMap,
......@@ -41,13 +44,13 @@ lazy_static! {
pub static ref PROCESSES: RwLock<BTreeMap<Pid, Process>> = RwLock::new(BTreeMap::new());
}
/// Whether or not it is the first time that the processor is running
pub static FIRST_TIME: AtomicBool = AtomicBool::new(true);
/// The inner of the Processor
pub struct ProcessorInner {
/// The scheduler which chooses the next thread
pub scheduler: Box<dyn SchedulerAlgo>,
/// Whether or not it is the first time that the processor is running
first_time: bool,
}
/// A Processor which includes a context dispatcher (to change the context of the thread) and a
......@@ -72,112 +75,6 @@ impl Processor {
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<(usize, usize)>) {
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: usize, rsp: usize, 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
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!();
}
}
impl ProcessorInner {
......@@ -185,7 +82,6 @@ impl ProcessorInner {
pub fn new(scheduler: Box<dyn SchedulerAlgo>) -> Self {
Self {
scheduler,
first_time: true,
}
}
}
......@@ -15,15 +15,40 @@
* along with this program. If not, see https://www.gnu.org/licenses.
*/
//! Definition of a thread which will be chosen (by the scheduler) and executed
use core::sync::atomic::{AtomicUsize, Ordering};
use core::{
sync::atomic::{AtomicUsize, Ordering},
mem::{transmute, size_of},
};
use alloc::{
prelude::v1::Box,
alloc::{
Layout,
GlobalAlloc,
},
};
use x86_64::{
VirtAddr,
structures::paging::{Page, Mapper, Size4KiB},
};
use super::{
context::Context,
context::Registers,
process::Pid,
info::StackWriter,
};
use crate::{
gdt,
memory::{
heap::HEAP_ALLOCATOR,
KERNEL_MAPPER,
Mappings,
},
};
static NEXT_TID: AtomicUsize = AtomicUsize::new(0);
const KSTACK_SIZE: usize = 4096;
#[derive(Debug, Clone, Copy, PartialEq, Eq, PartialOrd, Ord)]
/// A thread ID
pub struct Tid(usize);
......@@ -34,24 +59,49 @@ pub struct Thread {
/// The thread ID
pub tid: Tid,
/// The context of the thread
pub ctx: Context,
/// Whether or not it is the first time that the thread is chosen by the scheduler
pub first_time: bool,
/// The process which have this thread
pub process: Pid,
/// Kernel stack used to store the thread's context
pub kstack: usize,
}
impl Thread {
/// Create a new Thread
pub fn new(process: Pid, rip: usize, rsp: usize) -> Self {
Self {
pub fn new(process: Pid, rip: usize, rsp: usize, cr3: usize, argc: usize, argv: u64) -> (Self, Mappings) {
let kstack_pointer = Box::into_raw(unsafe { Box::from_raw(HEAP_ALLOCATOR.alloc(Layout::from_size_align_unchecked(KSTACK_SIZE, 4096)) as *mut [u8; KSTACK_SIZE]) }) as u64 + KSTACK_SIZE as u64;
// Create a new set of registers
let mut regs = Registers::default();
// Set the arguments
regs.rdi = argc;
regs.rsi = argv as usize;
// Create a new stack frame
regs.rip = rip;
regs.rsp = rsp;
regs.rflags = 1 << 9;
regs.cs = gdt::GDT_USER_CODE << 3 | 3;
regs.ds = gdt::GDT_USER_DATA << 3 | 3;
regs.cr3 = cr3;
// Push the registers in the kernel's stack
let mut writer = StackWriter { sp: kstack_pointer as usize };
writer.push_slice(unsafe { &transmute::<Registers, [usize; size_of::<Registers>()/8]>(regs) });
let sp = writer.sp;
let page = Page::<Size4KiB>::containing_address(VirtAddr::new(sp as u64));
let mapping = vec![(KERNEL_MAPPER.r#try().unwrap().lock().translate_page(page).unwrap(), page)];
(Self {
tid: Tid(NEXT_TID.fetch_add(1, Ordering::Relaxed)),
ctx: Context::new(rip, rsp),
first_time: true,
process,
}
kstack: sp,
}, mapping)
}
}
......@@ -2,6 +2,7 @@
#![no_std]
use core::panic::PanicInfo;
use core::fmt::{Write, Result};
use core::sync::atomic::{Ordering, AtomicUsize};
const SYSCALL_WRITE: usize = 1;
......@@ -36,12 +37,15 @@ macro_rules! println {
($fmt:expr, $($arg:tt)*) => ($crate::print!(
concat!($fmt, "\n"), $($arg)*));
}
pub static COUNTER: AtomicUsize = AtomicUsize::new(0);
#[no_mangle]
pub extern "C" fn main(_: i32, _: *const *const i8) -> i32 {
// This function is interrupted by the timer interrupt handler but it resumes at the same place
// because the RIP and RSP registers are saved
// because the context is saved
loop {
print!(".");
print!("{}", COUNTER.fetch_add(1, Ordering::Relaxed));
}
}
......
Markdown is supported
0% or
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment