Compare commits

..

10 Commits

Author SHA1 Message Date
Philipp Oppermann
3d485535ae Refactor: Move interrupt wakers/operations into separate modules 2020-03-09 08:25:24 +01:00
Philipp Oppermann
08582948c5 Let executor enable interrupts 2020-03-08 14:51:51 +01:00
Philipp Oppermann
b3ba0ba4e9 Use new enable_interrupts_and_hlt function of x86_64 2020-03-08 14:50:58 +01:00
Philipp Oppermann
c3ea4190ca Merge branch 'post-11' into post-12-async-await 2020-03-08 14:48:33 +01:00
Philipp Oppermann
f75d63853f Refactor Executor::run into separate methods 2020-03-08 13:49:36 +01:00
Philipp Oppermann
378159ce76 Simplify executor by removing separate Spawner type 2020-03-08 13:07:43 +01:00
Philipp Oppermann
a5ff4261a0 Handle timer interrupts using async task too 2020-02-28 19:09:15 +01:00
Philipp Oppermann
ea83d905fe Fix wrong calculation of task ID
Pin implements Deref too, so by dereferencing only once we get a `&Box` reference. Since Box also implements Future, the type of `future_ref` was still correct. To avoid this error in the future, we added an assertion to catch duplicate task IDson `pending_tasks.insert()`.
2020-02-28 19:08:31 +01:00
Philipp Oppermann
786a7a6922 Use crossbeam-queue and AtomicWaker for async keypress handling 2020-02-28 17:52:51 +01:00
Philipp Oppermann
6329274f02 Create a basic executor based on crossbeam_queue 2020-02-28 11:42:36 +01:00
13 changed files with 370 additions and 284 deletions

67
Cargo.lock generated
View File

