Compare commits

..

33 Commits

Author SHA1 Message Date
Philipp Oppermann
7f6576c9dc Set the stack index of the double fault IDT entry 2017-11-19 14:24:33 +01:00
Philipp Oppermann
aa8028cf6c Reload cs register and load TSS 2017-11-19 14:24:33 +01:00
Philipp Oppermann
165054d12a Create and load a static TSS and GDT 2017-11-19 14:24:33 +01:00
Philipp Oppermann
58e90d497e Add a Gdt::load method 2017-11-19 14:24:33 +01:00
Philipp Oppermann
238cc575c3 Add a Gdt::add_entry method 2017-11-19 14:24:33 +01:00
Philipp Oppermann
d2060e76f8 Create a GDT descriptor struct 2017-11-19 14:24:33 +01:00
Philipp Oppermann
f651987666 Add a dependency on bit_field 2017-11-19 14:24:33 +01:00
Philipp Oppermann
eea8c10a97 Create a interrupts::gdt submodule 2017-11-19 14:24:27 +01:00
Philipp Oppermann
e1d2af5ea7 Rename interrupts.rs to interrupts/mod.rs 2017-11-19 14:24:27 +01:00
Philipp Oppermann
a05db759d7 Create a TSS and save the double fault stack pointer it its IST 2017-11-19 14:24:27 +01:00
Philipp Oppermann
00bbd6fbc6 Pass the memory controller to interrupts::init and use it to create a double fault stack 2017-11-19 14:24:27 +01:00
Philipp Oppermann
f1459a552c Return a memory controller from memory::init 2017-11-19 14:24:27 +01:00
Philipp Oppermann
73d4390f27 Create a memory manager for convenience 2017-11-19 14:23:45 +01:00
Philipp Oppermann
0e3857ca50 Create a stack allocator module 2017-11-19 14:23:45 +01:00
Philipp Oppermann
3efe54169e Provoke a stack overflow, which still causes a boot loop 2017-11-19 14:23:45 +01:00
Philipp Oppermann
2b9d880e48 Add a double fault handler 2017-11-19 14:23:45 +01:00
Philipp Oppermann
91ffde4728 Trigger a double fault by causing a page fault 2017-11-19 14:23:45 +01:00
Philipp Oppermann
590b2fd1b0 Update Readme for “Double Faults” post 2017-11-19 14:23:45 +01:00
Philipp Oppermann
93aff8cfa8 Test our exception handler by invoking a breakpoint exception 2017-11-19 14:22:24 +01:00
Philipp Oppermann
fab320271a Create and load an IDT 2017-11-19 14:21:51 +01:00
Philipp Oppermann
7becaf5f30 Add a dependency on lazy_static 2017-11-19 14:21:51 +01:00
Philipp Oppermann
3bbc2a0bdc Add a simple handler function for the breakpoint exception 2017-11-19 14:21:36 +01:00
Philipp Oppermann
c2d22af1c7 Create a new interrupts module 2017-11-19 14:21:12 +01:00
Philipp Oppermann
0ddd214a1b Update Readme for “Handling Exceptions” post 2017-11-19 14:21:00 +01:00
Philipp Oppermann
ad211de615 Use linked list allocator instead of bump allocator 2017-11-19 14:20:46 +01:00
Philipp Oppermann
01f8c43ffb Map the heap pages to physical frames 2017-11-19 14:20:46 +01:00
Philipp Oppermann
f2bbe43099 Use once crate to ensure that memory::init is only called once 2017-11-19 14:20:45 +01:00
Philipp Oppermann
76550dcd95 Refactor: Move memory initialization to memory::init function 2017-11-19 14:20:45 +01:00
Philipp Oppermann
c0d403abbe Set a global allocator 2017-11-19 14:20:45 +01:00
Philipp Oppermann
9dc998222a Make the bump allocator lock free and impl Alloc for shared reference 2017-11-19 14:20:45 +01:00
Philipp Oppermann
1f6633fe44 Add a heap_allocator module with a basic bump allocator 2017-11-19 14:20:45 +01:00
Philipp Oppermann
5f017124dd Add a dependency on the alloc crate 2017-11-19 14:20:45 +01:00
Philipp Oppermann
36369cfbe2 Update Readme for “Kernel Heap” post 2017-11-19 14:20:45 +01:00
10 changed files with 494 additions and 36 deletions

