Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Thread local storage #339

Merged
merged 26 commits into from
Aug 12, 2019
Merged
Show file tree
Hide file tree
Changes from 1 commit
Commits
Show all changes
26 commits
Select commit Hold shift + click to select a range
6c489ef
Kernel: TLS allocation
Orycterope Jun 8, 2019
7664199
Kernel: GDT array, fixed descriptors
Orycterope Jun 9, 2019
f2e7bda
Kernel: thread entrypoint fastcall ABI
Orycterope Jun 13, 2019
c47c047
Kernel: thread argument ABI + main thread handle
Orycterope Jun 14, 2019
d1b9b6f
Libuser: threads module
Orycterope Jun 13, 2019
145e24c
Libuser: save main thread's handle in TLS
Orycterope Jun 17, 2019
497f027
Shell: fix test_threads
Orycterope Jun 14, 2019
fdae89f
Kernel: MAIN_TSS safety
Orycterope Jul 9, 2019
c610eb9
Kernel: use 2 TLS segments
Orycterope Jul 9, 2019
03472ce
Kernel: panic takes a panic origin
Orycterope Jul 12, 2019
d391598
Kernel: generate_trap_gate_handler! macro
Orycterope Jul 17, 2019
1b10702
Kernel: syscalls use generate_trap_gate_handler!
Orycterope Jul 18, 2019
6ec33cd
Kernel: irqs use generate_trap_gate_handler!
Orycterope Jul 18, 2019
cbdda3f
Kernel: cpu_locals
Orycterope Jul 18, 2019
984b4a0
Kernel: CURRENT_THREAD is cpu-local
Orycterope Jul 20, 2019
f801f18
Libuser: #[thread_local]
Orycterope Jul 20, 2019
36db582
Merge remote-tracking branch 'upstream/master' into thread_local_storage
Orycterope Aug 1, 2019
55ccdf9
Kernel: fixup trap_gate_asm! constraint
Orycterope Jul 22, 2019
526810c
Kernel: open issue "per-cpu TSS / GDT"
Orycterope Jul 30, 2019
23b7729
Kernel: document MAIN_TASK locking guarantees
Orycterope Jul 31, 2019
0ee8da1
Kernel: open issue read_cr3 arch-independent
Orycterope Jul 31, 2019
e9420ca
Libuser: remove __start_tls__ from module_header
Orycterope Aug 5, 2019
aa1b5eb
TLS: safety notices
Orycterope Aug 5, 2019
7727135
Merge branch 'master' into thread_local_storage
Orycterope Aug 5, 2019
7c4efbb
TLS: fixup safety notices
Orycterope Aug 12, 2019
722e53f
Libuser: svcCreateThread is unsafe
Orycterope Aug 12, 2019
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
376 changes: 244 additions & 132 deletions kernel/src/i386/gdt.rs

Large diffs are not rendered by default.

112 changes: 36 additions & 76 deletions kernel/src/i386/mod.rs
Original file line number Diff line number Diff line change
Expand Up @@ -4,9 +4,6 @@
#![cfg(any(target_arch = "x86", test, rustdoc))]
#![allow(dead_code)]

use alloc::boxed::Box;
use core::ops::{Deref, DerefMut};

pub mod acpi;

