Compare commits

..

19 Commits

Author SHA1 Message Date
Philipp Oppermann
3fc7bf6aa1 Fix register typo: rpb -> rbp
Fixes #746
2020-02-16 18:02:24 +01:00
Philipp Oppermann
b337f65abb Add a yield_now function and use it in idle thread 2020-01-28 12:28:50 +01:00
Philipp Oppermann
22c6bd5aa7 Add idle thread and begin support for blocking 2020-01-28 12:22:37 +01:00
Philipp Oppermann
87719f2260 Switch current_thread_id before context switch 2020-01-28 11:29:21 +01:00
Philipp Oppermann
0caf5c351e Run cargo fmt 2020-01-23 14:24:13 +01:00
Philipp Oppermann
cb7bb0ddef Refactor and rewrite 2020-01-23 14:22:29 +01:00
Philipp Oppermann
7ad30651fb Rename allocator.rs to allocator/mod.rs 2020-01-23 11:46:45 +01:00
Philipp Oppermann
49923acb3f Print thread id instead of hardcoding numbers 2020-01-23 11:03:15 +01:00
Philipp Oppermann
f2b1f3a593 Fix handling of current thread id 2020-01-23 10:49:51 +01:00
Philipp Oppermann
5e2e0b629e Refactor threading code 2020-01-23 10:42:37 +01:00
Philipp Oppermann
35379c90e6 Force unlock writer on panic to avoid deadlocks 2020-01-23 10:42:19 +01:00
Philipp Oppermann
e5d10fcaec Increase stack size to avoid stack overflow 2020-01-23 10:41:59 +01:00
Philipp Oppermann
e1242a867f Move global_asm inline in threads module 2020-01-23 09:20:17 +01:00
Philipp Oppermann
cd138a3a1b Rename multitasking module to threads 2020-01-23 09:19:38 +01:00
Philipp Oppermann
11a0eb679c Fix race condition
The first timer interrupt might occur before the heap is initialized. With lazy_static, this causes an allocation failure since the VecDeque is allocated when it's accessed the first time. This commit fixes this by only initializing VecDeque in `add_thread`.
2020-01-23 08:50:35 +01:00
Philipp Oppermann
241c1ab2c9 Add support for closures 2020-01-23 08:24:48 +01:00
Philipp Oppermann
b75406b37e Add new modules 2020-01-22 17:24:17 +01:00
Philipp Oppermann
c3450b6df7 Refactor a bit 2020-01-22 16:33:23 +01:00
Philipp Oppermann
ce1fdcf768 Wip 2020-01-22 16:15:23 +01:00
16 changed files with 564 additions and 265 deletions

162
Cargo.lock generated
View File