View File

@@ -7,9 +7,16 @@ authors = ["Philipp Oppermann <dev@phil-opp.com>"]
crate-type = ["staticlib"]
[dependencies]
bit_field = "0.7.0"
rlibc = "1.0"
volatile = "0.1.0"
spin = "0.4.5"
multiboot2 = "0.1.0"
bitflags = "0.7.0"
x86_64 = "0.1.2"
once = "0.3.3"
linked_list_allocator = "0.4.2"
[dependencies.lazy_static]
version = "0.2.4"
features = ["spin_no_std"]

View File

@@ -1,12 +1,14 @@
# Blog OS (Remap the Kernel)
[![Build Status](https://travis-ci.org/phil-opp/blog_os.svg?branch=post_7)](https://travis-ci.org/phil-opp/blog_os/branches)
# Blog OS (Double Faults)
[![Build Status](https://travis-ci.org/phil-opp/blog_os.svg?branch=post_10)](https://travis-ci.org/phil-opp/blog_os/branches)
This repository contains the source code for the [Remap the Kernel](http://os.phil-opp.com/remap-the-kernel.html) post of the [Writing an OS in Rust](http://os.phil-opp.com) series.
This repository contains the source code for the [Double Faults](http://os.phil-opp.com/double-faults.html) post of the [Writing an OS in Rust](http://os.phil-opp.com) series.
**Check out the [master branch](https://github.com/phil-opp/blog_os) for more information.**
## Building
You need to have `nasm`, `grub-mkrescue`, `xorriso`, `qemu`, and a nightly Rust compiler installed. Then you can run it using `make run`.
You need to have `nasm`, `grub-mkrescue`, `xorriso`, `qemu`, a nightly Rust compiler, and [xargo] installed. Then you can run it using `make run`.
[xargo]: https://github.com/japaric/xargo
Please file an issue if you have any problems.

2
Xargo.toml Normal file
View File

@@ -0,0 +1,2 @@
[target.x86_64-blog_os.dependencies]
alloc = {}

95
src/interrupts/gdt.rs Normal file
View File

@@ -0,0 +1,95 @@
use x86_64::structures::tss::TaskStateSegment;
use x86_64::structures::gdt::SegmentSelector;
use x86_64::PrivilegeLevel;
pub struct Gdt {
table: [u64; 8],
next_free: usize,
}
impl Gdt {
pub fn new() -> Gdt {
Gdt {
table: [0; 8],
next_free: 1,
}
}
pub fn add_entry(&mut self, entry: Descriptor) -> SegmentSelector {
let index = match entry {
Descriptor::UserSegment(value) => self.push(value),
Descriptor::SystemSegment(value_low, value_high) => {
let index = self.push(value_low);
self.push(value_high);
index
}
};
SegmentSelector::new(index as u16, PrivilegeLevel::Ring0)
}
pub fn load(&'static self) {
use x86_64::instructions::tables::{DescriptorTablePointer, lgdt};
use core::mem::size_of;
let ptr = DescriptorTablePointer {
base: self.table.as_ptr() as u64,
limit: (self.table.len() * size_of::<u64>() - 1) as u16,
};
unsafe { lgdt(&ptr) };
}
fn push(&mut self, value: u64) -> usize {
if self.next_free < self.table.len() {
let index = self.next_free;
self.table[index] = value;
self.next_free += 1;
index
} else {
panic!("GDT full");
}
}
}
pub enum Descriptor {
UserSegment(u64),
SystemSegment(u64, u64),
}
impl Descriptor {
pub fn kernel_code_segment() -> Descriptor {
let flags = USER_SEGMENT | PRESENT | EXECUTABLE | LONG_MODE;
Descriptor::UserSegment(flags.bits())
}
pub fn tss_segment(tss: &'static TaskStateSegment) -> Descriptor {
use core::mem::size_of;
use bit_field::BitField;
let ptr = tss as *const _ as u64;
let mut low = PRESENT.bits();
// base
low.set_bits(16..40, ptr.get_bits(0..24));
low.set_bits(56..64, ptr.get_bits(24..32));
// limit (the `-1` in needed since the bound is inclusive)
low.set_bits(0..16, (size_of::<TaskStateSegment>() - 1) as u64);
// type (0b1001 = available 64-bit tss)
low.set_bits(40..44, 0b1001);
let mut high = 0;
high.set_bits(0..32, ptr.get_bits(32..64));
Descriptor::SystemSegment(low, high)
}
}
bitflags! {
flags DescriptorFlags: u64 {
const CONFORMING = 1 << 42,
const EXECUTABLE = 1 << 43,
const USER_SEGMENT = 1 << 44,
const PRESENT = 1 << 47,
const LONG_MODE = 1 << 53,
}
}

72
src/interrupts/mod.rs Normal file
View File

@@ -0,0 +1,72 @@
use x86_64::VirtualAddress;
use x86_64::structures::idt::{Idt, ExceptionStackFrame};
use x86_64::structures::tss::TaskStateSegment;
use memory::MemoryController;
use spin::Once;
mod gdt;
lazy_static! {
static ref IDT: Idt = {
let mut idt = Idt::new();
idt.breakpoint.set_handler_fn(breakpoint_handler);
unsafe {
idt.double_fault.set_handler_fn(double_fault_handler)
.set_stack_index(DOUBLE_FAULT_IST_INDEX as u16);
}
idt
};
}
static TSS: Once<TaskStateSegment> = Once::new();
static GDT: Once<gdt::Gdt> = Once::new();
const DOUBLE_FAULT_IST_INDEX: usize = 0;
pub fn init(memory_controller: &mut MemoryController) {
use x86_64::structures::gdt::SegmentSelector;
use x86_64::instructions::segmentation::set_cs;
use x86_64::instructions::tables::load_tss;
let double_fault_stack = memory_controller.alloc_stack(1)
.expect("could not allocate double fault stack");
let tss = TSS.call_once(|| {
let mut tss = TaskStateSegment::new();
tss.interrupt_stack_table[DOUBLE_FAULT_IST_INDEX] = VirtualAddress(
double_fault_stack.top());
tss
});
let mut code_selector = SegmentSelector(0);
let mut tss_selector = SegmentSelector(0);
let gdt = GDT.call_once(|| {
let mut gdt = gdt::Gdt::new();
code_selector = gdt.add_entry(gdt::Descriptor::kernel_code_segment());
tss_selector = gdt.add_entry(gdt::Descriptor::tss_segment(&tss));
gdt
});
gdt.load();
unsafe {
// reload code segment register
set_cs(code_selector);
// load TSS
load_tss(tss_selector);
}
IDT.load();
}
extern "x86-interrupt" fn breakpoint_handler(
stack_frame: &mut ExceptionStackFrame)
{
println!("EXCEPTION: BREAKPOINT\n{:#?}", stack_frame);
}
extern "x86-interrupt" fn double_fault_handler(
stack_frame: &mut ExceptionStackFrame, _error_code: u64)
{
println!("\nEXCEPTION: DOUBLE FAULT\n{:#?}", stack_frame);
loop {}
}

View File

@@ -1,9 +1,17 @@
#![feature(lang_items)]
#![feature(const_fn)]
#![feature(const_unique_new)]
#![feature(alloc)]
#![feature(const_unique_new, const_atomic_usize_new)]
#![feature(unique)]
#![feature(allocator_api)]
#![feature(global_allocator)]
#![feature(abi_x86_interrupt)]
#![no_std]
#[macro_use]
extern crate alloc;
extern crate rlibc;
extern crate volatile;
extern crate spin;
@@ -11,45 +19,55 @@ extern crate multiboot2;
#[macro_use]
extern crate bitflags;
extern crate x86_64;
#[macro_use]
extern crate once;
extern crate linked_list_allocator;
#[macro_use]
extern crate lazy_static;
extern crate bit_field;
#[macro_use]
mod vga_buffer;
mod memory;
mod interrupts;
#[no_mangle]
pub extern fn rust_main(multiboot_information_address: usize) {
use memory::FrameAllocator;
pub extern "C" fn rust_main(multiboot_information_address: usize) {
// ATTENTION: we have a very small stack and no guard page
vga_buffer::clear_screen();
println!("Hello World{}", "!");
let boot_info = unsafe{ multiboot2::load(multiboot_information_address) };
let memory_map_tag = boot_info.memory_map_tag()
.expect("Memory map tag required");
let elf_sections_tag = boot_info.elf_sections_tag()
.expect("Elf sections tag required");
let kernel_start = elf_sections_tag.sections().map(|s| s.addr)
.min().unwrap();
let kernel_end = elf_sections_tag.sections().map(|s| s.addr + s.size)
.max().unwrap();
let multiboot_start = multiboot_information_address;
let multiboot_end = multiboot_start + (boot_info.total_size as usize);
println!("kernel start: 0x{:x}, kernel end: 0x{:x}",
kernel_start, kernel_end);
println!("multiboot start: 0x{:x}, multiboot end: 0x{:x}",
multiboot_start, multiboot_end);
let mut frame_allocator = memory::AreaFrameAllocator::new(
kernel_start as usize, kernel_end as usize, multiboot_start,
multiboot_end, memory_map_tag.memory_areas());
let boot_info = unsafe {
multiboot2::load(multiboot_information_address)
};
enable_nxe_bit();
enable_write_protect_bit();
memory::remap_the_kernel(&mut frame_allocator, boot_info);
println!("It did not crash!");
// set up guard page and map the heap pages
let mut memory_controller = memory::init(boot_info);
unsafe {
HEAP_ALLOCATOR.lock().init(HEAP_START, HEAP_START + HEAP_SIZE);
}
// initialize our IDT
interrupts::init(&mut memory_controller);
for i in 0..10000 {
format!("Some String");
}
// invoke a breakpoint exception
x86_64::instructions::interrupts::int3();
fn stack_overflow() {
stack_overflow(); // for each recursion, the return address is pushed
}
// trigger a stack overflow
stack_overflow();
println!("It did not crash!");
loop {}
}
@@ -78,3 +96,11 @@ pub extern fn panic_fmt(fmt: core::fmt::Arguments, file: &'static str, line: u32
println!(" {}", fmt);
loop{}
}
use linked_list_allocator::LockedHeap;
pub const HEAP_START: usize = 0o_000_001_000_000_0000;
pub const HEAP_SIZE: usize = 100 * 1024; // 100 KiB
#[global_allocator]
static HEAP_ALLOCATOR: LockedHeap = LockedHeap::empty();

View File

@@ -0,0 +1,62 @@
use alloc::heap::{Alloc, AllocErr, Layout};
use core::sync::atomic::{AtomicUsize, Ordering};
/// A simple allocator that allocates memory linearly and ignores freed memory.
#[derive(Debug)]
pub struct BumpAllocator {
heap_start: usize,
heap_end: usize,
next: AtomicUsize,
}
impl BumpAllocator {
pub const fn new(heap_start: usize, heap_end: usize) -> Self {
Self { heap_start, heap_end, next: AtomicUsize::new(heap_start) }
}
}
unsafe impl<'a> Alloc for &'a BumpAllocator {
unsafe fn alloc(&mut self, layout: Layout) -> Result<*mut u8, AllocErr> {
loop {
// load current state of the `next` field
let current_next = self.next.load(Ordering::Relaxed);
let alloc_start = align_up(current_next, layout.align());
let alloc_end = alloc_start.saturating_add(layout.size());
if alloc_end <= self.heap_end {
// update the `next` pointer if it still has the value `current_next`
let next_now = self.next.compare_and_swap(current_next, alloc_end,
Ordering::Relaxed);
if next_now == current_next {
// next address was successfully updated, allocation succeeded
return Ok(alloc_start as *mut u8);
}
} else {
return Err(AllocErr::Exhausted{ request: layout })
}
}
}
unsafe fn dealloc(&mut self, ptr: *mut u8, layout: Layout) {
// do nothing, leak memory
}
}
/// Align downwards. Returns the greatest x with alignment `align`
/// so that x <= addr. The alignment must be a power of 2.
pub fn align_down(addr: usize, align: usize) -> usize {
if align.is_power_of_two() {
addr & !(align - 1)
} else if align == 0 {
addr
} else {
panic!("`align` must be a power of 2");
}
}
/// Align upwards. Returns the smallest x with alignment `align`
/// so that x >= addr. The alignment must be a power of 2.
pub fn align_up(addr: usize, align: usize) -> usize {
align_down(addr + align - 1, align)
}

View File

@@ -1,12 +1,70 @@
pub use self::area_frame_allocator::AreaFrameAllocator;
pub use self::paging::remap_the_kernel;
pub use self::stack_allocator::Stack;
use self::paging::PhysicalAddress;
use multiboot2::BootInformation;
mod area_frame_allocator;
pub mod heap_allocator;
mod paging;
mod stack_allocator;
pub const PAGE_SIZE: usize = 4096;
pub fn init(boot_info: &BootInformation) -> MemoryController {
assert_has_not_been_called!("memory::init must be called only once");
let memory_map_tag = boot_info.memory_map_tag().expect(
"Memory map tag required");
let elf_sections_tag = boot_info.elf_sections_tag().expect(
"Elf sections tag required");
let kernel_start = elf_sections_tag.sections()
.filter(|s| s.is_allocated()).map(|s| s.addr).min().unwrap();
let kernel_end = elf_sections_tag.sections()
.filter(|s| s.is_allocated()).map(|s| s.addr + s.size).max()
.unwrap();
println!("kernel start: {:#x}, kernel end: {:#x}",
kernel_start,
kernel_end);
println!("multiboot start: {:#x}, multiboot end: {:#x}",
boot_info.start_address(),
boot_info.end_address());
let mut frame_allocator = AreaFrameAllocator::new(
kernel_start as usize, kernel_end as usize,
boot_info.start_address(), boot_info.end_address(),
memory_map_tag.memory_areas());
let mut active_table = paging::remap_the_kernel(&mut frame_allocator,
boot_info);
use self::paging::Page;
use {HEAP_START, HEAP_SIZE};
let heap_start_page = Page::containing_address(HEAP_START);
let heap_end_page = Page::containing_address(HEAP_START + HEAP_SIZE-1);
for page in Page::range_inclusive(heap_start_page, heap_end_page) {
active_table.map(page, paging::WRITABLE, &mut frame_allocator);
}
let stack_allocator = {
let stack_alloc_start = heap_end_page + 1;
let stack_alloc_end = stack_alloc_start + 100;
let stack_alloc_range = Page::range_inclusive(stack_alloc_start,
stack_alloc_end);
stack_allocator::StackAllocator::new(stack_alloc_range)
};
MemoryController {
active_table: active_table,
frame_allocator: frame_allocator,
stack_allocator: stack_allocator,
}
}
#[derive(Debug, PartialEq, Eq, PartialOrd, Ord)]
pub struct Frame {
number: usize,
@@ -56,3 +114,19 @@ pub trait FrameAllocator {
fn allocate_frame(&mut self) -> Option<Frame>;
fn deallocate_frame(&mut self, frame: Frame);
}
pub struct MemoryController {
active_table: paging::ActivePageTable,
frame_allocator: AreaFrameAllocator,
stack_allocator: stack_allocator::StackAllocator,
}
impl MemoryController {
pub fn alloc_stack(&mut self, size_in_pages: usize) -> Option<Stack> {
let &mut MemoryController { ref mut active_table,
ref mut frame_allocator,
ref mut stack_allocator } = self;
stack_allocator.alloc_stack(active_table, frame_allocator,
size_in_pages)
}
}

View File

@@ -1,6 +1,6 @@
pub use self::entry::*;
pub use self::mapper::Mapper;
use core::ops::{Deref, DerefMut};
use core::ops::{Deref, DerefMut, Add};
use core::ptr::Unique;
use memory::{PAGE_SIZE, Frame, FrameAllocator};
use multiboot2::BootInformation;
@@ -17,7 +17,7 @@ const ENTRY_COUNT: usize = 512;
pub type PhysicalAddress = usize;
pub type VirtualAddress = usize;
#[derive(Debug, Clone, Copy)]
#[derive(Debug, Clone, Copy, PartialEq, Eq, PartialOrd, Ord)]
pub struct Page {
number: usize,
}
@@ -30,7 +30,7 @@ impl Page {
Page { number: address / PAGE_SIZE }
}
fn start_address(&self) -> usize {
pub fn start_address(&self) -> usize {
self.number * PAGE_SIZE
}
@@ -46,6 +46,42 @@ impl Page {
fn p1_index(&self) -> usize {
(self.number >> 0) & 0o777
}
pub fn range_inclusive(start: Page, end: Page) -> PageIter {
PageIter {
start: start,
end: end,
}
}
}
impl Add<usize> for Page {
type Output = Page;
fn add(self, rhs: usize) -> Page {
Page { number: self.number + rhs }
}
}
#[derive(Clone)]
pub struct PageIter {
start: Page,
end: Page,
}
impl Iterator for PageIter {
type Item = Page;
fn next(&mut self) -> Option<Page> {
if self.start <= self.end {
let page = self.start;
self.start.number += 1;
Some(page)
} else {
None
}
}
}
pub struct ActivePageTable {
@@ -145,6 +181,7 @@ impl InactivePageTable {
}
pub fn remap_the_kernel<A>(allocator: &mut A, boot_info: &BootInformation)
-> ActivePageTable
where A: FrameAllocator
{
let mut temporary_page = TemporaryPage::new(Page { number: 0xcafebabe },
@@ -203,4 +240,6 @@ pub fn remap_the_kernel<A>(allocator: &mut A, boot_info: &BootInformation)
);
active_table.unmap(old_p4_page, allocator);
println!("guard page at {:#x}", old_p4_page.start_address());
active_table
}

View File

@@ -0,0 +1,79 @@
use memory::paging::{self, Page, PageIter, ActivePageTable};
use memory::{PAGE_SIZE, FrameAllocator};
pub struct StackAllocator {
range: PageIter,
}
impl StackAllocator {
pub fn new(page_range: PageIter) -> StackAllocator {
StackAllocator { range: page_range }
}
}
impl StackAllocator {
pub fn alloc_stack<FA: FrameAllocator>(&mut self,
active_table: &mut ActivePageTable,
frame_allocator: &mut FA,
size_in_pages: usize)
-> Option<Stack> {
if size_in_pages == 0 {
return None; /* a zero sized stack makes no sense */
}
// clone the range, since we only want to change it on success
let mut range = self.range.clone();
// try to allocate the stack pages and a guard page
let guard_page = range.next();
let stack_start = range.next();
let stack_end = if size_in_pages == 1 {
stack_start
} else {
// choose the (size_in_pages-2)th element, since index
// starts at 0 and we already allocated the start page
range.nth(size_in_pages - 2)
};
match (guard_page, stack_start, stack_end) {
(Some(_), Some(start), Some(end)) => {
// success! write back updated range
self.range = range;
// map stack pages to physical frames
for page in Page::range_inclusive(start, end) {
active_table.map(page, paging::WRITABLE, frame_allocator);
}
// create a new stack
let top_of_stack = end.start_address() + PAGE_SIZE;
Some(Stack::new(top_of_stack, start.start_address()))
}
_ => None, /* not enough pages */
}
}
}
#[derive(Debug)]
pub struct Stack {
top: usize,
bottom: usize,
}
impl Stack {
fn new(top: usize, bottom: usize) -> Stack {
assert!(top > bottom);
Stack {
top: top,
bottom: bottom,
}
}
pub fn top(&self) -> usize {
self.top
}
pub fn bottom(&self) -> usize {
self.bottom
}
}