#[macro_use]
Expand Down Expand Up @@ -291,7 +288,17 @@ impl PrivilegeLevel {
/// information about a task. The TSS is primarily suited for hardware multitasking,
/// where each individual process has its own TSS.
/// ([see OSDEV](https://wiki.osdev.org/TSS))
#[repr(C)]
#[repr(C, align(128))] // According to the IA32-E PDF, volume 3, 7.2.1:
// If paging is used:
// - Avoid placing a page boundary in the part of the TSS that the processor
// reads during a task switch (the first 104 bytes). The processor may not
// correctly perform address translations if a boundary occurs in this area.
// During a task switch, the processor reads and writes into the first 104
// bytes of each TSS (using contiguous physical addresses beginning with the
// physical address of the first byte of the TSS). So, after TSS access
// begins, if part of the 104 bytes is not physically contiguous, the
// processor will access incorrect information without generating a
// page-fault exception.
#[derive(Copy, Clone, Debug)]
#[allow(missing_docs, clippy::missing_docs_in_private_items)]
pub struct TssStruct {
Expand Down Expand Up @@ -335,8 +342,17 @@ pub struct TssStruct {
pub iopboffset: u16,
}

impl Default for TssStruct {
fn default() -> TssStruct {
impl TssStruct {
/// Creates an empty TssStruct.
///
/// All fields are set to `0`, suitable for static declarations, so that it can live in the `.bss`.
///
/// The TssStruct must then be initialized with [init].
///
/// Note that until it is initialized properly, the `.iopboffset` field will be invalid.
///
/// [init]: TssStruct::init
pub const fn empty() -> TssStruct {
roblabla marked this conversation as resolved.
Show resolved Hide resolved
TssStruct {
_reserved1: 0,
link: 0,
Expand Down Expand Up @@ -374,40 +390,23 @@ impl Default for TssStruct {
gs: 0,
_reservedb: 0,
ldt_selector: 0,
iopboffset: ::core::mem::size_of::<TssStruct>() as u16,
iopboffset: 0,
_reservedc: 0,
}
}
}

const_assert_eq!(::core::mem::size_of::<TssStruct>(), 0x68);

impl TssStruct {
/// Creates a new TssStruct.
/// Fills the TSS.
///
/// The new struct inherits the current task's values (except registers, which are set to 0)
pub fn new() -> TssStruct {
let ds: u16;
let cs: u16;
let ss: u16;
let cr3: u32;
let ldt_selector: u16;

unsafe {
// Safety: this is perfectly safe. Maybe I should do safe wrappers for this however...
asm!("
mov AX, DS
mov $0, AX
mov AX, CS
mov $1, AX
mov AX, SS
mov $2, AX
mov $3, CR3
sldt $4
" : "=r"(ds), "=r"(cs), "=r"(ss), "=r"(cr3), "=r"(ldt_selector) :: "ax" : "intel");
}

TssStruct {
/// The TSS is filled with kernel segments selectors, and the current cr3.
/// Registers are set to 0.
pub fn init(&mut self) {
let ds = gdt::GdtIndex::KData.selector().0;
let cs = gdt::GdtIndex::KCode.selector().0;
let ss = gdt::GdtIndex::KStack.selector().0;
let cr3 = crate::paging::read_cr3().addr() as u32;
let ldt_selector = gdt::GdtIndex::LDT.selector().0;

*self = TssStruct {
ss0: ss,
ss1: ss,
ss2: ss,
Expand All @@ -419,7 +418,8 @@ impl TssStruct {
ds: ds,
fs: ds,
gs: ds,
..TssStruct::default()
iopboffset: ::core::mem::size_of::<TssStruct>() as u16,
..TssStruct::empty()
}
}

Expand All @@ -440,43 +440,3 @@ impl TssStruct {
self.eip = eip;
}
}

/// Wrapper around TssStruct ensuring it is kept at the page boundary.
///
/// According to the IA32-E PDF, volume 3, 7.2.1:
///
/// If paging is used:
/// - Avoid placing a page boundary in the part of the TSS that the processor
/// reads during a task switch (the first 104 bytes). The processor may not
/// correctly perform address translations if a boundary occurs in this area.
/// During a task switch, the processor reads and writes into the first 104
/// bytes of each TSS (using contiguous physical addresses beginning with the
/// physical address of the first byte of the TSS). So, after TSS access
/// begins, if part of the 104 bytes is not physically contiguous, the
/// processor will access incorrect information without generating a
/// page-fault exception.
#[derive(Debug)]
#[repr(C, align(4096))]
pub struct AlignedTssStruct(TssStruct);

impl AlignedTssStruct {
/// Create a new AlignedTssStruct, using boxing to avoid putting a ridiculously large
/// object (4kb) on the stack.
pub fn new(tss: TssStruct) -> Box<AlignedTssStruct> {
box AlignedTssStruct(tss)
}
}

impl Deref for AlignedTssStruct {
type Target = TssStruct;

fn deref(&self) -> &TssStruct {
&self.0
}
}

impl DerefMut for AlignedTssStruct {
fn deref_mut(&mut self) -> &mut TssStruct {
&mut self.0
}
}
59 changes: 35 additions & 24 deletions kernel/src/i386/process_switch.rs
Original file line number Diff line number Diff line change
Expand Up @@ -3,11 +3,10 @@
//! This modules describe low-level functions and structures needed to perform a process switch

use crate::process::ThreadStruct;
use crate::i386::gdt;
use alloc::sync::Arc;
use core::mem::size_of;
use crate::i386::TssStruct;
use crate::i386::gdt::update_userspace_tls;
use crate::i386::gdt::{GDT, MAIN_TASK};
use crate::i386::gdt::GdtIndex;

/// The hardware context of a paused thread. It contains just enough registers to get the thread
/// running again.
Expand Down Expand Up @@ -102,8 +101,12 @@ pub unsafe extern "C" fn process_switch(thread_b: Arc<ThreadStruct>, thread_curr
// Switch the memory pages
thread_b_lock_pmemory.switch_to();

// Reload the TLS
update_userspace_tls(thread_b.tls);
// Update the TLS segments. They are not loaded yet.
let mut gdt = GDT
.r#try().expect("GDT not initialized")
.try_lock().expect("Could not lock GDT");
gdt.table[GdtIndex::UTlsRegion as usize].set_base(thread_b.tls.addr() as u32);
gdt.commit(None, None, None, None, None, None);

let current_esp: usize;
asm!("mov $0, esp" : "=r"(current_esp) : : : "intel", "volatile");
Expand All @@ -127,11 +130,13 @@ pub unsafe extern "C" fn process_switch(thread_b: Arc<ThreadStruct>, thread_curr

// Set IOPB back to "nothing allowed" state
// todo do not change iopb if thread_b belongs to the same process.
let iopb = gdt::get_main_iopb();
let mut main_tss = MAIN_TASK.try_lock()
.expect("Cannot lock main tss");
Orycterope marked this conversation as resolved.
Show resolved Hide resolved
for ioport in &thread_current.process.capabilities.ioports {
let ioport = *ioport as usize;
iopb[ioport / 8] = 0xFF;
main_tss.iopb[ioport / 8] = 0xFF;
}
drop(main_tss);

// current is still stored in scheduler's global CURRENT_PROCESS, so it's not dropped yet.
drop(thread_current);
Expand Down Expand Up @@ -178,14 +183,16 @@ pub unsafe extern "C" fn process_switch(thread_b: Arc<ThreadStruct>, thread_curr
// recreate the Arc to our ThreadStruct from the pointer that was passed to us
let me = unsafe { Arc::from_raw(whoami) };

let mut main_tss = MAIN_TASK.try_lock()
.expect("Cannot lock main tss");

// Set the ESP0
let tss = gdt::MAIN_TASK.addr() as *mut TssStruct;
(*tss).esp0 = me.kstack.get_stack_start() as u32;
main_tss.tss.esp0 = me.kstack.get_stack_start() as u32;

// Set IOPB
for ioport in &me.process.capabilities.ioports {
let ioport = *ioport as usize;
iopb[ioport / 8] &= !(1 << (ioport % 8));
main_tss.iopb[ioport / 8] &= !(1 << (ioport % 8));
}

me
Expand Down Expand Up @@ -278,23 +285,21 @@ fn first_schedule() {
// reconstruct an Arc to our ProcessStruct from the leaked pointer
let current = unsafe { Arc::from_raw(whoami) };

let mut main_tss = MAIN_TASK.try_lock()
.expect("Cannot lock main tss");

// Set the ESP0
let tss = gdt::MAIN_TASK.addr() as *mut TssStruct;
unsafe {
// Safety: TSS is always valid.
(*tss).esp0 = current.kstack.get_stack_start() as u32;
}
main_tss.tss.esp0 = current.kstack.get_stack_start() as u32;

// todo do not touch iopb if we come from a thread of the same process.
// Set IOPB
let iopb = unsafe {
gdt::get_main_iopb()
};
for ioport in &current.process.capabilities.ioports {
let ioport = *ioport as usize;
iopb[ioport / 8] &= !(1 << (ioport % 8));
main_tss.iopb[ioport / 8] &= !(1 << (ioport % 8));
}

drop(main_tss); // unlock it

// call the scheduler to finish the high-level process switch mechanics
crate::scheduler::scheduler_first_schedule(current, || jump_to_entrypoint(entrypoint, userspace_stack, userspace_arg));

Expand All @@ -315,20 +320,26 @@ fn first_schedule() {
/// This way, just after the `iret`, cpu will be in ring 3, witl all of its registers cleared,
/// `$eip` pointing to `ep`, and `$esp` pointing to `userspace_stack_ptr`.
fn jump_to_entrypoint(ep: usize, userspace_stack_ptr: usize, arg: usize) -> ! {
// gonna write constants in the code, cause not enough registers.
Orycterope marked this conversation as resolved.
Show resolved Hide resolved
// just check we aren't hard-coding the wrong values.
const_assert_eq!((GdtIndex::UCode as u16) << 3 | 0b11, 0x2B);
const_assert_eq!((GdtIndex::UData as u16) << 3 | 0b11, 0x33);
const_assert_eq!((GdtIndex::UTlsRegion as u16) << 3 | 0b11, 0x3B);
const_assert_eq!((GdtIndex::UStack as u16) << 3 | 0b11, 0x4B);
unsafe {
asm!("
mov ax,0x2B // Set data segment selector to Userland Data, Ring 3
mov ax,0x33 // ds, es <- UData, Ring 3
mov ds,ax
mov es,ax
mov ax,0x3B // fs <- UTlsRegion, Ring 3
mov fs,ax
// gs is set to the userspace TLS region earlier in the call stack (see process_switch's
// call to update_userspace_tls.
mov gs,ax

// Build the fake stack for IRET
push 0x33 // Userland Stack, Ring 3
push 0x4B // Userland Stack, Ring 3
push $1 // Userspace ESP
pushfd
push 0x23 // Userland Code, Ring 3
push 0x2B // Userland Code, Ring 3
push $0 // Entrypoint

// Clean up all registers. Also setup arguments.
Expand Down
40 changes: 8 additions & 32 deletions kernel/src/i386/structures/idt.rs
Original file line number Diff line number Diff line change
Expand Up @@ -14,11 +14,8 @@ use core::marker::PhantomData;
use core::mem;
use core::ops::{Index, IndexMut};
use bit_field::BitField;
use crate::i386::{AlignedTssStruct, TssStruct, PrivilegeLevel};
use crate::i386::PrivilegeLevel;
use crate::mem::VirtualAddress;
use crate::paging::{PAGE_SIZE, kernel_memory::get_kernel_memory};
use alloc::boxed::Box;
use crate::i386::gdt;
use crate::i386::structures::gdt::SegmentSelector;

/// An Interrupt Descriptor Table with 256 entries.
Expand Down Expand Up @@ -589,25 +586,16 @@ impl<F> IdtEntry<F> {

/// Set a task gate for the IDT entry and sets the present bit.
///
/// For the code selector field, this function uses the code segment selector currently
/// active in the CPU.
pub fn set_handler_task_gate_addr(&mut self, addr: u32) {
/// # Unsafety
///
/// `tss_selector` must point to a valid TSS, which will remain present.
/// The TSS' `eip` should point to the handler function.
/// The TSS' `esp` and `esp0` should point to a usable stack for the handler function.
pub unsafe fn set_handler_task_gate(&mut self, tss_selector: SegmentSelector) {

self.pointer_low = 0;
self.pointer_high = 0;

let stack = get_kernel_memory().get_page();

// Load tss segment with addr in IP.
let mut tss = AlignedTssStruct::new(TssStruct::new());
//tss.ss0 = SegmentSelector(3 << 3);
tss.esp0 = (stack.addr() + PAGE_SIZE) as u32;
tss.esp = (stack.addr() + PAGE_SIZE) as u32;
tss.eip = addr;

let tss = Box::leak(tss);

self.gdt_selector = gdt::push_task_segment(tss);
self.gdt_selector = tss_selector;
self.options.set_present_task(true);
}
}
Expand All @@ -628,18 +616,6 @@ macro_rules! impl_set_handler_fn {
self.set_interrupt_gate_addr(handler as u32)
}
}

/// Set a task gate function for the IDT entry and sets the present bit.
///
/// For the code selector field, this function uses the code segment selector currently
/// active in the CPU.
///
/// The function returns a mutable reference to the entry's options that allows
/// further customization.
#[allow(clippy::fn_to_numeric_cast)] // it **is** a u32
pub fn set_task_fn(&mut self, handler: $h) {
self.set_handler_task_gate_addr(handler as u32)
}
}
}
}
Expand Down
Loading