mirror of
https://github.com/phil-opp/blog_os.git
synced 2025-12-16 14:27:49 +00:00
Use x86's TaskStateSegment and use usize for stack pointers
This commit is contained in:
@@ -307,47 +307,29 @@ Finally, we create and return a new `Stack`, which we define as follows:
|
|||||||
|
|
||||||
#[derive(Debug)]
|
#[derive(Debug)]
|
||||||
pub struct Stack {
|
pub struct Stack {
|
||||||
top: StackPointer,
|
top: usize,
|
||||||
bottom: StackPointer,
|
bottom: usize,
|
||||||
}
|
}
|
||||||
|
|
||||||
impl Stack {
|
impl Stack {
|
||||||
fn new(top: usize, bottom: usize) -> Stack {
|
fn new(top: usize, bottom: usize) -> Stack {
|
||||||
assert!(top > bottom);
|
assert!(top > bottom);
|
||||||
Stack {
|
Stack {
|
||||||
top: StackPointer::new(top),
|
top: top,
|
||||||
bottom: StackPointer::new(bottom),
|
bottom: bottom,
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
pub fn top(&self) -> StackPointer {
|
pub fn top(&self) -> StackPointer {
|
||||||
self.top
|
self.top
|
||||||
}
|
}
|
||||||
}
|
|
||||||
|
|
||||||
use core::nonzero::NonZero;
|
pub fn bottom(&self) -> StackPointer {
|
||||||
|
self.bottom
|
||||||
#[derive(Debug, Clone, Copy)]
|
|
||||||
pub struct StackPointer(NonZero<usize>);
|
|
||||||
|
|
||||||
impl StackPointer {
|
|
||||||
fn new(ptr: usize) -> StackPointer {
|
|
||||||
assert!(ptr != 0);
|
|
||||||
StackPointer(unsafe { NonZero::new(ptr) })
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
impl Into<usize> for StackPointer {
|
|
||||||
fn into(self) -> usize {
|
|
||||||
*self.0
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
```
|
```
|
||||||
The `Stack` struct describes a stack though its top and bottom pointers. A stack pointer can never be `0`, so we use the unstable [NonZero] wrapper for `StackPointer`. This wrapper is an optimization that tells the compiler that it can use the value `0` to differentiate enum variants. Thus, an `Option<StackPointer>` has always the same size as a bare `usize` (the value `0` is used to store the `None` case). We will require this feature when we create the Interrupt Stack Table later.
|
The `Stack` struct describes a stack though its top and bottom addresses.
|
||||||
|
|
||||||
Since `NonZero` is unstable, we need to add `#![feature(nonzero)]` in our `lib.rs`.
|
|
||||||
|
|
||||||
[NonZero]: https://doc.rust-lang.org/nightly/core/nonzero/struct.NonZero.html
|
|
||||||
|
|
||||||
#### The Memory Controller
|
#### The Memory Controller
|
||||||
Now we're able to allocate a new double fault stack. However, we add one more level of abstraction to make things easier. For that we add a new `MemoryController` type to our `memory` module:
|
Now we're able to allocate a new double fault stack. However, we add one more level of abstraction to make things easier. For that we add a new `MemoryController` type to our `memory` module:
|
||||||
@@ -355,7 +337,7 @@ Now we're able to allocate a new double fault stack. However, we add one more le
|
|||||||
```rust
|
```rust
|
||||||
// in src/memory/mod.rs
|
// in src/memory/mod.rs
|
||||||
|
|
||||||
pub use self::stack_allocator::{Stack, StackPointer};
|
pub use self::stack_allocator::Stack;
|
||||||
|
|
||||||
pub struct MemoryController {
|
pub struct MemoryController {
|
||||||
active_table: paging::ActivePageTable,
|
active_table: paging::ActivePageTable,
|
||||||
@@ -475,82 +457,17 @@ I/O Map Base Address | `u16`
|
|||||||
|
|
||||||
The _Privilege Stack Table_ is used by the CPU when the privilege level changes. For example, if an exception occurs while the CPU is in user mode (privilege level 3), the CPU normally switches to kernel mode (privilege level 0) before invoking the exception handler. In that case, the CPU would switch to the 0th stack in the Privilege Stack Table (since 0 is the target privilege level). We don't have any user mode programs yet, so we ignore this table for now.
|
The _Privilege Stack Table_ is used by the CPU when the privilege level changes. For example, if an exception occurs while the CPU is in user mode (privilege level 3), the CPU normally switches to kernel mode (privilege level 0) before invoking the exception handler. In that case, the CPU would switch to the 0th stack in the Privilege Stack Table (since 0 is the target privilege level). We don't have any user mode programs yet, so we ignore this table for now.
|
||||||
|
|
||||||
Let's create a `TaskStateSegment` struct in a new `tss` submodule:
|
#### Creating a TSS
|
||||||
|
Let's create a new TSS that contains our double fault stack in its Interrupt Stack Table. For that we need a TSS struct. Fortunately, the `x86` crate already contains a [`TaskStateSegment` struct] that we can use:
|
||||||
|
|
||||||
|
[`TaskStateSegment` struct]: https://docs.rs/x86/0.7.1/x86/task/struct.TaskStateSegment.html
|
||||||
|
|
||||||
```rust
|
```rust
|
||||||
// in src/interrupts/mod.rs
|
// in src/interrupts/mod.rs
|
||||||
|
|
||||||
mod tss;
|
use x86::task::TaskStateSegment;
|
||||||
|
|
||||||
// in src/interrupts/tss.rs
|
|
||||||
|
|
||||||
#[derive(Debug)]
|
|
||||||
#[repr(C, packed)]
|
|
||||||
pub struct TaskStateSegment {
|
|
||||||
reserved_0: u32,
|
|
||||||
pub privilege_stacks: PrivilegeStackTable,
|
|
||||||
reserved_1: u64,
|
|
||||||
pub interrupt_stacks: InterruptStackTable,
|
|
||||||
reserved_2: u64,
|
|
||||||
reserved_3: u16,
|
|
||||||
iomap_base: u16,
|
|
||||||
}
|
|
||||||
|
|
||||||
use memory::StackPointer;
|
|
||||||
|
|
||||||
#[derive(Debug)]
|
|
||||||
pub struct PrivilegeStackTable([Option<StackPointer>; 3]);
|
|
||||||
|
|
||||||
#[derive(Debug)]
|
|
||||||
pub struct InterruptStackTable([Option<StackPointer>; 7]);
|
|
||||||
```
|
```
|
||||||
|
|
||||||
We use [repr\(C)] for the struct since the order is fields is important. We also use `[repr(packed)]` because otherwise the compiler might insert additional padding between the `reserved_0` and `privilege_stacks` fields.
|
|
||||||
|
|
||||||
The `PrivilegeStackTable` and `InterruptStackTable` types are just newtype wrappers for arrays of `Option<StackPointer>`. Here it becomes important that we implemented `NonZero` for `StackPointer`: Thus, an `Option<StackPointer>` still has the required size of 64 bits.
|
|
||||||
|
|
||||||
Let's add a `TaskStateSegment::new` function that creates an empty TSS:
|
|
||||||
|
|
||||||
```rust
|
|
||||||
impl TaskStateSegment {
|
|
||||||
pub fn new() -> TaskStateSegment {
|
|
||||||
TaskStateSegment {
|
|
||||||
privilege_stacks: PrivilegeStackTable([None, None, None]),
|
|
||||||
interrupt_stacks: InterruptStackTable(
|
|
||||||
[None, None, None, None, None, None, None]),
|
|
||||||
iomap_base: 0,
|
|
||||||
reserved_0: 0,
|
|
||||||
reserved_1: 0,
|
|
||||||
reserved_2: 0,
|
|
||||||
reserved_3: 0,
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
```
|
|
||||||
|
|
||||||
We also add a `InterruptStackTable::insert_stack` method, that inserts a given stack into a free table entry:
|
|
||||||
|
|
||||||
```rust
|
|
||||||
use memory::Stack;
|
|
||||||
|
|
||||||
impl InterruptStackTable {
|
|
||||||
pub fn insert_stack(&mut self, stack: Stack) -> Result<u8, Stack> {
|
|
||||||
// TSS index starts at 1, so we do a `zip(1..)`
|
|
||||||
for (entry, i) in self.0.iter_mut().zip(1..) {
|
|
||||||
if entry.is_none() {
|
|
||||||
*entry = Some(stack.top());
|
|
||||||
return Ok(i);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
Err(stack)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
```
|
|
||||||
The function iterates over the table and places the stack pointer in the first free entry. In the case of success, we return the table index of the inserted pointer. If there's no free entry left, we return the stack back to the caller as `Err`.
|
|
||||||
|
|
||||||
#### Creating a TSS
|
|
||||||
Let's build a new TSS that contains our double fault stack in its Interrupt Stack Table.
|
|
||||||
|
|
||||||
The Global Descriptor Table (again)
|
The Global Descriptor Table (again)
|
||||||
Putting it together
|
Putting it together
|
||||||
What’s next?
|
What’s next?
|
||||||
|
|||||||
@@ -1,6 +1,6 @@
|
|||||||
use bit_field::BitField;
|
use bit_field::BitField;
|
||||||
use collections::vec::Vec;
|
use collections::vec::Vec;
|
||||||
use interrupts::tss::TaskStateSegment;
|
use x86::bits64::task::TaskStateSegment;
|
||||||
|
|
||||||
pub struct Gdt(Vec<u64>);
|
pub struct Gdt(Vec<u64>);
|
||||||
|
|
||||||
@@ -26,11 +26,11 @@ impl Gdt {
|
|||||||
}
|
}
|
||||||
|
|
||||||
pub fn load(&'static self) {
|
pub fn load(&'static self) {
|
||||||
use x86::dtables::{DescriptorTablePointer, lgdt};
|
use x86::shared::dtables::{DescriptorTablePointer, lgdt};
|
||||||
use core::mem::size_of;
|
use core::mem::size_of;
|
||||||
|
|
||||||
let ptr = DescriptorTablePointer {
|
let ptr = DescriptorTablePointer {
|
||||||
base: self.0.as_ptr() as u64,
|
base: self.0.as_ptr() as *const ::x86::shared::segmentation::SegmentDescriptor,
|
||||||
limit: (self.0.len() * size_of::<u64>() - 1) as u16,
|
limit: (self.0.len() * size_of::<u64>() - 1) as u16,
|
||||||
};
|
};
|
||||||
|
|
||||||
|
|||||||
@@ -109,7 +109,7 @@ impl EntryOptions {
|
|||||||
|
|
||||||
#[allow(dead_code)]
|
#[allow(dead_code)]
|
||||||
pub fn set_stack_index(&mut self, index: u8) -> &mut Self {
|
pub fn set_stack_index(&mut self, index: u8) -> &mut Self {
|
||||||
self.0.set_range(0..3, index.into());
|
self.0.set_range(0..3, (index + 1).into());
|
||||||
self
|
self
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -9,9 +9,9 @@
|
|||||||
|
|
||||||
use spin::Once;
|
use spin::Once;
|
||||||
use memory::MemoryController;
|
use memory::MemoryController;
|
||||||
|
use x86::bits64::task::TaskStateSegment;
|
||||||
|
|
||||||
mod idt;
|
mod idt;
|
||||||
mod tss;
|
|
||||||
mod gdt;
|
mod gdt;
|
||||||
|
|
||||||
macro_rules! save_scratch_registers {
|
macro_rules! save_scratch_registers {
|
||||||
@@ -92,22 +92,18 @@ macro_rules! handler_with_error_code {
|
|||||||
}
|
}
|
||||||
|
|
||||||
static IDT: Once<idt::Idt> = Once::new();
|
static IDT: Once<idt::Idt> = Once::new();
|
||||||
static TSS: Once<tss::TaskStateSegment> = Once::new();
|
static TSS: Once<TaskStateSegment> = Once::new();
|
||||||
static GDT: Once<gdt::Gdt> = Once::new();
|
static GDT: Once<gdt::Gdt> = Once::new();
|
||||||
|
|
||||||
pub fn init(memory_controller: &mut MemoryController) {
|
pub fn init(memory_controller: &mut MemoryController) {
|
||||||
let double_fault_stack = memory_controller.alloc_stack(1)
|
let double_fault_stack = memory_controller.alloc_stack(1)
|
||||||
.expect("could not allocate double fault stack");
|
.expect("could not allocate double fault stack");
|
||||||
|
|
||||||
let mut double_fault_ist_index = 0;
|
const DOUBLE_FAULT_IST_INDEX: u8 = 0;
|
||||||
|
|
||||||
let tss = TSS.call_once(|| {
|
let tss = TSS.call_once(|| {
|
||||||
let mut tss = tss::TaskStateSegment::new();
|
let mut tss = TaskStateSegment::new();
|
||||||
|
tss.ist[DOUBLE_FAULT_IST_INDEX as usize] = double_fault_stack.top() as u64;
|
||||||
double_fault_ist_index = tss.interrupt_stacks
|
|
||||||
.insert_stack(double_fault_stack)
|
|
||||||
.expect("IST flush_all");
|
|
||||||
|
|
||||||
tss
|
tss
|
||||||
});
|
});
|
||||||
|
|
||||||
@@ -134,7 +130,7 @@ pub fn init(memory_controller: &mut MemoryController) {
|
|||||||
idt.set_handler(3, handler!(breakpoint_handler));
|
idt.set_handler(3, handler!(breakpoint_handler));
|
||||||
idt.set_handler(6, handler!(invalid_opcode_handler));
|
idt.set_handler(6, handler!(invalid_opcode_handler));
|
||||||
idt.set_handler(8, handler_with_error_code!(double_fault_handler))
|
idt.set_handler(8, handler_with_error_code!(double_fault_handler))
|
||||||
.set_stack_index(double_fault_ist_index);
|
.set_stack_index(DOUBLE_FAULT_IST_INDEX);
|
||||||
idt.set_handler(14, handler_with_error_code!(page_fault_handler));
|
idt.set_handler(14, handler_with_error_code!(page_fault_handler));
|
||||||
|
|
||||||
idt
|
idt
|
||||||
|
|||||||
@@ -1,46 +0,0 @@
|
|||||||
use memory::{Stack, StackPointer};
|
|
||||||
|
|
||||||
#[derive(Debug)]
|
|
||||||
#[repr(C, packed)]
|
|
||||||
pub struct TaskStateSegment {
|
|
||||||
reserved_0: u32,
|
|
||||||
pub privilege_stacks: PrivilegeStackTable,
|
|
||||||
reserved_1: u64,
|
|
||||||
pub interrupt_stacks: InterruptStackTable,
|
|
||||||
reserved_2: u64,
|
|
||||||
reserved_3: u16,
|
|
||||||
iomap_base: u16,
|
|
||||||
}
|
|
||||||
|
|
||||||
impl TaskStateSegment {
|
|
||||||
pub fn new() -> TaskStateSegment {
|
|
||||||
TaskStateSegment {
|
|
||||||
privilege_stacks: PrivilegeStackTable([None, None, None]),
|
|
||||||
interrupt_stacks: InterruptStackTable([None, None, None, None, None, None, None]),
|
|
||||||
iomap_base: 0,
|
|
||||||
reserved_0: 0,
|
|
||||||
reserved_1: 0,
|
|
||||||
reserved_2: 0,
|
|
||||||
reserved_3: 0,
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
#[derive(Debug)]
|
|
||||||
pub struct PrivilegeStackTable([Option<StackPointer>; 3]);
|
|
||||||
|
|
||||||
#[derive(Debug)]
|
|
||||||
pub struct InterruptStackTable([Option<StackPointer>; 7]);
|
|
||||||
|
|
||||||
impl InterruptStackTable {
|
|
||||||
pub fn insert_stack(&mut self, stack: Stack) -> Result<u8, Stack> {
|
|
||||||
// TSS index starts at 1
|
|
||||||
for (entry, i) in self.0.iter_mut().zip(1..) {
|
|
||||||
if entry.is_none() {
|
|
||||||
*entry = Some(stack.top());
|
|
||||||
return Ok(i);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
Err(stack)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
@@ -13,7 +13,6 @@
|
|||||||
#![feature(asm)]
|
#![feature(asm)]
|
||||||
#![feature(naked_functions)]
|
#![feature(naked_functions)]
|
||||||
#![feature(core_intrinsics)]
|
#![feature(core_intrinsics)]
|
||||||
#![feature(nonzero)]
|
|
||||||
#![feature(drop_types_in_const)]
|
#![feature(drop_types_in_const)]
|
||||||
#![no_std]
|
#![no_std]
|
||||||
|
|
||||||
|
|||||||
@@ -9,7 +9,7 @@
|
|||||||
|
|
||||||
pub use self::area_frame_allocator::AreaFrameAllocator;
|
pub use self::area_frame_allocator::AreaFrameAllocator;
|
||||||
pub use self::paging::remap_the_kernel;
|
pub use self::paging::remap_the_kernel;
|
||||||
pub use self::stack_allocator::{Stack, StackPointer};
|
pub use self::stack_allocator::Stack;
|
||||||
use self::paging::PhysicalAddress;
|
use self::paging::PhysicalAddress;
|
||||||
use multiboot2::BootInformation;
|
use multiboot2::BootInformation;
|
||||||
|
|
||||||
|
|||||||
@@ -1,6 +1,5 @@
|
|||||||
use memory::paging::{self, Page, PageIter, ActivePageTable};
|
use memory::paging::{self, Page, PageIter, ActivePageTable};
|
||||||
use memory::{PAGE_SIZE, FrameAllocator};
|
use memory::{PAGE_SIZE, FrameAllocator};
|
||||||
use core::nonzero::NonZero;
|
|
||||||
|
|
||||||
pub fn new_stack_allocator(page_range: PageIter) -> StackAllocator {
|
pub fn new_stack_allocator(page_range: PageIter) -> StackAllocator {
|
||||||
StackAllocator { range: page_range }
|
StackAllocator { range: page_range }
|
||||||
@@ -52,36 +51,24 @@ impl StackAllocator {
|
|||||||
|
|
||||||
#[derive(Debug)]
|
#[derive(Debug)]
|
||||||
pub struct Stack {
|
pub struct Stack {
|
||||||
top: StackPointer,
|
top: usize,
|
||||||
bottom: StackPointer,
|
bottom: usize,
|
||||||
}
|
}
|
||||||
|
|
||||||
impl Stack {
|
impl Stack {
|
||||||
fn new(top: usize, bottom: usize) -> Stack {
|
fn new(top: usize, bottom: usize) -> Stack {
|
||||||
assert!(top > bottom);
|
assert!(top > bottom);
|
||||||
Stack {
|
Stack {
|
||||||
top: StackPointer::new(top),
|
top: top,
|
||||||
bottom: StackPointer::new(bottom),
|
bottom: bottom,
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
pub fn top(&self) -> StackPointer {
|
pub fn top(&self) -> usize {
|
||||||
self.top
|
self.top
|
||||||
}
|
}
|
||||||
}
|
|
||||||
|
|
||||||
#[derive(Debug, Clone, Copy)]
|
pub fn bottom(&self) -> usize {
|
||||||
pub struct StackPointer(NonZero<usize>);
|
self.bottom
|
||||||
|
|
||||||
impl StackPointer {
|
|
||||||
fn new(ptr: usize) -> StackPointer {
|
|
||||||
assert!(ptr != 0);
|
|
||||||
StackPointer(unsafe { NonZero::new(ptr) })
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
impl Into<usize> for StackPointer {
|
|
||||||
fn into(self) -> usize {
|
|
||||||
*self.0
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
Reference in New Issue
Block a user