Skip to content

Commit be0b250

Browse files
committed
Added basic support for CPU-local data
This is a fairly naive implementation, where we allocate a per-CPU region from CPU_SPACE for each CPU (which is currently just the first CPU, as we don't have multi-CPU support yet), then we store a pointer to the beginning of that space in the GS base. To load the per-CPU data, we simply read the address back out of the GS base and dereference it to access the data. This isn't as efficient as the approach being discussed in rust-osdev/x86_64#257, but it works well enough for now. Signed-off-by: SlyMarbo <the.sly.marbo@googlemail.com>
1 parent 9a02520 commit be0b250

File tree

6 files changed

+172
-51
lines changed

6 files changed

+172
-51
lines changed

kernel/src/gdt.rs

+5-1
Original file line numberDiff line numberDiff line change
@@ -7,7 +7,7 @@
77
// handler.
88

99
use lazy_static::lazy_static;
10-
use x86_64::instructions::segmentation::{Segment, CS};
10+
use x86_64::instructions::segmentation::{Segment, CS, GS};
1111
use x86_64::instructions::tables::load_tss;
1212
use x86_64::structures::gdt::{Descriptor, GlobalDescriptorTable, SegmentSelector};
1313
use x86_64::structures::tss::TaskStateSegment;
@@ -20,6 +20,7 @@ pub fn init() {
2020
GDT.0.load();
2121
unsafe {
2222
CS::set_reg(GDT.1.code_selector);
23+
GS::set_reg(GDT.1.cpu_selector);
2324
load_tss(GDT.1.tss_selector);
2425
}
2526
}
@@ -29,11 +30,13 @@ lazy_static! {
2930
let mut gdt = GlobalDescriptorTable::new();
3031
let code_selector = gdt.add_entry(Descriptor::kernel_code_segment());
3132
let tss_selector = gdt.add_entry(Descriptor::tss_segment(&TSS));
33+
let cpu_selector = gdt.add_entry(Descriptor::kernel_data_segment());
3234
(
3335
gdt,
3436
Selectors {
3537
code_selector,
3638
tss_selector,
39+
cpu_selector,
3740
},
3841
)
3942
};
@@ -42,6 +45,7 @@ lazy_static! {
4245
struct Selectors {
4346
code_selector: SegmentSelector,
4447
tss_selector: SegmentSelector,
48+
cpu_selector: SegmentSelector,
4549
}
4650

4751
// Set up the task state segment with a safe

kernel/src/lib.rs

+3-1
Original file line numberDiff line numberDiff line change
@@ -31,7 +31,8 @@
3131

3232
extern crate alloc;
3333

34-
use crate::multitasking::thread;
34+
use crate::memory::KERNEL_STACK_0;
35+
use crate::multitasking::{cpu_local, thread};
3536
use bootloader::BootInfo;
3637
use core::panic::PanicInfo;
3738
use lazy_static::lazy_static;
@@ -63,6 +64,7 @@ pub fn init(boot_info: &'static BootInfo) {
6364

6465
// Set up the heap allocator.
6566
unsafe { memory::init(boot_info) };
67+
cpu_local::init(cpu_local::CpuId::new(), &KERNEL_STACK_0);
6668
thread::init();
6769
}
6870

kernel/src/main.rs

+1-1
Original file line numberDiff line numberDiff line change
@@ -72,7 +72,7 @@ fn kmain() {
7272

7373
// We're now executing as the idle
7474
// thread.
75-
thread::idle_thread();
75+
thread::idle_loop();
7676
}
7777

7878
fn debug_threading() -> ! {

kernel/src/multitasking/cpu_local.rs

+154
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,154 @@
1+
//! cpu_local provides functionality to access a different copy of the same
2+
//! structure on each CPU. This is used to track data like the CPU ID and
3+
//! the currently-executing thread.
4+
5+
// For now, we take a simple but slightly inefficient approach, where we
6+
// allocate a copy of the CpuData struct in the CPU-local address space
7+
// and store a pointer to it in the GS base. To access the data, we use
8+
// a wrapper function to retrieve the pointer from the GS base, casting
9+
// it to the right type, then access the data as usual.
10+
//
11+
// This is less efficient than using offsets from the GS base directly
12+
// in assembly, as described here[1], but it's much simpler to implement.
13+
// If rust-osdev/x86_64#257 is merged, that will probably be used to
14+
// replace this module.
15+
//
16+
// [1]: https://github.com/rust-osdev/x86_64/pull/257#issuecomment-849514649
17+
18+
use crate::memory::{kernel_pml4, pmm, VirtAddrRange, CPU_LOCAL};
19+
use crate::multitasking::thread::Thread;
20+
use alloc::sync::Arc;
21+
use core::mem::size_of;
22+
use core::sync::atomic::{AtomicU64, Ordering};
23+
use x86_64::registers::model_specific::GsBase;
24+
use x86_64::structures::paging::{
25+
FrameAllocator, Mapper, Page, PageSize, PageTableFlags, Size4KiB,
26+
};
27+
28+
/// init prepares the current CPU's local
29+
/// data using the given CPU ID and stack
30+
/// space.
31+
///
32+
pub fn init(cpu_id: CpuId, stack_space: &VirtAddrRange) {
33+
if cpu_id.0 != 0 {
34+
unimplemented!("additional CPUs not implemented: no idle stack space");
35+
}
36+
37+
// Next, work out where we will store our CpuId
38+
// data. We align up to page size to make paging
39+
// easier.
40+
let size = align_up(size_of::<CpuId>(), Size4KiB::SIZE as usize) as u64;
41+
let start = CPU_LOCAL.start() + cpu_id.as_u64() * size;
42+
let end = start + size;
43+
44+
// The page addresses should already be aligned,
45+
// so we shouldn't get any panics here.
46+
let start_page = Page::from_start_address(start).expect("bad start address");
47+
let end_page = Page::from_start_address(end).expect("bad end address");
48+
49+
// Map our per-CPU address space.
50+
let mut mapper = unsafe { kernel_pml4() };
51+
let mut frame_allocator = pmm::ALLOCATOR.lock();
52+
for page in Page::range(start_page, end_page) {
53+
let frame = frame_allocator
54+
.allocate_frame()
55+
.expect("failed to allocate for per-CPU data");
56+
57+
let flags = PageTableFlags::PRESENT | PageTableFlags::WRITABLE | PageTableFlags::NO_EXECUTE;
58+
unsafe {
59+
mapper
60+
.map_to(page, frame, flags, &mut *frame_allocator)
61+
.expect("failed to map per-CPU data")
62+
.flush()
63+
};
64+
}
65+
66+
// Store the pointer to the CpuData in the GS base.
67+
GsBase::write(start);
68+
69+
// Create our idle thread.
70+
let idle = Thread::new_idle_thread(stack_space);
71+
72+
// Initialise the CpuData from a pointer at the
73+
// start of the address space.
74+
let cpu_data = start.as_mut_ptr() as *mut CpuData;
75+
unsafe {
76+
cpu_data.write(CpuData {
77+
id: cpu_id,
78+
idle_thread: idle.clone(),
79+
current_thread: idle,
80+
});
81+
}
82+
}
83+
84+
// Helper functions to expose the CPU data.
85+
86+
/// cpu_data is our helper function to get
87+
/// the pointer to the CPU data from the
88+
/// GS register.
89+
///
90+
unsafe fn cpu_data() -> &'static mut CpuData {
91+
let ptr = GsBase::read();
92+
93+
&mut *(ptr.as_mut_ptr() as *mut CpuData)
94+
}
95+
96+
/// cpu_id returns this CPU's unique ID.
97+
///
98+
pub fn cpu_id() -> CpuId {
99+
unsafe { cpu_data() }.id
100+
}
101+
102+
/// idle_thread returns this CPU's idle thread.
103+
///
104+
pub fn idle_thread() -> Arc<Thread> {
105+
unsafe { cpu_data() }.idle_thread.clone()
106+
}
107+
108+
/// current_thread returns the currently executing thread.
109+
///
110+
pub fn current_thread() -> Arc<Thread> {
111+
unsafe { cpu_data() }.current_thread.clone()
112+
}
113+
114+
/// set_current_thread overwrites the currently executing
115+
/// thread.
116+
///
117+
pub fn set_current_thread(thread: Arc<Thread>) {
118+
unsafe { cpu_data() }.current_thread = thread;
119+
}
120+
121+
/// align_up aligns the given address upwards to alignment align.
122+
///
123+
/// Requires that align is a power of two.
124+
///
125+
fn align_up(addr: usize, align: usize) -> usize {
126+
(addr + align - 1) & !(align - 1)
127+
}
128+
129+
/// CpuId uniquely identifies a CPU core.
130+
///
131+
#[derive(Debug, Clone, Copy, PartialEq, Eq, PartialOrd, Ord)]
132+
pub struct CpuId(u64);
133+
134+
impl CpuId {
135+
/// new allocates and returns the next available
136+
/// CpuId.
137+
///
138+
pub fn new() -> Self {
139+
static NEXT_CPU_ID: AtomicU64 = AtomicU64::new(0);
140+
CpuId(NEXT_CPU_ID.fetch_add(1, Ordering::Relaxed))
141+
}
142+
143+
pub const fn as_u64(&self) -> u64 {
144+
self.0
145+
}
146+
}
147+
148+
// CpuData contains the data specific to an individual CPU core.
149+
//
150+
struct CpuData {
151+
id: CpuId,
152+
idle_thread: Arc<Thread>,
153+
current_thread: Arc<Thread>,
154+
}

kernel/src/multitasking/mod.rs

+1
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,6 @@
11
//! multitasking contains the functionality used to implement cooperative and
22
//! preemptive multitasking.
33
4+
pub mod cpu_local;
45
pub mod task;
56
pub mod thread;

kernel/src/multitasking/thread/mod.rs

+8-48
Original file line numberDiff line numberDiff line change
@@ -1,9 +1,9 @@
11
//! thread implements preemptive multitasking, using threads of execution.
22
3-
use crate::memory::{new_kernel_stack, StackBounds, KERNEL_STACK_0};
3+
use crate::memory::{new_kernel_stack, StackBounds, VirtAddrRange};
4+
use crate::multitasking::cpu_local::{current_thread, idle_thread, set_current_thread};
45
use crate::multitasking::thread::scheduler::Scheduler;
56
use crate::println;
6-
use crate::utils::lazy::Lazy;
77
use crate::utils::once::Once;
88
use alloc::collections::BTreeMap;
99
use alloc::sync::Arc;
@@ -15,17 +15,6 @@ use crossbeam::atomic::AtomicCell;
1515
mod scheduler;
1616
mod switch;
1717

18-
/// IDLE_THREAD is the idle kernel thread, which we
19-
/// only ever execute if no other threads are runnable.
20-
///
21-
/// Note that this is not thread-safe, as we haven't
22-
/// set up thread-local storage yet. However, as we
23-
/// haven't set up multiple CPUs yet either, it's
24-
/// safe in practice. We still have to use unsafe
25-
/// to access it, but for now that will have to do.
26-
///
27-
pub static mut IDLE_THREAD: Lazy<Arc<Thread>> = Lazy::new();
28-
2918
type ThreadTable = BTreeMap<ThreadId, Arc<Thread>>;
3019

3120
/// THREADS stores all living threads, referencing them by
@@ -39,23 +28,6 @@ static THREADS: spin::Mutex<ThreadTable> = spin::Mutex::new(BTreeMap::new());
3928
///
4029
static SCHEDULER: Once<spin::Mutex<Scheduler>> = Once::new();
4130

42-
/// CURRENT is the currently executing thread, which
43-
/// is initialised in init().
44-
///
45-
/// Note that this is not thread-safe, as we haven't
46-
/// set up thread-local storage yet. However, as we
47-
/// haven't set up multiple CPUs yet either, it's
48-
/// safe in practice. We still have to use unsafe
49-
/// to access it, but for now that will have to do.
50-
///
51-
static mut CURRENT: Lazy<Arc<Thread>> = Lazy::new();
52-
53-
/// current_thread returns the currently executing thread.
54-
///
55-
pub fn current_thread() -> &'static Arc<Thread> {
56-
unsafe { CURRENT.get() }
57-
}
58-
5931
/// DEAD_STACKS is a free list of kernel stacks that
6032
/// have been released by kernel threads that have
6133
/// exited.
@@ -72,11 +44,11 @@ pub fn current_thread() -> &'static Arc<Thread> {
7244
///
7345
static DEAD_STACKS: spin::Mutex<Vec<StackBounds>> = spin::Mutex::new(Vec::new());
7446

75-
/// idle_thread implements the idle thread. We
47+
/// idle_loop implements the idle thread. We
7648
/// fall back to this if the kernel has no other
7749
/// work left to do.
7850
///
79-
pub fn idle_thread() -> ! {
51+
pub fn idle_loop() -> ! {
8052
println!("Kernel entering the idle thread.");
8153
loop {
8254
println!("Shutting down.");
@@ -101,18 +73,6 @@ const DEFAULT_RFLAGS: u64 = 0x2;
10173
/// init prepares the thread scheduler.
10274
///
10375
pub fn init() {
104-
// Set up the idle thread so that when
105-
// kmain hands over to the scheduler,
106-
// the idle thread inherits the kernel's
107-
// initial stack and has something to
108-
// fall back to if no other threads
109-
// are runnable.
110-
let idle = Thread::new_idle_thread();
111-
unsafe {
112-
CURRENT.set(idle.clone());
113-
IDLE_THREAD.set(idle);
114-
}
115-
11676
SCHEDULER.init(|| spin::Mutex::new(Scheduler::new()));
11777
}
11878

@@ -135,7 +95,7 @@ pub fn switch() {
13595

13696
match scheduler.next() {
13797
Some(thread) => THREADS.lock().get(&thread).unwrap().clone(),
138-
None => unsafe { IDLE_THREAD.get().clone() },
98+
None => idle_thread(),
13999
}
140100
};
141101

@@ -153,7 +113,7 @@ pub fn switch() {
153113
let new_stack_pointer = next.stack_pointer.get();
154114

155115
// Switch into the next thread.
156-
unsafe { CURRENT.set(next.clone()) };
116+
set_current_thread(next.clone());
157117
unsafe { switch::switch_stack(current_stack_pointer, new_stack_pointer) };
158118
}
159119

@@ -257,7 +217,7 @@ impl Thread {
257217
/// new_idle_thread creates a new kernel thread, to
258218
/// which we fall back if no other threads are runnable.
259219
///
260-
fn new_idle_thread() -> Arc<Thread> {
220+
pub fn new_idle_thread(stack_space: &VirtAddrRange) -> Arc<Thread> {
261221
// The idle thread always has thread id 0, which
262222
// is otherwise invalid.
263223
let id = ThreadId(0);
@@ -273,7 +233,7 @@ impl Thread {
273233
// We inherit the kernel's initial stack, although
274234
// we never access any data previously stored
275235
// on the stack.
276-
let stack_bounds = Some(StackBounds::from(&KERNEL_STACK_0));
236+
let stack_bounds = Some(StackBounds::from(stack_space));
277237

278238
let thread = Arc::new(Thread {
279239
id,

0 commit comments

Comments
 (0)