Compare commits

..

35 Commits

Author SHA1 Message Date
Philipp Oppermann
1007c5157d Remove paging testing code again 2017-11-19 11:42:16 +01:00
Philipp Oppermann
55e602dc61 Fix unmap function by flushing the TLB 2017-11-19 11:42:16 +01:00
Philipp Oppermann
eb5c9afa51 Add a dependency on the x86_64 crate 2017-11-19 11:42:16 +01:00
Philipp Oppermann
0ee4ed738a Test the unmap function (it doesn't work correctly) 2017-11-19 11:42:16 +01:00
Philipp Oppermann
b49cb6986c Test the map_to function 2017-11-19 11:42:16 +01:00
Philipp Oppermann
15c9f43622 Add an (unfinished) unmap method 2017-11-19 11:42:16 +01:00
Philipp Oppermann
3696c7bacb Add map and identity_map functions for convenience 2017-11-19 11:42:16 +01:00
Philipp Oppermann
bb473c7907 Make translate and map_to safe by making them methods of a new ActivePageTable struct 2017-11-19 11:42:16 +01:00
Philipp Oppermann
dedf6e8959 Add a map_to function 2017-11-19 11:42:16 +01:00
Philipp Oppermann
83d56aa9de Add translate and translate_page functions 2017-11-19 11:42:16 +01:00
Philipp Oppermann
a7170a4a44 Use type system tricks to make next_table methods safe 2017-11-19 11:42:16 +01:00
Philipp Oppermann
0874625269 Add methods to retrieve a (mutual) reference to the next table 2017-11-19 10:39:17 +01:00
Philipp Oppermann
3706331a43 Add a P4 constant and a Table::next_table_address method 2017-11-19 10:39:17 +01:00
Philipp Oppermann
8fcea7951b Recursive map the P4 table 2017-11-19 10:39:17 +01:00
Philipp Oppermann
e50d70c02e Add a Table::zero method to clear all entries 2017-11-19 10:39:17 +01:00
Philipp Oppermann
2b7fa410fb Implement the Index and IndexMut traits for Table 2017-11-19 10:39:17 +01:00
Philipp Oppermann
62d655fbdd Create a memory::paging::table submodule with a Table struct 2017-11-19 10:39:17 +01:00
Philipp Oppermann
fe464463eb Add pointed_frame and set methods to Entry 2017-11-19 10:39:17 +01:00
Philipp Oppermann
148d506629 Use bitflags to create an EntryFlags type 2017-11-19 10:39:17 +01:00
Philipp Oppermann
f957f2ecde Add a dependency on the bitflags crate 2017-11-19 10:39:17 +01:00
Philipp Oppermann
c4c27c10e6 Create a memory::paging::entry submodule with an Entry struct 2017-11-19 10:39:17 +01:00
Philipp Oppermann
c7c02d7dca Create a memory::paging module with a Page struct 2017-11-19 10:39:17 +01:00
Philipp Oppermann
9f84e37e1b Update Readme for “Page Tables” post 2017-11-19 10:39:17 +01:00
Philipp Oppermann
9f1a69cafa Create a new AreaFrameAllocator and allocate maximum number of frames 2017-11-19 10:39:13 +01:00
Philipp Oppermann
edb2e693da Create an AreaFrameAllocator 2017-11-19 10:39:13 +01:00
Philipp Oppermann
84c337e6f0 Create a memory module with a Frame struct and FrameAllocator trait 2017-11-19 10:39:13 +01:00
Philipp Oppermann
98d97703f4 Calculate start and end of kernel and multiboot struct 2017-11-19 10:39:13 +01:00
Philipp Oppermann
402ec77bc0 Reduce number of ELF section by merging .text.* (etc.) sections together 2017-11-19 10:39:13 +01:00
Philipp Oppermann
9a86f60835 Print kernel ELF sections 2017-11-19 10:39:13 +01:00
Philipp Oppermann
cbc9e112bd Print a proper message on panics 2017-11-19 10:39:13 +01:00
Philipp Oppermann
f231f2c7f2 Print available memory areas 2017-11-19 10:39:13 +01:00
Philipp Oppermann
5f65e1d31f Add dependency on multiboot2 crate 2017-11-19 10:39:13 +01:00
Philipp Oppermann
28ce5310c8 Pass address of multiboot info structure to rust_main 2017-11-19 10:39:13 +01:00
Philipp Oppermann
08a4e795a4 Increase stack size to 16k 2017-11-19 10:39:13 +01:00
Philipp Oppermann
8040f8d565 Update Readme for “Allocating Frames” post 2017-11-19 10:39:13 +01:00
10 changed files with 486 additions and 8 deletions

View File

@@ -10,3 +10,6 @@ crate-type = ["staticlib"]
rlibc = "1.0"
volatile = "0.1.0"
spin = "0.4.5"
multiboot2 = "0.1.0"
bitflags = "0.7.0"
x86_64 = "0.1.2"

View File

@@ -1,7 +1,7 @@
# Blog OS (Printing To Screen)
[![Build Status](https://travis-ci.org/phil-opp/blog_os.svg?branch=post_4)](https://travis-ci.org/phil-opp/blog_os/branches)
# Blog OS (Page Tables)
[![Build Status](https://travis-ci.org/phil-opp/blog_os.svg?branch=post_6)](https://travis-ci.org/phil-opp/blog_os/branches)
This repository contains the source code for the [Printing To Screen](http://os.phil-opp.com/printing-to-screen.html) post of the [Writing an OS in Rust](http://os.phil-opp.com) series.
This repository contains the source code for the [Page Tables](http://os.phil-opp.com/modifying-page-tables.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.**

View File

@@ -5,6 +5,7 @@ section .text
bits 32
start:
mov esp, stack_top
mov edi, ebx ; move Multiboot info pointer to edi
call check_multiboot
call check_cpuid
@@ -84,6 +85,11 @@ check_long_mode:
jmp error
set_up_page_tables:
; map P4 table recursively
mov eax, p4_table
or eax, 0b11 ; present + writable
mov [p4_table + 511 * 8], eax
; map first P4 entry to P3 table
mov eax, p3_table
or eax, 0b11 ; present + writable
@@ -151,7 +157,7 @@ p3_table:
p2_table:
resb 4096
stack_bottom:
resb 64
resb 4096 * 4
stack_top:
section .rodata

View File

@@ -11,6 +11,14 @@ SECTIONS {
.text :
{
*(.text)
*(.text .text.*)
}
.rodata : {
*(.rodata .rodata.*)
}
.data.rel.ro : {
*(.data.rel.ro.local*) *(.data.rel.ro .data.rel.ro.*)
}
}

View File

@@ -7,19 +7,61 @@
extern crate rlibc;
extern crate volatile;
extern crate spin;
extern crate multiboot2;
#[macro_use]
extern crate bitflags;
extern crate x86_64;
#[macro_use]
mod vga_buffer;
mod memory;
#[no_mangle]
pub extern fn rust_main() {
// ATTENTION: we have a very small stack and no guard page
pub extern fn rust_main(multiboot_information_address: usize) {
use memory::FrameAllocator;
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");
println!("memory areas:");
for area in memory_map_tag.memory_areas() {
println!(" start: 0x{:x}, length: 0x{:x}",
area.base_addr, area.length);
}
let elf_sections_tag = boot_info.elf_sections_tag()
.expect("Elf-sections tag required");
println!("kernel sections:");
for section in elf_sections_tag.sections() {
println!(" addr: 0x{:x}, size: 0x{:x}, flags: 0x{:x}",
section.addr, section.size, section.flags);
}
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);
let mut frame_allocator = memory::AreaFrameAllocator::new(
kernel_start as usize, kernel_end as usize, multiboot_start,
multiboot_end, memory_map_tag.memory_areas());
loop{}
}
#[lang = "eh_personality"] extern fn eh_personality() {}
#[lang = "panic_fmt"] #[no_mangle] pub extern fn panic_fmt() -> ! {loop{}}
#[lang = "panic_fmt"]
#[no_mangle]
pub extern fn panic_fmt(fmt: core::fmt::Arguments, file: &'static str, line: u32) -> ! {
println!("\n\nPANIC in {} at line {}:", file, line);
println!(" {}", fmt);
loop{}
}

View File

@@ -0,0 +1,88 @@
use memory::{Frame, FrameAllocator};
use multiboot2::{MemoryAreaIter, MemoryArea};
pub struct AreaFrameAllocator {
next_free_frame: Frame,
current_area: Option<&'static MemoryArea>,
areas: MemoryAreaIter,
kernel_start: Frame,
kernel_end: Frame,
multiboot_start: Frame,
multiboot_end: Frame,
}
impl FrameAllocator for AreaFrameAllocator {
fn allocate_frame(&mut self) -> Option<Frame> {
if let Some(area) = self.current_area {
// "Clone" the frame to return it if it's free. Frame doesn't
// implement Clone, but we can construct an identical frame.
let frame = Frame{ number: self.next_free_frame.number };
// the last frame of the current area
let current_area_last_frame = {
let address = area.base_addr + area.length - 1;
Frame::containing_address(address as usize)
};
if frame > current_area_last_frame {
// all frames of current area are used, switch to next area
self.choose_next_area();
} else if frame >= self.kernel_start && frame <= self.kernel_end {
// `frame` is used by the kernel
self.next_free_frame = Frame {
number: self.kernel_end.number + 1
};
} else if frame >= self.multiboot_start && frame <= self.multiboot_end {
// `frame` is used by the multiboot information structure
self.next_free_frame = Frame {
number: self.multiboot_end.number + 1
};
} else {
// frame is unused, increment `next_free_frame` and return it
self.next_free_frame.number += 1;
return Some(frame);
}
// `frame` was not valid, try it again with the updated `next_free_frame`
self.allocate_frame()
} else {
None // no free frames left
}
}
fn deallocate_frame(&mut self, _frame: Frame) {
unimplemented!()
}
}
impl AreaFrameAllocator {
pub fn new(kernel_start: usize, kernel_end: usize,
multiboot_start: usize, multiboot_end: usize,
memory_areas: MemoryAreaIter) -> AreaFrameAllocator
{
let mut allocator = AreaFrameAllocator {
next_free_frame: Frame::containing_address(0),
current_area: None,
areas: memory_areas,
kernel_start: Frame::containing_address(kernel_start),
kernel_end: Frame::containing_address(kernel_end),
multiboot_start: Frame::containing_address(multiboot_start),
multiboot_end: Frame::containing_address(multiboot_end),
};
allocator.choose_next_area();
allocator
}
fn choose_next_area(&mut self) {
self.current_area = self.areas.clone().filter(|area| {
let address = area.base_addr + area.length - 1;
Frame::containing_address(address as usize) >= self.next_free_frame
}).min_by_key(|area| area.base_addr);
if let Some(area) = self.current_area {
let start_frame = Frame::containing_address(area.base_addr as usize);
if self.next_free_frame < start_frame {
self.next_free_frame = start_frame;
}
}
}
}

27
src/memory/mod.rs Normal file
View File

@@ -0,0 +1,27 @@
pub use self::area_frame_allocator::AreaFrameAllocator;
use self::paging::PhysicalAddress;
mod area_frame_allocator;
mod paging;
pub const PAGE_SIZE: usize = 4096;
#[derive(Debug, PartialEq, Eq, PartialOrd, Ord)]
pub struct Frame {
number: usize,
}
impl Frame {
fn containing_address(address: usize) -> Frame {
Frame{ number: address / PAGE_SIZE }
}
fn start_address(&self) -> PhysicalAddress {
self.number * PAGE_SIZE
}
}
pub trait FrameAllocator {
fn allocate_frame(&mut self) -> Option<Frame>;
fn deallocate_frame(&mut self, frame: Frame);
}

View File

@@ -0,0 +1,47 @@
use memory::Frame;
pub struct Entry(u64);
impl Entry {
pub fn is_unused(&self) -> bool {
self.0 == 0
}
pub fn set_unused(&mut self) {
self.0 = 0;
}
pub fn flags(&self) -> EntryFlags {
EntryFlags::from_bits_truncate(self.0)
}
pub fn pointed_frame(&self) -> Option<Frame> {
if self.flags().contains(PRESENT) {
Some(Frame::containing_address(
self.0 as usize & 0x000fffff_fffff000
))
} else {
None
}
}
pub fn set(&mut self, frame: Frame, flags: EntryFlags) {
assert!(frame.start_address() & !0x000fffff_fffff000 == 0);
self.0 = (frame.start_address() as u64) | flags.bits();
}
}
bitflags! {
pub flags EntryFlags: u64 {
const PRESENT = 1 << 0,
const WRITABLE = 1 << 1,
const USER_ACCESSIBLE = 1 << 2,
const WRITE_THROUGH = 1 << 3,
const NO_CACHE = 1 << 4,
const ACCESSED = 1 << 5,
const DIRTY = 1 << 6,
const HUGE_PAGE = 1 << 7,
const GLOBAL = 1 << 8,
const NO_EXECUTE = 1 << 63,
}
}

157
src/memory/paging/mod.rs Normal file
View File

@@ -0,0 +1,157 @@
pub use self::entry::*;
use core::ptr::Unique;
use memory::{PAGE_SIZE, Frame, FrameAllocator};
use self::table::{Table, Level4};
mod entry;
mod table;
const ENTRY_COUNT: usize = 512;
pub type PhysicalAddress = usize;
pub type VirtualAddress = usize;
pub struct Page {
number: usize,
}
impl Page {
pub fn containing_address(address: VirtualAddress) -> Page {
assert!(address < 0x0000_8000_0000_0000 ||
address >= 0xffff_8000_0000_0000,
"invalid address: 0x{:x}", address);
Page { number: address / PAGE_SIZE }
}
fn start_address(&self) -> usize {
self.number * PAGE_SIZE
}
fn p4_index(&self) -> usize {
(self.number >> 27) & 0o777
}
fn p3_index(&self) -> usize {
(self.number >> 18) & 0o777
}
fn p2_index(&self) -> usize {
(self.number >> 9) & 0o777
}
fn p1_index(&self) -> usize {
(self.number >> 0) & 0o777
}
}
pub struct ActivePageTable {
p4: Unique<Table<Level4>>,
}
impl ActivePageTable {
pub unsafe fn new() -> ActivePageTable {
ActivePageTable {
p4: Unique::new_unchecked(table::P4),
}
}
fn p4(&self) -> &Table<Level4> {
unsafe { self.p4.as_ref() }
}
fn p4_mut(&mut self) -> &mut Table<Level4> {
unsafe { self.p4.as_mut() }
}
pub fn translate(&self, virtual_address: VirtualAddress) -> Option<PhysicalAddress> {
let offset = virtual_address % PAGE_SIZE;
self.translate_page(Page::containing_address(virtual_address))
.map(|frame| frame.number * PAGE_SIZE + offset)
}
fn translate_page(&self, page: Page) -> Option<Frame> {
use self::entry::HUGE_PAGE;
let p3 = self.p4().next_table(page.p4_index());
let huge_page = || {
p3.and_then(|p3| {
let p3_entry = &p3[page.p3_index()];
// 1GiB page?
if let Some(start_frame) = p3_entry.pointed_frame() {
if p3_entry.flags().contains(HUGE_PAGE) {
// address must be 1GiB aligned
assert!(start_frame.number % (ENTRY_COUNT * ENTRY_COUNT) == 0);
return Some(Frame {
number: start_frame.number + page.p2_index() *
ENTRY_COUNT + page.p1_index(),
});
}
}
if let Some(p2) = p3.next_table(page.p3_index()) {
let p2_entry = &p2[page.p2_index()];
// 2MiB page?
if let Some(start_frame) = p2_entry.pointed_frame() {
if p2_entry.flags().contains(HUGE_PAGE) {
// address must be 2MiB aligned
assert!(start_frame.number % ENTRY_COUNT == 0);
return Some(Frame {
number: start_frame.number + page.p1_index()
});
}
}
}
None
})
};
p3.and_then(|p3| p3.next_table(page.p3_index()))
.and_then(|p2| p2.next_table(page.p2_index()))
.and_then(|p1| p1[page.p1_index()].pointed_frame())
.or_else(huge_page)
}
pub fn map_to<A>(&mut self, page: Page, frame: Frame, flags: EntryFlags,
allocator: &mut A)
where A: FrameAllocator
{
let p4 = self.p4_mut();
let mut p3 = p4.next_table_create(page.p4_index(), allocator);
let mut p2 = p3.next_table_create(page.p3_index(), allocator);
let mut p1 = p2.next_table_create(page.p2_index(), allocator);
assert!(p1[page.p1_index()].is_unused());
p1[page.p1_index()].set(frame, flags | PRESENT);
}
pub fn map<A>(&mut self, page: Page, flags: EntryFlags, allocator: &mut A)
where A: FrameAllocator
{
let frame = allocator.allocate_frame().expect("out of memory");
self.map_to(page, frame, flags, allocator)
}
pub fn identity_map<A>(&mut self, frame: Frame, flags: EntryFlags, allocator: &mut A)
where A: FrameAllocator
{
let page = Page::containing_address(frame.start_address());
self.map_to(page, frame, flags, allocator)
}
fn unmap<A>(&mut self, page: Page, allocator: &mut A)
where A: FrameAllocator
{
use x86_64::instructions::tlb;
use x86_64::VirtualAddress;
assert!(self.translate(page.start_address()).is_some());
let p1 = self.p4_mut()
.next_table_mut(page.p4_index())
.and_then(|p3| p3.next_table_mut(page.p3_index()))
.and_then(|p2| p2.next_table_mut(page.p2_index()))
.expect("mapping code does not support huge pages");
let frame = p1[page.p1_index()].pointed_frame().unwrap();
p1[page.p1_index()].set_unused();
tlb::flush(VirtualAddress(page.start_address()));
// TODO free p(1,2,3) table if empty
//allocator.deallocate_frame(frame);
}
}

100
src/memory/paging/table.rs Normal file
View File

@@ -0,0 +1,100 @@
use core::marker::PhantomData;
use core::ops::{Index, IndexMut};
use memory::FrameAllocator;
use memory::paging::entry::*;
use memory::paging::ENTRY_COUNT;
pub const P4: *mut Table<Level4> = 0xffffffff_fffff000 as *mut _;
pub struct Table<L: TableLevel> {
entries: [Entry; ENTRY_COUNT],
level: PhantomData<L>,
}
impl<L> Table<L> where L: TableLevel {
pub fn zero(&mut self) {
for entry in self.entries.iter_mut() {
entry.set_unused();
}
}
}
impl<L> Table<L> where L: HierarchicalLevel {
fn next_table_address(&self, index: usize) -> Option<usize> {
let entry_flags = self[index].flags();
if entry_flags.contains(PRESENT) && !entry_flags.contains(HUGE_PAGE) {
let table_address = self as *const _ as usize;
Some((table_address << 9) | (index << 12))
} else {
None
}
}
pub fn next_table(&self, index: usize) -> Option<&Table<L::NextLevel>> {
self.next_table_address(index)
.map(|address| unsafe { &*(address as *const _) })
}
pub fn next_table_mut(&mut self, index: usize) -> Option<&mut Table<L::NextLevel>> {
self.next_table_address(index)
.map(|address| unsafe { &mut *(address as *mut _) })
}
pub fn next_table_create<A>(&mut self,
index: usize,
allocator: &mut A)
-> &mut Table<L::NextLevel>
where A: FrameAllocator
{
if self.next_table(index).is_none() {
assert!(!self.entries[index].flags().contains(HUGE_PAGE),
"mapping code does not support huge pages");
let frame = allocator.allocate_frame().expect("no frames available");
self.entries[index].set(frame, PRESENT | WRITABLE);
self.next_table_mut(index).unwrap().zero();
}
self.next_table_mut(index).unwrap()
}
}
impl<L> Index<usize> for Table<L> where L: TableLevel {
type Output = Entry;
fn index(&self, index: usize) -> &Entry {
&self.entries[index]
}
}
impl<L> IndexMut<usize> for Table<L> where L: TableLevel {
fn index_mut(&mut self, index: usize) -> &mut Entry {
&mut self.entries[index]
}
}
pub trait TableLevel {}
pub enum Level4 {}
pub enum Level3 {}
pub enum Level2 {}
pub enum Level1 {}
impl TableLevel for Level4 {}
impl TableLevel for Level3 {}
impl TableLevel for Level2 {}
impl TableLevel for Level1 {}
pub trait HierarchicalLevel: TableLevel {
type NextLevel: TableLevel;
}
impl HierarchicalLevel for Level4 {
type NextLevel = Level3;
}
impl HierarchicalLevel for Level3 {
type NextLevel = Level2;
}
impl HierarchicalLevel for Level2 {
type NextLevel = Level1;
}