@@ -24,7 +24,6 @@ version = "0.1.0"
dependencies = [
"bootloader",
"conquer-once",
"cooked-waker",
"crossbeam-queue",
"futures-util",
"lazy_static",
@@ -39,9 +38,9 @@ dependencies = [
[[package]]
name = "bootloader"
version = "0.8.9"
version = "0.8.8"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "152a28c753e229e037e910b4cd4cd16a90c53dd9a67fd751fa304b4b4a03970c"
checksum = "c3ed4f735c4e455ba86a3d2939b1c0729414153642106c9d035693355630a42c"
[[package]]
name = "cfg-if"
@@ -64,27 +63,6 @@ version = "0.2.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "654fb2472cc369d311c547103a1fa81d467bef370ae7a0680f65939895b1182a"
[[package]]
name = "cooked-waker"
version = "1.0.2"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "b0032e8be0680b63daf14b0fd7fd57f85fd6897951e110b041d32e839a831914"
dependencies = [
"cooked-waker-derive",
"stowaway",
]
[[package]]
name = "cooked-waker-derive"
version = "1.0.1"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "74ded02b04a8ff53ea7328cd71297cde598bee70d165ad92512bdb7e20be9d74"
dependencies = [
"proc-macro2",
"quote",
"syn",
]
[[package]]
name = "cpuio"
version = "0.2.0"
@@ -182,24 +160,6 @@ version = "0.1.0-alpha.4"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "5894c618ce612a3fa23881b152b608bafb8c56cfc22f434a3ba3120b40f7b587"
[[package]]
name = "proc-macro2"
version = "1.0.9"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "6c09721c6781493a2a492a96b5a5bf19b65917fe6728884e7c44dd0c60ca3435"
dependencies = [
"unicode-xid",
]
[[package]]
name = "quote"
version = "1.0.3"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "2bdc6c187c65bca4260c9011c9e3132efe4909da44726bad24cf7572ae338d7f"
dependencies = [
"proc-macro2",
]
[[package]]
name = "scopeguard"
version = "1.1.0"
@@ -221,23 +181,6 @@ dependencies = [
"lock_api",
]
[[package]]
name = "stowaway"
version = "1.1.2"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "32b13d61e782863c0eaeeaf7e8e580dc956a625851fd2fb73e5e92db9601c891"
[[package]]
name = "syn"
version = "1.0.17"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "0df0eb663f387145cab623dea85b09c2c5b4b0aef44e945d928e682fce71bb03"
dependencies = [
"proc-macro2",
"quote",
"unicode-xid",
]
[[package]]
name = "uart_16550"
version = "0.2.4"
@@ -248,12 +191,6 @@ dependencies = [
"x86_64",
]
[[package]]
name = "unicode-xid"
version = "0.2.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "826e7639553986605ec5979c7dd957c7895e93eabed50ab2ffa7f6128a75097c"
[[package]]
name = "volatile"
version = "0.2.6"

View File

@@ -13,7 +13,7 @@ name = "stack_overflow"
harness = false
[dependencies]
bootloader = { version = "0.8.8", features = ["map_physical_memory"]}
bootloader = { version = "0.8.0", features = ["map_physical_memory"]}
volatile = "0.2.6"
spin = "0.5.2"
x86_64 = "0.9.6"
@@ -21,7 +21,6 @@ uart_16550 = "0.2.0"
pic8259_simple = "0.1.1"
pc-keyboard = "0.5.0"
linked_list_allocator = "0.8.0"
cooked-waker = "1.0.2"
[dependencies.lazy_static]
version = "1.0"
@@ -32,14 +31,14 @@ 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"]
features = ["alloc", "async-await"]
[dependencies.conquer-once]
version = "0.2.0"
default-features = false
[package.metadata.bootimage]
test-args = [

75
src/driver/keyboard.rs Normal file
View File

@@ -0,0 +1,75 @@
use crate::{print, println, task::interrupt_wake};
use conquer_once::spin::OnceCell;
use core::future::Future;
use core::{
pin::Pin,
task::{Context, Poll},
};
use crossbeam_queue::ArrayQueue;
use futures_util::task::AtomicWaker;
use pc_keyboard::{layouts, DecodedKey, HandleControl, Keyboard, ScancodeSet1};
static WAKER: AtomicWaker = AtomicWaker::new();
static SCANCODE_QUEUE: OnceCell<ArrayQueue<u8>> = OnceCell::uninit();
pub fn init() {
SCANCODE_QUEUE
.try_init_once(|| ArrayQueue::new(10))
.expect("failed to init scancode queue");
}
/// Called by the keyboard interrupt handler
///
/// Must not block (including spinlocks).
pub(crate) fn keyboard_scancode(scancode: u8) {
let scancode_queue = SCANCODE_QUEUE
.try_get()
.expect("scancode queue not initialized");
if let Err(_) = scancode_queue.push(scancode) {
println!("WARNING: dropping keyboard input");
}
if let Some(waker) = WAKER.take() {
interrupt_wake(waker);
}
}
fn next_scancode() -> impl Future<Output = u8> {
NextScancode
}
struct NextScancode;
impl Future for NextScancode {
type Output = u8;
fn poll(self: Pin<&mut Self>, cx: &mut Context) -> Poll<u8> {
let scancodes = SCANCODE_QUEUE
.try_get()
.expect("scancode queue not initialized");
// fast path
if let Ok(scancode) = scancodes.pop() {
return Poll::Ready(scancode);
}
WAKER.register(&cx.waker());
match scancodes.pop() {
Ok(scancode) => Poll::Ready(scancode),
Err(crossbeam_queue::PopError) => Poll::Pending,
}
}
}
pub async fn keyboard_task() {
let mut keyboard = Keyboard::new(layouts::Us104Key, ScancodeSet1, HandleControl::Ignore);
loop {
if let Ok(Some(key_event)) = keyboard.add_byte(next_scancode().await) {
if let Some(key) = keyboard.process_keyevent(key_event) {
match key {
DecodedKey::Unicode(character) => print!("{}", character),
DecodedKey::RawKey(key) => print!("{:?}", key),
}
}
}
}
}

6
src/driver/mod.rs Normal file
View File

@@ -0,0 +1,6 @@
pub mod keyboard;
pub mod timer;
pub fn init() {
keyboard::init();
}

55
src/driver/timer.rs Normal file
View File

@@ -0,0 +1,55 @@
use crate::{print, task::interrupt_wake};
use core::future::Future;
use core::{
pin::Pin,
sync::atomic::{AtomicU64, Ordering},
task::{Context, Poll},
};
use futures_util::task::AtomicWaker;
static TICKS: AtomicU64 = AtomicU64::new(0);
static WAKER: AtomicWaker = AtomicWaker::new();
/// Called by the timer interrupt handler
///
/// Must not block (including spinlocks).
pub(crate) fn tick() {
TICKS.fetch_add(1, Ordering::Release);
if let Some(waker) = WAKER.take() {
interrupt_wake(waker);
}
}
fn next_tick() -> impl Future<Output = u64> {
static NEXT_TICK: AtomicU64 = AtomicU64::new(1);
NextTick {
ticks: NEXT_TICK.fetch_add(1, Ordering::Release),
}
}
struct NextTick {
ticks: u64,
}
impl Future for NextTick {
type Output = u64;
fn poll(mut self: Pin<&mut Self>, cx: &mut Context) -> Poll<u64> {
WAKER.register(&cx.waker());
let current_ticks = TICKS.load(Ordering::Acquire);
if self.ticks < current_ticks {
self.ticks += 1;
Poll::Ready(self.ticks)
} else {
Poll::Pending
}
}
}
pub async fn timer_task() {
loop {
next_tick().await;
print!(".");
}
}

View File

@@ -1,4 +1,4 @@
use crate::{gdt, hlt_loop, print, println};
use crate::{gdt, hlt_loop, println};
use lazy_static::lazy_static;
use pic8259_simple::ChainedPics;
use spin;
@@ -72,7 +72,8 @@ extern "x86-interrupt" fn double_fault_handler(
}
extern "x86-interrupt" fn timer_interrupt_handler(_stack_frame: &mut InterruptStackFrame) {
print!(".");
crate::driver::timer::tick();
unsafe {
PICS.lock()
.notify_end_of_interrupt(InterruptIndex::Timer.as_u8());
@@ -81,10 +82,10 @@ extern "x86-interrupt" fn timer_interrupt_handler(_stack_frame: &mut InterruptSt
extern "x86-interrupt" fn keyboard_interrupt_handler(_stack_frame: &mut InterruptStackFrame) {
use x86_64::instructions::port::Port;
let mut port = Port::new(0x60);
let scancode: u8 = unsafe { port.read() };
crate::task::keyboard::add_scancode(scancode);
crate::driver::keyboard::keyboard_scancode(scancode);
unsafe {
PICS.lock()

View File

@@ -5,7 +5,10 @@
#![feature(alloc_error_handler)]
#![feature(const_fn)]
#![feature(alloc_layout_extra)]
#![feature(wake_trait)]
#![feature(const_in_array_repeat_expressions)]
#![feature(type_alias_impl_trait)]
#![feature(asm)]
#![test_runner(crate::test_runner)]
#![reexport_test_harness_main = "test_main"]
@@ -14,6 +17,7 @@ extern crate alloc;
use core::panic::PanicInfo;
pub mod allocator;
pub mod driver;
pub mod gdt;
pub mod interrupts;
pub mod memory;
@@ -25,7 +29,11 @@ pub fn init() {
gdt::init();
interrupts::init_idt();
unsafe { interrupts::PICS.lock().initialize() };
x86_64::instructions::interrupts::enable();
}
pub fn init_heap_structures() {
task::init();
driver::init();
}
pub fn test_runner(tests: &[&dyn Fn()]) {

View File

@@ -6,6 +6,7 @@
extern crate alloc;
use alloc::{boxed::Box, rc::Rc, vec, vec::Vec};
use blog_os::println;
use bootloader::{entry_point, BootInfo};
use core::panic::PanicInfo;
@@ -15,7 +16,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::{keyboard, simple_executor::SimpleExecutor, Task};
use x86_64::VirtAddr;
println!("Hello World{}", "!");
@@ -26,26 +26,50 @@ fn kernel_main(boot_info: &'static BootInfo) -> ! {
let mut frame_allocator = unsafe { BootInfoFrameAllocator::init(&boot_info.memory_map) };
allocator::init_heap(&mut mapper, &mut frame_allocator).expect("heap initialization failed");
blog_os::init_heap_structures();
// 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)
);
use blog_os::task::executor::Executor;
let mut executor = Executor::new();
executor.spawn(bar());
executor.spawn(async {
#[cfg(test)]
test_main();
});
executor.spawn(async {
println!("It did not crash!");
});
executor.spawn(blog_os::driver::timer::timer_task());
executor.spawn(blog_os::driver::keyboard::keyboard_task());
let mut executor = SimpleExecutor::new();
executor.spawn(Task::new(example_task()));
executor.spawn(Task::new(keyboard::print_keypresses()));
executor.run();
#[cfg(test)]
test_main();
println!("It did not crash!");
blog_os::hlt_loop();
}
async fn async_number() -> u32 {
42
}
async fn example_task() {
let number = async_number().await;
println!("async number: {}", number);
}
/// This function is called on panic.
@@ -61,3 +85,15 @@ fn panic(info: &PanicInfo) -> ! {
fn panic(info: &PanicInfo) -> ! {
blog_os::test_panic_handler(info)
}
async fn test() -> u32 {
42
}
async fn foo() -> u32 {
test().await * 2
}
async fn bar() {
println!("foo result: {}", foo().await);
}

127
src/task/executor.rs Normal file
View File

@@ -0,0 +1,127 @@
use super::interrupt_wakeups::interrupt_wakeups;
use crate::println;
use alloc::{
boxed::Box,
collections::{BTreeMap, VecDeque},
sync::Arc,
task::Wake,
};
use core::{
future::Future,
pin::Pin,
task::{Context, Poll},
};
use crossbeam_queue::SegQueue;
pub type Task = Pin<Box<dyn Future<Output = ()>>>;
type TaskId = usize;
pub struct Executor {
task_queue: VecDeque<Task>,
wake_queue: Arc<SegQueue<TaskId>>,
pending_tasks: BTreeMap<TaskId, Task>,
}
impl Executor {
pub fn new() -> Self {
Executor {
task_queue: VecDeque::new(),
wake_queue: Arc::new(SegQueue::new()),
pending_tasks: BTreeMap::new(),
}
}
pub fn spawn(&mut self, task: impl Future<Output = ()> + 'static) {
self.task_queue.push_back(Box::pin(task))
}
pub fn run(&mut self) -> ! {
loop {
self.run_ready_tasks();
self.apply_interrupt_wakeups();
self.wake_waiting_tasks();
self.hlt_if_idle();
}
}
fn run_ready_tasks(&mut self) {
while let Some(mut task) = self.task_queue.pop_front() {
let waker = self.create_waker(&task).into();
let mut context = Context::from_waker(&waker);
match task.as_mut().poll(&mut context) {
Poll::Ready(()) => {} // task done
Poll::Pending => {
// add task to pending_tasks and wait for wakeup
let task_id = Self::task_id(&task);
if self.pending_tasks.insert(task_id, task).is_some() {
panic!("Task with same ID already in pending_tasks");
}
}
}
}
}
/// Invoke wakers for tasks woken by interrupts
///
/// The interrupt handlers can't invoke the waker directly since wakers
/// might execute arbitrary code, e.g. allocate, which should not be done
/// in interrupt handlers to avoid deadlocks.
fn apply_interrupt_wakeups(&mut self) {
while let Ok(waker) = interrupt_wakeups().pop() {
waker.wake();
}
}
fn wake_waiting_tasks(&mut self) {
while let Ok(task_id) = self.wake_queue.pop() {
if let Some(task) = self.pending_tasks.remove(&task_id) {
self.task_queue.push_back(task);
} else {
println!("WARNING: woken task not found in pending_tasks");
}
}
}
/// Executes the `hlt` instruction if there are no ready tasks
fn hlt_if_idle(&self) {
if self.task_queue.is_empty() {
// disable interrupts to avoid races
x86_64::instructions::interrupts::disable();
// check if relevant interrupts occured since the last check
if interrupt_wakeups().is_empty() {
// no interrupts occured -> hlt to wait for next interrupt
x86_64::instructions::interrupts::enable_interrupts_and_hlt();
} else {
// there were some new wakeups -> continue execution
x86_64::instructions::interrupts::enable();
}
}
}
fn task_id(task: &Task) -> TaskId {
let future_ref: &dyn Future<Output = ()> = &**task;
future_ref as *const _ as *const () as usize
}
fn create_waker(&self, task: &Task) -> Arc<Waker> {
Arc::new(Waker {
wake_queue: self.wake_queue.clone(),
task_id: Self::task_id(task),
})
}
}
pub struct Waker {
wake_queue: Arc<SegQueue<TaskId>>,
task_id: TaskId,
}
impl Wake for Waker {
fn wake(self: Arc<Self>) {
self.wake_by_ref();
}
fn wake_by_ref(self: &Arc<Self>) {
self.wake_queue.push(self.task_id);
}
}

View File

@@ -0,0 +1,25 @@
use crate::println;
use conquer_once::spin::OnceCell;
use core::task::Waker;
use crossbeam_queue::ArrayQueue;
static INTERRUPT_WAKEUPS: OnceCell<ArrayQueue<Waker>> = OnceCell::uninit();
pub fn init() {
INTERRUPT_WAKEUPS
.try_init_once(|| ArrayQueue::new(10))
.expect("failed to init interrupt wakeup queue");
}
/// Queues a waker for waking in an interrupt-safe way
pub(crate) fn interrupt_wake(waker: Waker) {
if let Err(_) = interrupt_wakeups().push(waker) {
println!("WARNING: dropping interrupt wakeup");
}
}
pub(super) fn interrupt_wakeups() -> &'static ArrayQueue<Waker> {
INTERRUPT_WAKEUPS
.try_get()
.expect("interrupt wakeup queue not initialized")
}

View File

@@ -1,79 +0,0 @@
use crate::print;
use crate::println;
use conquer_once::spin::OnceCell;
use core::{
pin::Pin,
task::{Context, Poll},
};
use crossbeam_queue::ArrayQueue;
use futures_util::stream::StreamExt;
use futures_util::{stream::Stream, task::AtomicWaker};
use pc_keyboard::{layouts, DecodedKey, HandleControl, Keyboard, ScancodeSet1};
static SCANCODE_QUEUE: OnceCell<ArrayQueue<u8>> = OnceCell::uninit();
static WAKER: AtomicWaker = AtomicWaker::new();
/// Called by the keyboard interrupt handler
///
/// Must not block or allocate.
pub(crate) fn add_scancode(scancode: u8) {
if let Ok(queue) = SCANCODE_QUEUE.try_get() {
if let Err(_) = queue.push(scancode) {
println!("WARNING: scancode queue full; dropping keyboard input");
} else {
WAKER.wake();
}
} else {
println!("WARNING: scancode queue uninitialized");
}
}
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>, cx: &mut Context) -> Poll<Option<u8>> {
let queue = SCANCODE_QUEUE
.try_get()
.expect("scancode queue not initialized");
// fast path
if let Ok(scancode) = queue.pop() {
return Poll::Ready(Some(scancode));
}
WAKER.register(&cx.waker());
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,26 +1,8 @@
use alloc::boxed::Box;
use core::task::{Context, Poll};
use core::{future::Future, pin::Pin};
pub(crate) use interrupt_wakeups::interrupt_wake;
pub mod keyboard;
pub mod simple_executor;
pub mod executor;
mod interrupt_wakeups;
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)
}
fn id(&self) -> usize {
&*self.future as *const _ as *const () as usize
}
pub fn init() {
interrupt_wakeups::init();
}

View File

@@ -1,86 +0,0 @@
use super::Task;
use alloc::{
collections::{BTreeMap, VecDeque},
sync::Arc,
};
use cooked_waker::IntoWaker;
use core::task::{Context, Poll};
use crossbeam_queue::ArrayQueue;
pub struct SimpleExecutor {
task_queue: VecDeque<Task>,
waiting_tasks: BTreeMap<usize, Task>,
wake_queue: Arc<ArrayQueue<usize>>,
}
impl SimpleExecutor {
pub fn new() -> SimpleExecutor {
SimpleExecutor {
task_queue: VecDeque::new(),
waiting_tasks: BTreeMap::new(),
wake_queue: Arc::new(ArrayQueue::new(100)),
}
}
pub fn spawn(&mut self, task: Task) {
self.task_queue.push_back(task)
}
pub fn run(&mut self) {
loop {
self.handle_wakeups();
self.run_ready_tasks();
}
}
fn handle_wakeups(&mut self) {
while let Ok(task_id) = self.wake_queue.pop() {
if let Some(task) = self.waiting_tasks.remove(&task_id) {
self.task_queue.push_back(task);
}
}
}
fn run_ready_tasks(&mut self) {
while let Some(mut task) = self.task_queue.pop_front() {
let waker = TaskWaker {
task_id: task.id(),
wake_queue: self.wake_queue.clone(),
}
.into_waker();
let mut context = Context::from_waker(&waker);
match task.poll(&mut context) {
Poll::Ready(()) => {} // task done
Poll::Pending => {
if self.waiting_tasks.insert(task.id(), task).is_some() {
panic!("Same task inserted into waiting_tasks twice");
}
}
}
}
}
}
#[derive(Debug, Clone, IntoWaker)]
struct TaskWaker {
task_id: usize,
wake_queue: Arc<ArrayQueue<usize>>,
}
impl TaskWaker {
fn wake_task(&self) {
self.wake_queue.push(self.task_id).expect("wake queue full");
}
}
impl cooked_waker::WakeRef for TaskWaker {
fn wake_by_ref(&self) {
self.wake_task();
}
}
impl cooked_waker::Wake for TaskWaker {
fn wake(self) {
self.wake_task();
}
}