Skip to content

Commit d56e450

Browse files
committed
Added initial support for kernel threads
This is a fairly big change. This introduces threads of execution, which are initially implemented only for kernel threads. The longer term plan is to implement threads for userspace too. The plan for Firefly is to have a hierarchical structure of threads (the unit of scheduling) belonging to processes (the unit of programs) belonging to workloads (the unit of applications). This initial support is quite primitive, but it's a good starting point. There is no support for preemption yet, so threads must yield to the scheduler using thread::switch(). The structures for tracking the current process and the idle thread are not yet CPU-local, so the implementation is not safe to run in parallel. However, as we don't yet enable more than one CPU, this is safe in practice. We will need to add support for CPU-local data before we can enable multiple cores. Calling thread::init() initialises the thread module, creating the idle thread and preparing the current thread and the scheduler. When thread::switch is called for the first time (in kmain), the kernel's initial context is stored into the idle thread. From that point on, the idle thread will execute when no other threads are runnable. For now, the idle thread exits QEMU, to simplify development, but this will be removed once the thread functionality is more stable. For now, we just demonstrate the threads functionality by switching to a simple debugging thread, returning to the idle thread, and exiting. Signed-off-by: SlyMarbo <[email protected]>
1 parent 92dbf95 commit d56e450

File tree

10 files changed

+678
-6
lines changed

10 files changed

+678
-6
lines changed

kernel/Cargo.lock

Lines changed: 63 additions & 4 deletions
Some generated files are not rendered by default. Learn more about customizing how changed files appear on GitHub.

kernel/Cargo.toml

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -6,6 +6,7 @@ edition = "2018"
66

77
[dependencies]
88
bootloader = {version = "=0.9.19", features = ["map_physical_memory"]}
9+
crossbeam = { version = "=0.8.1", default-features = false, features = ["alloc"] }
910
crossbeam-queue = {version = "=0.2.1", default-features = false, features = ["alloc"]}
1011
lazy_static = {version = "=1.4.0", features = ["spin_no_std"]}
1112
linked_list_allocator = "=0.9.0"

kernel/src/lib.rs

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -22,13 +22,16 @@
2222
#![cfg_attr(test, no_main)]
2323
#![feature(abi_x86_interrupt)]
2424
#![feature(alloc_error_handler)]
25+
#![feature(const_btree_new)]
2526
#![feature(const_mut_refs)]
2627
#![feature(custom_test_frameworks)]
28+
#![feature(global_asm)]
2729
#![test_runner(crate::test_runner)]
2830
#![reexport_test_harness_main = "test_main"]
2931

3032
extern crate alloc;
3133

34+
use crate::multitasking::thread;
3235
use bootloader::BootInfo;
3336
use core::panic::PanicInfo;
3437
use lazy_static::lazy_static;
@@ -60,6 +63,7 @@ pub fn init(boot_info: &'static BootInfo) {
6063

6164
// Set up the heap allocator.
6265
unsafe { memory::init(boot_info) };
66+
thread::init();
6367
}
6468

6569
#[alloc_error_handler]

kernel/src/main.rs

Lines changed: 21 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -14,6 +14,7 @@ extern crate alloc;
1414

1515
use bootloader::{entry_point, BootInfo};
1616
use core::panic::PanicInfo;
17+
use kernel::multitasking::thread;
1718
use kernel::{memory, pci, println};
1819

1920
/// This function is called on panic.
@@ -62,7 +63,26 @@ fn kmain() {
6263

6364
pci::init();
6465

65-
kernel::shutdown_qemu();
66+
// Schedule the thread we want to run next
67+
// with switch.
68+
thread::Thread::new_kernel_thread(debug_threading);
69+
70+
// Hand over to the scheduler.
71+
thread::switch();
72+
73+
// We're now executing as the idle
74+
// thread.
75+
thread::idle_thread();
76+
}
77+
78+
fn debug_threading() -> ! {
79+
let foo: u64 = 1;
80+
println!(
81+
"Successfully entered new thread with stack address {:p}.",
82+
&foo
83+
);
84+
85+
thread::exit();
6686
}
6787

6888
#[allow(dead_code)]

kernel/src/memory/mod.rs

Lines changed: 92 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -17,8 +17,12 @@
1717
// build the memory manager.
1818