@@ -1,10 +1,13 @@
# This file is automatically @generated by Cargo.
# It is not intended for manual editing.
[[package]]
name = "autocfg"
version = "1.0.0"
name = "array-init"
version = "0.0.4"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "f8aac770f1885fd7e387acedd76065302551364496e46b3dd00860b2f8359b9d"
checksum = "23589ecb866b460d3a0f1278834750268c607e8e28a1b982c907219f3178cd72"
dependencies = [
"nodrop",
]
[[package]]
name = "bit_field"
@@ -23,9 +26,6 @@ name = "blog_os"
version = "0.1.0"
dependencies = [
"bootloader",
"conquer-once",
"crossbeam-queue",
"futures-util",
"lazy_static",
"linked_list_allocator",
"pc-keyboard",
@@ -33,83 +33,30 @@ dependencies = [
"spin",
"uart_16550",
"volatile",
"x86_64",
"x86_64 0.8.1",
]
[[package]]
name = "bootloader"
version = "0.8.8"
version = "0.8.3"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "d596849a47f28abdea62d7a6a25c4f6e69c3d9b09b0a2877db6e9cda004ca993"
[[package]]
name = "cfg-if"
version = "0.1.10"
name = "cast"
version = "0.2.3"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "4785bdd1c96b2a846b2bd7cc02e86b6b3dbf14e7e53446c4f54c92a361040822"
[[package]]
name = "conquer-once"
version = "0.2.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "6f7644600a548ecad74e4a918392af1798f7dd045be610be3203b9e129b4f98f"
checksum = "4b9434b9a5aa1450faa3f9cb14ea0e8c53bb5d2b3c1bfd1ab4fc03e9f33fbfb0"
dependencies = [
"conquer-util",
"rustc_version",
]
[[package]]
name = "conquer-util"
version = "0.2.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "654fb2472cc369d311c547103a1fa81d467bef370ae7a0680f65939895b1182a"
[[package]]
name = "cpuio"
version = "0.2.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "22b8e308ccfc5acf3b82f79c0eac444cf6114cb2ac67a230ca6c177210068daa"
[[package]]
name = "crossbeam-queue"
version = "0.2.1"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "c695eeca1e7173472a32221542ae469b3e9aac3a4fc81f7696bcad82029493db"
dependencies = [
"cfg-if",
"crossbeam-utils",
]
[[package]]
name = "crossbeam-utils"
version = "0.7.2"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "c3c7c73a2d1e9fc0886a08b93e98eb643461230d5f1925e4036204d5f2e261a8"
dependencies = [
"autocfg",
"cfg-if",
]
[[package]]
name = "futures-core"
version = "0.3.4"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "f25592f769825e89b92358db00d26f965761e094951ac44d3663ef25b7ac464a"
[[package]]
name = "futures-task"
version = "0.3.4"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "7b0a34e53cf6cdcd0178aa573aed466b646eb3db769570841fda0c7ede375a27"
[[package]]
name = "futures-util"
version = "0.3.4"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "22766cf25d64306bedf0384da004d05c9974ab104fcc4528f1236181c18004c5"
dependencies = [
"futures-core",
"futures-task",
"pin-utils",
]
[[package]]
name = "lazy_static"
version = "1.4.0"
@@ -121,27 +68,24 @@ dependencies = [
[[package]]
name = "linked_list_allocator"
version = "0.8.0"
version = "0.6.4"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "18c618c431dfe4419afbe22852f6aceffbc17bd82ba0a18b982def291000824c"
checksum = "47314ec1d29aa869ee7cb5a5be57be9b1055c56567d59c3fb6689926743e0bea"
dependencies = [
"spinning_top",
"spin",
]
[[package]]
name = "lock_api"
version = "0.3.3"
name = "nodrop"
version = "0.1.14"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "79b2de95ecb4691949fea4716ca53cdbcfccb2c612e19644a8bad05edcf9f47b"
dependencies = [
"scopeguard",
]
checksum = "72ef4a56884ca558e5ddb05a1d1e7e1bfd9a68d9ed024c21704cc98872dae1bb"
[[package]]
name = "pc-keyboard"
version = "0.5.0"
version = "0.3.1"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "c48392db76c4e9a69e0b3be356c5f97ebb7b14413c5e4fd0af4755dbf86e2fce"
checksum = "fff50ab09ba31bcebc0669f4e64c0952fae1acdca9e6e0587e68e4e8443808ac"
[[package]]
name = "pic8259_simple"
@@ -153,16 +97,28 @@ dependencies = [
]
[[package]]
name = "pin-utils"
version = "0.1.0-alpha.4"
name = "rustc_version"
version = "0.2.3"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "5894c618ce612a3fa23881b152b608bafb8c56cfc22f434a3ba3120b40f7b587"
checksum = "138e3e0acb6c9fb258b19b67cb8abd63c00679d2851805ea151465464fe9030a"
dependencies = [
"semver",
]
[[package]]
name = "scopeguard"
version = "1.1.0"
name = "semver"
version = "0.9.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "d29ab0c6d3fc0ee92fe66e2d99f700eab17a8d57d1c1d3b748380fb20baa78cd"
checksum = "1d7eb9ef2c18661902cc47e535f9bc51b78acd254da71d375c2f6720d9a40403"
dependencies = [
"semver-parser",
]
[[package]]
name = "semver-parser"
version = "0.7.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "388a1df253eca08550bef6c72392cfe7c30914bf41df5269b68cbd6ff8f570a3"
[[package]]
name = "spin"
@@ -171,23 +127,20 @@ source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "6e63cff320ae2c57904679ba7cb63280a3dc4613885beafb148ee7bf9aa9042d"
[[package]]
name = "spinning_top"
version = "0.1.0"
name = "uart_16550"
version = "0.2.1"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "32d801a3a53bcf5071f85fef8d5cab9e5f638fc5580a37e6eb7aba4b37438d24"
checksum = "803ea8cb602dbb32c1a657a866d2dd79fe7dbeab0fb2ac667cb4dcc7de12a58b"
dependencies = [
"lock_api",
"bitflags",
"x86_64 0.7.7",
]
[[package]]
name = "uart_16550"
version = "0.2.4"
name = "ux"
version = "0.1.3"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "d44b0f30cb82b0fbc15b78ade1064226529ad52028bc8cb8accb98ff6f3d7131"
dependencies = [
"bitflags",
"x86_64",
]
checksum = "88dfeb711b61ce620c0cb6fd9f8e3e678622f0c971da2a63c4b3e25e88ed012f"
[[package]]
name = "volatile"
@@ -197,10 +150,25 @@ checksum = "6af0edf5b4faacc31fc51159244d78d65ec580f021afcef7bd53c04aeabc7f29"
[[package]]
name = "x86_64"
version = "0.9.6"
version = "0.7.7"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "4206b60c9f99766329b66962aa8ddc01df6c7edd02edc046b7a69d5df9fcdbcf"
checksum = "1f27d9168654aee1b0c1b73746caeb4aa33248f8b8c8f6e100e697fcc2a794b2"
dependencies = [
"array-init",
"bit_field",
"bitflags",
"cast",
"ux",
]
[[package]]
name = "x86_64"
version = "0.8.1"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "f21672dcbed52bc09eea030d189600c0189c66c97bc5b31779eb780e064a201f"
dependencies = [
"array-init",
"bit_field",
"bitflags",
"cast",
]

View File

@@ -16,29 +16,16 @@ harness = false
bootloader = { version = "0.8.0", features = ["map_physical_memory"]}
volatile = "0.2.6"
spin = "0.5.2"
x86_64 = "0.9.6"
x86_64 = "0.8.1"
uart_16550 = "0.2.0"
pic8259_simple = "0.1.1"
pc-keyboard = "0.5.0"
linked_list_allocator = "0.8.0"
pc-keyboard = "0.3.1"
linked_list_allocator = "0.6.4"
[dependencies.lazy_static]
version = "1.0"
features = ["spin_no_std"]
[dependencies.crossbeam-queue]
version = "0.2.1"
default-features = false
features = ["alloc"]
[dependencies.conquer-once]
version = "0.2.0"
default-features = false
[dependencies.futures-util]
version = "0.3.4"
default-features = false
features = ["alloc", "async-await"]
[package.metadata.bootimage]
test-args = [
@@ -46,3 +33,6 @@ test-args = [
"-display", "none"
]
test-success-exit-code = 33 # (0x10 << 1) | 1
[profile.release]
lto = true

View File

@@ -26,7 +26,7 @@ impl BumpAllocator {
/// memory range is unused. Also, this method must be called only once.
pub unsafe fn init(&mut self, heap_start: usize, heap_size: usize) {
self.heap_start = heap_start;
self.heap_end = heap_start.saturating_add(heap_size);
self.heap_end = heap_start + heap_size;
self.next = heap_start;
}
}
@@ -36,10 +36,7 @@ unsafe impl GlobalAlloc for Locked<BumpAllocator> {
let mut bump = self.lock(); // get a mutable reference
let alloc_start = align_up(bump.next, layout.align());
let alloc_end = match alloc_start.checked_add(layout.size()) {
Some(end) => end,
None => return ptr::null_mut(),
};
let alloc_end = alloc_start + layout.size();
if alloc_end > bump.heap_end {
ptr::null_mut() // out of memory

View File

@@ -86,7 +86,7 @@ impl LinkedListAllocator {
/// Returns the allocation start address on success.
fn alloc_from_region(region: &ListNode, size: usize, align: usize) -> Result<usize, ()> {
let alloc_start = align_up(region.start_addr(), align);
let alloc_end = alloc_start.checked_add(size).ok_or(())?;
let alloc_end = alloc_start + size;
if alloc_end > region.end_addr() {
// region too small
@@ -122,10 +122,10 @@ unsafe impl GlobalAlloc for Locked<LinkedListAllocator> {
unsafe fn alloc(&self, layout: Layout) -> *mut u8 {
// perform layout adjustments
let (size, align) = LinkedListAllocator::size_align(layout);
let mut allocator = self.lock();
let mut allocator = self.inner.lock();
if let Some((region, alloc_start)) = allocator.find_region(size, align) {
let alloc_end = alloc_start.checked_add(size).expect("overflow");
let alloc_end = alloc_start + size;
let excess_size = region.end_addr() - alloc_end;
if excess_size > 0 {
allocator.add_free_region(alloc_end, excess_size);
@@ -140,6 +140,6 @@ unsafe impl GlobalAlloc for Locked<LinkedListAllocator> {
// perform layout adjustments
let (size, _) = LinkedListAllocator::size_align(layout);
self.lock().add_free_region(ptr as usize, size)
self.inner.lock().add_free_region(ptr as usize, size)
}
}

View File

@@ -21,7 +21,7 @@ static ALLOCATOR: Locked<FixedSizeBlockAllocator> = Locked::new(FixedSizeBlockAl
pub fn init_heap(
mapper: &mut impl Mapper<Size4KiB>,
frame_allocator: &mut impl FrameAllocator<Size4KiB>,
) -> Result<(), MapToError<Size4KiB>> {
) -> Result<(), MapToError> {
let page_range = {
let heap_start = VirtAddr::new(HEAP_START as u64);
let heap_end = heap_start + HEAP_SIZE - 1u64;
@@ -74,9 +74,11 @@ impl<A> Locked<A> {
}
}
/// Align the given address `addr` upwards to alignment `align`.
///
/// Requires that `align` is a power of two.
fn align_up(addr: usize, align: usize) -> usize {
(addr + align - 1) & !(align - 1)
let remainder = addr % align;
if remainder == 0 {
addr // addr already aligned
} else {
addr - remainder + align
}
}

View File

@@ -77,17 +77,17 @@ extern "x86-interrupt" fn timer_interrupt_handler(_stack_frame: &mut InterruptSt
PICS.lock()
.notify_end_of_interrupt(InterruptIndex::Timer.as_u8());
}
crate::multitasking::invoke_scheduler();
}
extern "x86-interrupt" fn keyboard_interrupt_handler(_stack_frame: &mut InterruptStackFrame) {
use pc_keyboard::{layouts, DecodedKey, HandleControl, Keyboard, ScancodeSet1};
use pc_keyboard::{layouts, DecodedKey, Keyboard, ScancodeSet1};
use spin::Mutex;
use x86_64::instructions::port::Port;
lazy_static! {
static ref KEYBOARD: Mutex<Keyboard<layouts::Us104Key, ScancodeSet1>> = Mutex::new(
Keyboard::new(layouts::Us104Key, ScancodeSet1, HandleControl::Ignore)
);
static ref KEYBOARD: Mutex<Keyboard<layouts::Us104Key, ScancodeSet1>> =
Mutex::new(Keyboard::new(layouts::Us104Key, ScancodeSet1));
}
let mut keyboard = KEYBOARD.lock();

View File

@@ -6,8 +6,12 @@
#![feature(const_fn)]
#![feature(alloc_layout_extra)]
#![feature(const_in_array_repeat_expressions)]
#![feature(wake_trait)]
#![feature(async_closure)]
#![feature(global_asm)]
#![feature(asm)]
#![feature(raw)]
#![feature(never_type)]
#![feature(naked_functions)]
#![feature(option_expect_none)]
#![test_runner(crate::test_runner)]
#![reexport_test_harness_main = "test_main"]
@@ -19,8 +23,8 @@ pub mod allocator;
pub mod gdt;
pub mod interrupts;
pub mod memory;
pub mod multitasking;
pub mod serial;
pub mod task;
pub mod vga_buffer;
pub fn init() {

View File

@@ -6,7 +6,9 @@
extern crate alloc;
use blog_os::println;
use alloc::{boxed::Box, rc::Rc, vec, vec::Vec};
use blog_os::multitasking::{self, thread::Thread, with_scheduler};
use blog_os::{print, println};
use bootloader::{entry_point, BootInfo};
use core::panic::PanicInfo;
@@ -15,7 +17,6 @@ entry_point!(kernel_main);
fn kernel_main(boot_info: &'static BootInfo) -> ! {
use blog_os::allocator;
use blog_os::memory::{self, BootInfoFrameAllocator};
use blog_os::task::{simple_executor::SimpleExecutor, Task, keyboard};
use x86_64::VirtAddr;
println!("Hello World{}", "!");
@@ -27,31 +28,72 @@ fn kernel_main(boot_info: &'static BootInfo) -> ! {
allocator::init_heap(&mut mapper, &mut frame_allocator).expect("heap initialization failed");
let mut executor = SimpleExecutor::new();
executor.spawn(Task::new(example_task()));
executor.spawn(Task::new(keyboard::print_keypresses()));
executor.run();
// allocate a number on the heap
let heap_value = Box::new(41);
println!("heap_value at {:p}", heap_value);
// create a dynamically sized vector
let mut vec = Vec::new();
for i in 0..500 {
vec.push(i);
}
println!("vec at {:p}", vec.as_slice());
// create a reference counted vector -> will be freed when count reaches 0
let reference_counted = Rc::new(vec![1, 2, 3]);
let cloned_reference = reference_counted.clone();
println!(
"current reference count is {}",
Rc::strong_count(&cloned_reference)
);
core::mem::drop(reference_counted);
println!(
"reference count is {} now",
Rc::strong_count(&cloned_reference)
);
#[cfg(test)]
test_main();
let idle_thread = Thread::create(idle_thread, 2, &mut mapper, &mut frame_allocator).unwrap();
with_scheduler(|s| s.set_idle_thread(idle_thread));
for _ in 0..10 {
let thread = Thread::create(thread_entry, 2, &mut mapper, &mut frame_allocator).unwrap();
with_scheduler(|s| s.add_new_thread(thread));
}
let thread =
Thread::create_from_closure(|| thread_entry(), 2, &mut mapper, &mut frame_allocator)
.unwrap();
with_scheduler(|s| s.add_new_thread(thread));
println!("It did not crash!");
blog_os::hlt_loop();
thread_entry();
}
async fn async_number() -> u32 {
42
fn idle_thread() -> ! {
loop {
x86_64::instructions::hlt();
multitasking::yield_now();
}
}
async fn example_task() {
let number = async_number().await;
println!("async number: {}", number);
fn thread_entry() -> ! {
let thread_id = with_scheduler(|s| s.current_thread_id()).as_u64();
for _ in 0..=thread_id {
print!("{}", thread_id);
x86_64::instructions::hlt();
}
multitasking::exit_thread();
}
/// This function is called on panic.
#[cfg(not(test))]
#[panic_handler]
fn panic(info: &PanicInfo) -> ! {
unsafe {
blog_os::vga_buffer::WRITER.force_unlock();
}
println!("{}", info);
blog_os::hlt_loop();
}

View File

@@ -1,7 +1,7 @@
use bootloader::bootinfo::{MemoryMap, MemoryRegionType};
use x86_64::{
structures::paging::{
FrameAllocator, Mapper, OffsetPageTable, Page, PageTable, PhysFrame, Size4KiB,
mapper, FrameAllocator, Mapper, OffsetPageTable, Page, PageTable, PhysFrame, Size4KiB,
UnusedPhysFrame,
},
PhysAddr, VirtAddr,
@@ -36,6 +36,54 @@ unsafe fn active_level_4_table(physical_memory_offset: VirtAddr) -> &'static mut
&mut *page_table_ptr // unsafe
}
#[derive(Debug, Clone, Copy, PartialEq, Eq)]
pub struct StackBounds {
start: VirtAddr,
end: VirtAddr,
}
impl StackBounds {
pub fn start(&self) -> VirtAddr {
self.start
}
pub fn end(&self) -> VirtAddr {
self.end
}
}
pub fn alloc_stack(
size_in_pages: u64,
mapper: &mut impl Mapper<Size4KiB>,
frame_allocator: &mut impl FrameAllocator<Size4KiB>,
) -> Result<StackBounds, mapper::MapToError> {
use core::sync::atomic::{AtomicU64, Ordering};
use x86_64::structures::paging::PageTableFlags as Flags;
static STACK_ALLOC_NEXT: AtomicU64 = AtomicU64::new(0x_5555_5555_0000);
let guard_page_start = STACK_ALLOC_NEXT.fetch_add(
(size_in_pages + 1) * Page::<Size4KiB>::SIZE,
Ordering::SeqCst,
);
let guard_page = Page::from_start_address(VirtAddr::new(guard_page_start))
.expect("`STACK_ALLOC_NEXT` not page aligned");
let stack_start = guard_page + 1;
let stack_end = stack_start + size_in_pages;
let flags = Flags::PRESENT | Flags::WRITABLE;
for page in Page::range(stack_start, stack_end) {
let frame = frame_allocator
.allocate_frame()
.ok_or(mapper::MapToError::FrameAllocationFailed)?;
mapper.map_to(page, frame, flags, frame_allocator)?.flush();
}
Ok(StackBounds {
start: stack_start.start_address(),
end: stack_end.start_address(),
})
}
/// Creates an example mapping for the given page to frame `0xb8000`.
pub fn create_example_mapping(
page: Page,

View File

@@ -0,0 +1,105 @@
use super::{with_scheduler, SwitchReason};
use crate::multitasking::thread::ThreadId;
use alloc::boxed::Box;
use core::mem;
use core::raw::TraitObject;
use x86_64::VirtAddr;
pub struct Stack {
pointer: VirtAddr,
}
impl Stack {
pub unsafe fn new(stack_pointer: VirtAddr) -> Self {
Stack {
pointer: stack_pointer,
}
}
pub fn get_stack_pointer(self) -> VirtAddr {
self.pointer
}
pub fn set_up_for_closure(&mut self, closure: Box<dyn FnOnce() -> !>) {
let trait_object: TraitObject = unsafe { mem::transmute(closure) };
unsafe { self.push(trait_object.data) };
unsafe { self.push(trait_object.vtable) };
self.set_up_for_entry_point(call_closure_entry);
}
pub fn set_up_for_entry_point(&mut self, entry_point: fn() -> !) {
unsafe { self.push(entry_point) };
let rflags: u64 = 0x200;
unsafe { self.push(rflags) };
}
unsafe fn push<T>(&mut self, value: T) {
self.pointer -= core::mem::size_of::<T>();
let ptr: *mut T = self.pointer.as_mut_ptr();
ptr.write(value);
}
}
pub unsafe fn context_switch_to(
new_stack_pointer: VirtAddr,
prev_thread_id: ThreadId,
switch_reason: SwitchReason,
) {
asm!(
"call asm_context_switch"
:
: "{rdi}"(new_stack_pointer), "{rsi}"(prev_thread_id), "{rdx}"(switch_reason as u64)
: "rax", "rbx", "rcx", "rdx", "rsi", "rdi", "rbp", "r8", "r9", "r10",
"r11", "r12", "r13", "r14", "r15", "rflags", "memory"
: "intel", "volatile"
);
}
global_asm!(
"
.intel_syntax noprefix
// asm_context_switch(stack_pointer: u64, thread_id: u64)
asm_context_switch:
pushfq
mov rax, rsp
mov rsp, rdi
mov rdi, rax
call add_paused_thread
popfq
ret
"
);
#[no_mangle]
pub extern "C" fn add_paused_thread(
paused_stack_pointer: VirtAddr,
paused_thread_id: ThreadId,
switch_reason: SwitchReason,
) {
with_scheduler(|s| s.add_paused_thread(paused_stack_pointer, paused_thread_id, switch_reason));
}
#[naked]
fn call_closure_entry() -> ! {
unsafe {
asm!("
pop rsi
pop rdi
call call_closure
" ::: "mem" : "intel", "volatile")
};
unreachable!();
}
// no_mangle required because of https://github.com/rust-lang/rust/issues/68136
#[no_mangle]
extern "C" fn call_closure(data: *mut (), vtable: *mut ()) -> ! {
let trait_object = TraitObject { data, vtable };
let f: Box<dyn FnOnce() -> !> = unsafe { mem::transmute(trait_object) };
f()
}

57
src/multitasking/mod.rs Normal file
View File

@@ -0,0 +1,57 @@
use scheduler::Scheduler;
pub mod context_switch;
pub mod scheduler;
pub mod thread;
static SCHEDULER: spin::Mutex<Option<Scheduler>> = spin::Mutex::new(None);
#[repr(u64)]
pub enum SwitchReason {
Paused,
Yield,
Blocked,
Exit,
}
pub fn invoke_scheduler() {
let next = SCHEDULER
.try_lock()
.and_then(|mut scheduler| scheduler.as_mut().and_then(|s| s.schedule()));
if let Some((next_stack_pointer, prev_thread_id)) = next {
unsafe {
context_switch::context_switch_to(
next_stack_pointer,
prev_thread_id,
SwitchReason::Paused,
)
};
}
}
pub fn exit_thread() -> ! {
synchronous_context_switch(SwitchReason::Exit).expect("can't exit last thread");
unreachable!("finished thread continued");
}
pub fn yield_now() {
let _ = synchronous_context_switch(SwitchReason::Yield);
}
fn synchronous_context_switch(reason: SwitchReason) -> Result<(), ()> {
let next = with_scheduler(|s| s.schedule());
match next {
Some((next_stack_pointer, prev_thread_id)) => unsafe {
context_switch::context_switch_to(next_stack_pointer, prev_thread_id, reason);
Ok(())
},
None => Err(()),
}
}
pub fn with_scheduler<F, T>(f: F) -> T
where
F: FnOnce(&mut Scheduler) -> T,
{
f(SCHEDULER.lock().get_or_insert_with(Scheduler::new))
}

View File

@@ -0,0 +1,122 @@
use super::SwitchReason;
use crate::multitasking::thread::{Thread, ThreadId};
use alloc::collections::{BTreeMap, BTreeSet, VecDeque};
use core::mem;
use x86_64::VirtAddr;
pub struct Scheduler {
threads: BTreeMap<ThreadId, Thread>,
idle_thread_id: Option<ThreadId>,
current_thread_id: ThreadId,
paused_threads: VecDeque<ThreadId>,
blocked_threads: BTreeSet<ThreadId>,
wakeups: BTreeSet<ThreadId>,
}
impl Scheduler {
pub fn new() -> Self {
let root_thread = Thread::create_root_thread();
let root_id = root_thread.id();
let mut threads = BTreeMap::new();
threads
.insert(root_id, root_thread)
.expect_none("map is not empty after creation");
Scheduler {
threads,
current_thread_id: root_id,
paused_threads: VecDeque::new(),
blocked_threads: BTreeSet::new(),
wakeups: BTreeSet::new(),
idle_thread_id: None,
}
}
fn next_thread(&mut self) -> Option<ThreadId> {
self.paused_threads.pop_front()
}
pub fn schedule(&mut self) -> Option<(VirtAddr, ThreadId)> {
let mut next_thread_id = self.next_thread();
if next_thread_id.is_none() && Some(self.current_thread_id) != self.idle_thread_id {
next_thread_id = self.idle_thread_id
}
if let Some(next_id) = next_thread_id {
let next_thread = self
.threads
.get_mut(&next_id)
.expect("next thread does not exist");
let next_stack_pointer = next_thread
.stack_pointer()
.take()
.expect("paused thread has no stack pointer");
let prev_thread_id = mem::replace(&mut self.current_thread_id, next_thread.id());
Some((next_stack_pointer, prev_thread_id))
} else {
None
}
}
pub(super) fn add_paused_thread(
&mut self,
paused_stack_pointer: VirtAddr,
paused_thread_id: ThreadId,
switch_reason: SwitchReason,
) {
let paused_thread = self
.threads
.get_mut(&paused_thread_id)
.expect("paused thread does not exist");
paused_thread
.stack_pointer()
.replace(paused_stack_pointer)
.expect_none("running thread should have stack pointer set to None");
if Some(paused_thread_id) == self.idle_thread_id {
return; // do nothing
}
match switch_reason {
SwitchReason::Paused | SwitchReason::Yield => {
self.paused_threads.push_back(paused_thread_id)
}
SwitchReason::Blocked => {
self.blocked_threads.insert(paused_thread_id);
self.check_for_wakeup(paused_thread_id);
}
SwitchReason::Exit => {
let thread = self
.threads
.remove(&paused_thread_id)
.expect("thread not found");
// TODO: free stack memory again
}
}
}
pub fn add_new_thread(&mut self, thread: Thread) {
let thread_id = thread.id();
self.threads
.insert(thread_id, thread)
.expect_none("thread already exists");
self.paused_threads.push_back(thread_id);
}
pub fn set_idle_thread(&mut self, thread: Thread) {
let thread_id = thread.id();
self.threads
.insert(thread_id, thread)
.expect_none("thread already exists");
self.idle_thread_id
.replace(thread_id)
.expect_none("idle thread should be set only once");
}
pub fn current_thread_id(&self) -> ThreadId {
self.current_thread_id
}
fn check_for_wakeup(&mut self, thread_id: ThreadId) {
if self.wakeups.remove(&thread_id) {
assert!(self.blocked_threads.remove(&thread_id));
self.paused_threads.push_back(thread_id);
}
}
}

View File

@@ -0,0 +1,82 @@
use crate::memory::{alloc_stack, StackBounds};
use crate::multitasking::context_switch::Stack;
use alloc::boxed::Box;
use x86_64::{
structures::paging::{mapper, FrameAllocator, Mapper, Size4KiB},
VirtAddr,
};
#[derive(Debug, Clone, Copy, PartialEq, Eq, PartialOrd, Ord)]
pub struct ThreadId(u64);
impl ThreadId {
pub fn as_u64(&self) -> u64 {
self.0
}
fn new() -> Self {
use core::sync::atomic::{AtomicU64, Ordering};
static NEXT_THREAD_ID: AtomicU64 = AtomicU64::new(1);
ThreadId(NEXT_THREAD_ID.fetch_add(1, Ordering::SeqCst))
}
}
#[derive(Debug)]
pub struct Thread {
id: ThreadId,
stack_pointer: Option<VirtAddr>,
stack_bounds: Option<StackBounds>,
}
impl Thread {
pub fn create(
entry_point: fn() -> !,
stack_size: u64,
mapper: &mut impl Mapper<Size4KiB>,
frame_allocator: &mut impl FrameAllocator<Size4KiB>,
) -> Result<Self, mapper::MapToError> {
let stack_bounds = alloc_stack(stack_size, mapper, frame_allocator)?;
let mut stack = unsafe { Stack::new(stack_bounds.end()) };
stack.set_up_for_entry_point(entry_point);
Ok(Self::new(stack.get_stack_pointer(), stack_bounds))
}
pub fn create_from_closure<F>(
closure: F,
stack_size: u64,
mapper: &mut impl Mapper<Size4KiB>,
frame_allocator: &mut impl FrameAllocator<Size4KiB>,
) -> Result<Self, mapper::MapToError>
where
F: FnOnce() -> ! + 'static + Send + Sync,
{
let stack_bounds = alloc_stack(stack_size, mapper, frame_allocator)?;
let mut stack = unsafe { Stack::new(stack_bounds.end()) };
stack.set_up_for_closure(Box::new(closure));
Ok(Self::new(stack.get_stack_pointer(), stack_bounds))
}
fn new(stack_pointer: VirtAddr, stack_bounds: StackBounds) -> Self {
Thread {
id: ThreadId::new(),
stack_pointer: Some(stack_pointer),
stack_bounds: Some(stack_bounds),
}
}
pub(super) fn create_root_thread() -> Self {
Thread {
id: ThreadId(0),
stack_pointer: None,
stack_bounds: None,
}
}
pub fn id(&self) -> ThreadId {
self.id
}
pub(super) fn stack_pointer(&mut self) -> &mut Option<VirtAddr> {
&mut self.stack_pointer
}
}

View File

@@ -1,52 +0,0 @@
use conquer_once::spin::OnceCell;
use crossbeam_queue::ArrayQueue;
use futures_util::stream::{Stream, StreamExt};
use core::{pin::Pin, task::{Context, Poll}};
use pc_keyboard::{layouts, DecodedKey, HandleControl, Keyboard, ScancodeSet1};
use crate::print;
static SCANCODE_QUEUE: OnceCell<ArrayQueue<u8>> = OnceCell::uninit();
pub struct ScancodeStream {
_private: (),
}
impl ScancodeStream {
pub fn new() -> Self {
SCANCODE_QUEUE.try_init_once(|| ArrayQueue::new(100))
.expect("ScancodeStream::new should only be called once");
ScancodeStream {
_private: (),
}
}
}
impl Stream for ScancodeStream {
type Item = u8;
fn poll_next(self: Pin<&mut Self>, context: &mut Context) -> Poll<Option<u8>> {
let queue = SCANCODE_QUEUE
.try_get()
.expect("scancode queue not initialized");
match queue.pop() {
Ok(scancode) => Poll::Ready(Some(scancode)),
Err(crossbeam_queue::PopError) => Poll::Pending,
}
}
}
pub async fn print_keypresses() {
let mut scancodes = ScancodeStream::new();
let mut keyboard = Keyboard::new(layouts::Us104Key, ScancodeSet1, HandleControl::Ignore);
while let Some(scancode) = scancodes.next().await {
if let Ok(Some(key_event)) = keyboard.add_byte(scancode) {
if let Some(key) = keyboard.process_keyevent(key_event) {
match key {
DecodedKey::Unicode(character) => print!("{}", character),
DecodedKey::RawKey(key) => print!("{:?}", key),
}
}
}
}
}

View File

@@ -1,22 +0,0 @@
use alloc::boxed::Box;
use core::task::{Context, Poll};
use core::{future::Future, pin::Pin};
pub mod keyboard;
pub mod simple_executor;
pub struct Task {
future: Pin<Box<dyn Future<Output = ()>>>,
}
impl Task {
pub fn new(future: impl Future<Output = ()> + 'static) -> Task {
Task {
future: Box::pin(future),
}
}
fn poll(&mut self, context: &mut Context) -> Poll<()> {
self.future.as_mut().poll(context)
}
}

View File

@@ -1,44 +0,0 @@
use super::Task;
use alloc::{collections::VecDeque, sync::Arc, task::Wake};
use core::task::{Context, Poll, Waker};
pub struct SimpleExecutor {
task_queue: VecDeque<Task>,
}
impl SimpleExecutor {
pub fn new() -> SimpleExecutor {
SimpleExecutor {
task_queue: VecDeque::new(),
}
}
pub fn spawn(&mut self, task: Task) {
self.task_queue.push_back(task)
}
pub fn run(&mut self) {
while let Some(mut task) = self.task_queue.pop_front() {
let waker = DummyWaker.to_waker();
let mut context = Context::from_waker(&waker);
match task.poll(&mut context) {
Poll::Ready(()) => {} // task done
Poll::Pending => self.task_queue.push_back(task),
}
}
}
}
struct DummyWaker;
impl Wake for DummyWaker {
fn wake(self: Arc<Self>) {
// do nothing
}
}
impl DummyWaker {
fn to_waker(self) -> Waker {
Waker::from(Arc::new(self))
}
}