1919
use bootloader::BootInfo;
20+
use core::sync::atomic::{AtomicU64, Ordering};
2021
use x86_64::registers::control::Cr3;
21-
use x86_64::structures::paging::{OffsetPageTable, PageTable};
22+
use x86_64::structures::paging::mapper::MapToError;
23+
use x86_64::structures::paging::{
24+
FrameAllocator, Mapper, OffsetPageTable, Page, PageTable, PageTableFlags, Size4KiB,
25+
};
2226
use x86_64::VirtAddr;
2327

2428
mod constants;
@@ -82,6 +86,93 @@ pub unsafe fn kernel_pml4() -> OffsetPageTable<'static> {
8286
OffsetPageTable::new(page_table, PHYSICAL_MEMORY_OFFSET)
8387
}
8488

89+
/// StackBounds describes the address space used
90+
/// for a stack.
91+
///
92+
#[derive(Debug, Clone, Copy, PartialEq, Eq)]
93+
pub struct StackBounds {
94+
start: VirtAddr,
95+
end: VirtAddr,
96+
}
97+
98+
impl StackBounds {
99+
/// from returns a set of stack bounds consisting of the
100+
/// given virtual address range.
101+
///
102+
pub fn from(range: &VirtAddrRange) -> Self {
103+
StackBounds {
104+
start: range.start(),
105+
end: range.end() + 1u64, // StackBounds is exclusive, range is inclusive.
106+
}
107+
}
108+
109+
/// start returns the smallest valid address in the
110+
/// stack bounds. As the stack grows downwards, this
111+
/// is also known as the bottom of the stack.
112+
///
113+
pub fn start(&self) -> VirtAddr {
114+
self.start
115+
}
116+
117+
/// end returns the first address beyond the stack
118+
/// bounds. As the stack grows downwards, this is
119+
/// also known as the top of the stack.
120+
///
121+
pub fn end(&self) -> VirtAddr {
122+
self.end
123+
}
124+
}
125+
126+
/// reserve_kernel_stack reserves num_pages pages of
127+
/// stack memory for a kernel thread.
128+
///
129+
/// reserve_kernel_stack returns the page at the start
130+
/// of the stack (the lowest address).
131+
///
132+
fn reserve_kernel_stack(num_pages: u64) -> Page {
133+
static STACK_ALLOC_NEXT: AtomicU64 = AtomicU64::new(constants::KERNEL_STACK_1_START.as_u64());
134+
let start_addr = VirtAddr::new(
135+
STACK_ALLOC_NEXT.fetch_add(num_pages * Page::<Size4KiB>::SIZE, Ordering::Relaxed),
136+
);
137+
138+
let last_addr = start_addr + (num_pages * Page::<Size4KiB>::SIZE) - 1u64;
139+
if !KERNEL_STACK.contains_range(start_addr, last_addr) {
140+
panic!("cannot reserve kernel stack: kernel stack space exhausted");
141+
}
142+
143+
Page::from_start_address(start_addr).expect("`STACK_ALLOC_NEXT` not page aligned")
144+
}
145+
146+
/// new_kernel_stack allocates num_pages pages of stack
147+
/// memory for a kernel thread and guard page, returning
148+
/// the address space of the allocated stack.
149+
///
150+
pub fn new_kernel_stack(num_pages: u64) -> Result<StackBounds, MapToError<Size4KiB>> {
151+
let guard_page = reserve_kernel_stack(num_pages + 1);
152+
let stack_start = guard_page + 1;
153+
let stack_end = stack_start + num_pages;
154+
155+
let mut mapper = unsafe { kernel_pml4() };
156+
let mut frame_allocator = pmm::ALLOCATOR.lock();
157+
for page in Page::range(stack_start, stack_end) {
158+
let frame = frame_allocator
159+
.allocate_frame()
160+
.ok_or(MapToError::FrameAllocationFailed)?;
161+
162+
let flags = PageTableFlags::PRESENT | PageTableFlags::WRITABLE | PageTableFlags::NO_EXECUTE;
163+
unsafe {
164+
mapper
165+
.map_to(page, frame, flags, &mut *frame_allocator)?
166+
.flush()
167+
};
168+
}
169+
170+
Ok(StackBounds {
171+
start: stack_start.start_address(),
172+
end: stack_end.start_address(),
173+
})
174+
}
175+
85176
#[test_case]
86177
fn simple_allocation() {
87178
use alloc::boxed::Box;

kernel/src/multitasking/mod.rs

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -2,3 +2,4 @@
22
//! preemptive multitasking.
33
44
pub mod task;
5+
pub mod thread;

0 commit comments

Comments
 (0)