From 38849516a15d3a8c52fba9764d60ce5f03249802 Mon Sep 17 00:00:00 2001 From: Philipp Oppermann Date: Sun, 15 Nov 2015 13:04:00 +0100 Subject: [PATCH 01/76] Add preliminary outline for next post --- posts/DRAFT-paging.md | 14 ++++++++++++++ 1 file changed, 14 insertions(+) create mode 100644 posts/DRAFT-paging.md diff --git a/posts/DRAFT-paging.md b/posts/DRAFT-paging.md new file mode 100644 index 00000000..fa08dc43 --- /dev/null +++ b/posts/DRAFT-paging.md @@ -0,0 +1,14 @@ +--- +layout: post +title: 'A Paging Module' +--- + +## Recursive Mapping + +## A Safe Module + +## Switching Page Tables + +## Mapping Pages + +## Unmapping Pages From 0e574426c51fd8dc8ba47cd873ca30c97bdb21f7 Mon Sep 17 00:00:00 2001 From: Philipp Oppermann Date: Mon, 16 Nov 2015 00:55:54 +0100 Subject: [PATCH 02/76] Add recursive mapping and start describing it --- posts/DRAFT-paging.md | 9 +++++++++ src/arch/x86_64/boot.asm | 5 +++++ 2 files changed, 14 insertions(+) diff --git a/posts/DRAFT-paging.md b/posts/DRAFT-paging.md index fa08dc43..102dafed 100644 --- a/posts/DRAFT-paging.md +++ b/posts/DRAFT-paging.md @@ -4,6 +4,15 @@ title: 'A Paging Module' --- ## Recursive Mapping +The trick is to map the `P4` table _recursively_: The last entry doesn't point to a `P3` table, instead it points to the `P4` table itself. Through this entry, we can access and modify page tables of all levels. It may seem a bit strange at first, but is a very clean and simple solution once you wrapped your head around it. + +To access for example the `P4` table itself, we use the address that chooses the 511th `P4` entry, the 511th `P3` entry, the 511th `P2` entry and the 511th `P1` entry. Thus we choose the same `P4` frame over and over again and finally end up on it, too. Through the offset (12 bits) we choose the desired entry. + +To access a `P3` table, we do the same but choose the real `P4` index instead of the fourth loop. So if we like to access the 42th `P3` table, we use the address that chooses the 511th entry in the `P4`, `P3`, and `P2` table, but the 42th `P1` entry. + +When accessing a `P2` table, we only loop two times and then choose entries that correspond to the `P4` and `P3` table of the desired `P2` table. And accessing a `P1` table just loops once and then uses the corresponding `P4`, `P3`, and `P2` entries. + +The math checks out, too. If all page tables are used, there is 1 `P4` table, 511 `P3` tables (the last entry is used for the recursive mapping), `511*512` `P2` tables, and `511*512*512` `P1` tables. So there are `134217728` page tables altogether. Each page table occupies 4KiB, so we need `134217728 * 4KiB = 512GiB` to store them. That's exactly the amount of memory that can be accessed through one `P4` entry since `4KiB per page * 512 P1 entries * 512 P2 entries * 512 P3 entries = 512GiB`. ## A Safe Module diff --git a/src/arch/x86_64/boot.asm b/src/arch/x86_64/boot.asm index 2f5295d7..4abb2899 100644 --- a/src/arch/x86_64/boot.asm +++ b/src/arch/x86_64/boot.asm @@ -42,6 +42,11 @@ start: jmp gdt64.code:long_mode_start setup_page_tables: + ; recursive map P4 + 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 From f917bd67a1794f64083dd9bd9c4be61b9279b0ef Mon Sep 17 00:00:00 2001 From: Philipp Oppermann Date: Thu, 19 Nov 2015 15:44:38 +0100 Subject: [PATCH 03/76] Begin paging module --- posts/DRAFT-paging.md | 15 +++++++++++++++ src/lib.rs | 2 +- src/memory/mod.rs | 1 + src/memory/paging/mod.rs | 8 ++++++++ 4 files changed, 25 insertions(+), 1 deletion(-) create mode 100644 src/memory/paging/mod.rs diff --git a/posts/DRAFT-paging.md b/posts/DRAFT-paging.md index 102dafed..9d9e627c 100644 --- a/posts/DRAFT-paging.md +++ b/posts/DRAFT-paging.md @@ -15,6 +15,21 @@ When accessing a `P2` table, we only loop two times and then choose entries that The math checks out, too. If all page tables are used, there is 1 `P4` table, 511 `P3` tables (the last entry is used for the recursive mapping), `511*512` `P2` tables, and `511*512*512` `P1` tables. So there are `134217728` page tables altogether. Each page table occupies 4KiB, so we need `134217728 * 4KiB = 512GiB` to store them. That's exactly the amount of memory that can be accessed through one `P4` entry since `4KiB per page * 512 P1 entries * 512 P2 entries * 512 P3 entries = 512GiB`. ## A Safe Module +We need to make sure that the page tables can't be modified concurrently. So we must ensure exclusive access for all functions that modify the page table. For a normal struct, Rust would handle it at compile time through the `&` and `&mut` rules. But since we have some magic memory address instead, we must do some manual work. + +To ensure exclusivity, we introduce a `Lock` struct. All operations that modify the current page table borrow it exclusively (`&mut`) and all operations that just read the table borrow it through `&`. That way, we benefit from Rust's aliasing rules. + +The `Lock` struct looks like this (in a new `memory/paging/mod.rs` module): + +```rust +pub struct Lock { + _private: (), +} + +impl !Send for Lock {} +impl !Sync for Lock {} +``` +The `_private` field is needed to forbid construction from outside. The `!Send` and `!Sync` ## Switching Page Tables diff --git a/src/lib.rs b/src/lib.rs index e0ae38fc..b30e79a4 100644 --- a/src/lib.rs +++ b/src/lib.rs @@ -13,7 +13,7 @@ // limitations under the License. #![feature(no_std, lang_items)] -#![feature(const_fn, unique, core_str_ext, iter_cmp)] +#![feature(const_fn, unique, core_str_ext, iter_cmp, optin_builtin_traits)] #![no_std] extern crate rlibc; diff --git a/src/memory/mod.rs b/src/memory/mod.rs index 7bfc85d8..062cf380 100644 --- a/src/memory/mod.rs +++ b/src/memory/mod.rs @@ -1,5 +1,6 @@ pub use self::area_frame_allocator::AreaFrameAllocator; +mod paging; mod area_frame_allocator; pub const PAGE_SIZE: usize = 4096; diff --git a/src/memory/paging/mod.rs b/src/memory/paging/mod.rs new file mode 100644 index 00000000..3f7d97ae --- /dev/null +++ b/src/memory/paging/mod.rs @@ -0,0 +1,8 @@ +/// The paging lock must be unique. It is required for all page table operations and thus +/// guarantees exclusive page table access. +pub struct Lock { + _private: (), +} + +impl !Send for Lock {} +impl !Sync for Lock {} From 6b72dd82231c8e35c3c90bbc941d02fc771a9121 Mon Sep 17 00:00:00 2001 From: Philipp Oppermann Date: Thu, 26 Nov 2015 10:14:04 +0100 Subject: [PATCH 04/76] Remove `Lock` structure again as we changed design --- posts/DRAFT-paging.md | 14 -------------- src/memory/paging/mod.rs | 8 -------- 2 files changed, 22 deletions(-) diff --git a/posts/DRAFT-paging.md b/posts/DRAFT-paging.md index 9d9e627c..46f57507 100644 --- a/posts/DRAFT-paging.md +++ b/posts/DRAFT-paging.md @@ -14,22 +14,8 @@ When accessing a `P2` table, we only loop two times and then choose entries that The math checks out, too. If all page tables are used, there is 1 `P4` table, 511 `P3` tables (the last entry is used for the recursive mapping), `511*512` `P2` tables, and `511*512*512` `P1` tables. So there are `134217728` page tables altogether. Each page table occupies 4KiB, so we need `134217728 * 4KiB = 512GiB` to store them. That's exactly the amount of memory that can be accessed through one `P4` entry since `4KiB per page * 512 P1 entries * 512 P2 entries * 512 P3 entries = 512GiB`. -## A Safe Module -We need to make sure that the page tables can't be modified concurrently. So we must ensure exclusive access for all functions that modify the page table. For a normal struct, Rust would handle it at compile time through the `&` and `&mut` rules. But since we have some magic memory address instead, we must do some manual work. -To ensure exclusivity, we introduce a `Lock` struct. All operations that modify the current page table borrow it exclusively (`&mut`) and all operations that just read the table borrow it through `&`. That way, we benefit from Rust's aliasing rules. -The `Lock` struct looks like this (in a new `memory/paging/mod.rs` module): - -```rust -pub struct Lock { - _private: (), -} - -impl !Send for Lock {} -impl !Sync for Lock {} -``` -The `_private` field is needed to forbid construction from outside. The `!Send` and `!Sync` ## Switching Page Tables diff --git a/src/memory/paging/mod.rs b/src/memory/paging/mod.rs index 3f7d97ae..e69de29b 100644 --- a/src/memory/paging/mod.rs +++ b/src/memory/paging/mod.rs @@ -1,8 +0,0 @@ -/// The paging lock must be unique. It is required for all page table operations and thus -/// guarantees exclusive page table access. -pub struct Lock { - _private: (), -} - -impl !Send for Lock {} -impl !Sync for Lock {} From 6462a3263c9d34df8844bb29385d25d79bacb3de Mon Sep 17 00:00:00 2001 From: Philipp Oppermann Date: Thu, 26 Nov 2015 10:14:41 +0100 Subject: [PATCH 05/76] Add basic module and corresponding documentation --- Cargo.toml | 7 ++ posts/DRAFT-paging.md | 150 +++++++++++++++++++++++++++++ src/lib.rs | 3 + src/memory/paging/mod.rs | 202 +++++++++++++++++++++++++++++++++++++++ 4 files changed, 362 insertions(+) diff --git a/Cargo.toml b/Cargo.toml index 7eb63f79..1470612e 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -12,3 +12,10 @@ spin = "0.3.4" [dependencies.multiboot2] git = "https://github.com/phil-opp/multiboot2-elf64" + +[dependencies.x86] +git = "https://github.com/gz/rust-x86" + +[dependencies.bitflags] +git = "https://github.com/phil-opp/bitflags.git" +branch = "no_std" diff --git a/posts/DRAFT-paging.md b/posts/DRAFT-paging.md index 46f57507..5c345e84 100644 --- a/posts/DRAFT-paging.md +++ b/posts/DRAFT-paging.md @@ -3,6 +3,113 @@ layout: post title: 'A Paging Module' --- +## Paging + +## A Paging Module +Let's begin a `memory/paging/mod.rs` module: + +```rust +pub const PAGE_SIZE: usize = 4096; +const ENTRY_SIZE: usize = 8; +const ENTRY_COUNT: usize = 512; + +pub type PhysicalAddress = usize; +pub type VirtualAddress = usize; + +pub struct Page { + number: usize, +} + +struct Table(Page); + +#[derive(Debug, Clone, Copy)] +struct TableEntry(u64); +``` +We define constants for the page size, the size of an entry in a page table, and the number of entries per table. To make future function signatures more expressive, we can use the type aliases `PhysicalAddress` and `VirtualAddress`. The `Page` struct is similar to the `Frame` struct in the [previous post], but represents a virtual page instead of a physical frame. + +[previous post]: {{ page.previous.url }} + +The `Table` struct represents a P4, P3, P2, or P1 table. It's a newtype wrapper around the `Page` that contains the table. And the `TableEntry` type represents an 8 byte large page table entry. + +To get the i-th entry of a `Table`, we add a `entry()` method: + +```rust +fn entry(&self, index: usize) -> TableEntry { + assert!(index < ENTRY_COUNT); + let entry_address = self.0.start_address() + index * ENTRY_SIZE; + unsafe { *(entry_address as *const _) } +} +``` +The `start_address` function is covered below. We're doing manual pointer arithmetic in this function and need an `unsafe` block to convince Rust that there's a valid `TableEntry` at the given address. For this to be safe, we need to make sure that we only construct valid `Table` structs in the future. + +TODO formulierung for this to be safe + +### Sign Extension +The `Page::start_address` method doesn't exist yet. But it should be a simple `page.number * PAGE_SIZE`, right? Well, if the x86_64 architecture had true 64bit addresses, yes. But in reality the addresses are just 48bit long and the other bits are just _sign extension_, i.e. a copy of the most significant bit. That means that the address calculated by `page.number * PAGE_SIZE` is wrong if the 47th bit is used. Some examples: + +``` +invalid address: 0x0000_800000000000 + sign extension | 48bit address +valid sign extension: 0xffff_800000000000 +``` +TODO graphic + +So the address space is split into two halves: the _higher half_ containing addresses with sign extension and the _lower half_ containing addresses without. And our `Page::start_address` method needs to respect this: + +```rust +pub fn start_address(&self) -> VirtualAddress { + if self.number >= 0x800000000 { + // sign extension necessary + (self.number << 12) | 0xffff_000000000000 + } else { + self.number << 12 + } +} +``` +The `0x800000000` is the start address of the higher half without the last four 0s (because it's a page _number_). + +### Table entries +Now we can get a `TableEntry` through the `entry` function. Now we need to extract the relevant information. + +Remember, a page table entry looks like this: + +Bit(s) | Name | Meaning +--------------------- | ------ | ---------------------------------- +0 | present | the page is currently in memory +1 | writable | it's allowed to write to this page +2 | user accessible | if not set, only kernel mode code can access this page +3 | write through caching | writes go directly to memory +4 | disable cache | no cache is used for this page +5 | accessed | the CPU sets this bit when this page is used +6 | dirty | the CPU sets this bit when a write to this page occurs +7 | huge page/null | must be 0 in P1 and P4, creates a 1GiB page in P3, creates a 2MiB page in P2 +8 | global | page isn't flushed from caches on address space switch (PGE bit of CR4 register must be set) +9-11 | available | can be used freely by the OS +12-51 | physical address | the page aligned 52bit physical address of the frame or the next page table +52-62 | available | can be used freely by the OS +63 | no execute | forbid executing code on this page (the NXE bit in the EFER register must be set) + +To extract the physical address we add a `TableEntry::pointed_frame` method: + +```rust +fn pointed_frame(&self) -> Frame { + Frame { number: ((self.0 & 0x000fffff_fffff000) >> 12) as usize } +} +``` +First we mask bits 12-51 and then convert the physical address to the corresponding frame number (through `>> 12`). We don't need to respect any sign extension here since it only exists for virtual addresses. + +To model the various flags, we will use the [bitflags] crate. Unfortunately the official version depends on the standard library as `no_std` is still unstable. But since it does not actually require any `std` functions, it's pretty easy to create a `no_std` version. You can find it here [here][bitflags fork]. To add it as a dependency add the following to your `Cargo.toml`: + +[bitflags]: /TODO +[bitflags fork]: /TODO + +```toml +[dependencies.bitflags] +git = "https://github.com/phil-opp/bitflags.git" +branch = "no_std" +``` + + ## Recursive Mapping The trick is to map the `P4` table _recursively_: The last entry doesn't point to a `P3` table, instead it points to the `P4` table itself. Through this entry, we can access and modify page tables of all levels. It may seem a bit strange at first, but is a very clean and simple solution once you wrapped your head around it. @@ -14,8 +121,51 @@ When accessing a `P2` table, we only loop two times and then choose entries that The math checks out, too. If all page tables are used, there is 1 `P4` table, 511 `P3` tables (the last entry is used for the recursive mapping), `511*512` `P2` tables, and `511*512*512` `P1` tables. So there are `134217728` page tables altogether. Each page table occupies 4KiB, so we need `134217728 * 4KiB = 512GiB` to store them. That's exactly the amount of memory that can be accessed through one `P4` entry since `4KiB per page * 512 P1 entries * 512 P2 entries * 512 P3 entries = 512GiB`. +TODO: recursive map in assembly + +## Translating addresses +Now we can use the recursive mapping to translate virtual address manually. We will create a function that takes a virtual address and returns the corresponding physical address. + +TODO +To get the page tables and corresponding indexes for a page, we add some methods for `Page`: + +```rust +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} + +const fn p4_table(&self) -> Table { + Table(Page { number: 0o_777_777_777_777 } ) +} + +fn p3_table(&self) -> Table { + Table(Page { + number: 0o_777_777_777_000 | self.p4_index(), + }) +} + +fn p2_table(&self) -> Table { + Table(Page { + number: 0o_777_777_000_000 | (self.p4_index() << 9) | + self.p3_index(), + }) +} + +fn p1_table(&self) -> Table { + Table(Page { + number: 0o_777_000_000_000 | (self.p4_index() << 18) | + (self.p3_index() << 9) | self.p2_index(), + }) +} +``` +We use the octal numbers since they make it easy to express the 9 bit table indexes. + +The P4 table is the same for all addresses, so we can make the function `const`. The associated page has index 511 in all four pages, thus the four `777` blocks. The P3 table, however, is different for different P4 indexes. So the last block varies from `000` to `776`, dependent on the page's P4 index. The P2 table additionally depends on the P3 index and to get the P1 table we use the recursive mapping only once (thus only one `777` block). + +TODO ## Switching Page Tables diff --git a/src/lib.rs b/src/lib.rs index b30e79a4..8949a2a7 100644 --- a/src/lib.rs +++ b/src/lib.rs @@ -19,6 +19,9 @@ extern crate rlibc; extern crate spin; extern crate multiboot2; +extern crate x86; +#[macro_use] +extern crate bitflags; #[macro_use] mod vga_buffer; diff --git a/src/memory/paging/mod.rs b/src/memory/paging/mod.rs index e69de29b..9d710605 100644 --- a/src/memory/paging/mod.rs +++ b/src/memory/paging/mod.rs @@ -0,0 +1,202 @@ +use memory::Frame; + +pub const PAGE_SIZE: usize = 4096; +const ENTRY_SIZE: usize = 8; +const ENTRY_COUNT: usize = 512; + +pub type PhysicalAddress = usize; +pub type VirtualAddress = usize; + +// pub fn translate(virtual_address: usize) -> Option { +// let page = Page::containing_address(virtual_address); +// let offset = virtual_address % PAGE_SIZE; +// +// let p4_entry = page.p4_table().entry(page.p4_index()); +// assert!(!p4_entry.flags().contains(HUGE_PAGE)); +// if !p4_entry.flags().contains(PRESENT) { +// return None; +// } +// +// let p3_entry = page.p3_table().entry(page.p3_index()); +// if !p3_entry.flags().contains(PRESENT) { +// return None; +// } +// if p3_entry.flags().contains(HUGE_PAGE) { +// 1GiB page (address must be 1GiB aligned) +// let start_frame_number = p3_entry.pointed_frame().number; +// assert!(start_frame_number % (ENTRY_COUNT * ENTRY_COUNT) == 0); +// let frame_number = start_frame_number + page.p2_index() * ENTRY_COUNT + page.p1_index(); +// return Some(frame_number * PAGE_SIZE + offset); +// } +// +// let p2_entry = page.p2_table().entry(page.p2_index()); +// if !p2_entry.flags().contains(PRESENT) { +// return None; +// } +// if p2_entry.flags().contains(HUGE_PAGE) { +// 2MiB page (address must be 2MiB aligned) +// let start_frame_number = p2_entry.pointed_frame().number; +// assert!(start_frame_number % ENTRY_COUNT == 0); +// let frame_number = start_frame_number + page.p1_index(); +// return Some(frame_number * PAGE_SIZE + offset); +// } +// +// let p1_entry = page.p1_table().entry(page.p1_index()); +// assert!(!p1_entry.flags().contains(HUGE_PAGE)); +// if !p1_entry.flags().contains(PRESENT) { +// return None; +// } +// Some(p1_entry.pointed_frame().number * PAGE_SIZE + offset) +// } + +pub fn translate(virtual_address: usize) -> Option { + let page = Page::containing_address(virtual_address); + let offset = virtual_address % PAGE_SIZE; + + let frame_number = { + let p4_entry = page.p4_table().entry(page.p4_index()); + assert!(!p4_entry.flags().contains(HUGE_PAGE)); + if !p4_entry.flags().contains(PRESENT) { + return None; + } + + let p3_entry = unsafe { page.p3_table() }.entry(page.p3_index()); + if !p3_entry.flags().contains(PRESENT) { + return None; + } + if p3_entry.flags().contains(HUGE_PAGE) { + // 1GiB page (address must be 1GiB aligned) + let start_frame_number = p3_entry.pointed_frame().number; + assert!(start_frame_number % (ENTRY_COUNT * ENTRY_COUNT) == 0); + start_frame_number + page.p2_index() * ENTRY_COUNT + page.p1_index() + } else { + // 2MiB or 4KiB page + let p2_entry = unsafe { page.p2_table() }.entry(page.p2_index()); + if !p2_entry.flags().contains(PRESENT) { + return None; + } + if p2_entry.flags().contains(HUGE_PAGE) { + // 2MiB page (address must be 2MiB aligned) + let start_frame_number = p2_entry.pointed_frame().number; + assert!(start_frame_number % ENTRY_COUNT == 0); + start_frame_number + page.p1_index() + } else { + // standard 4KiB page + let p1_entry = unsafe { page.p1_table() }.entry(page.p1_index()); + assert!(!p1_entry.flags().contains(HUGE_PAGE)); + if !p1_entry.flags().contains(PRESENT) { + return None; + } + p1_entry.pointed_frame().number + } + } + }; + Some(frame_number * PAGE_SIZE + offset) +} + +pub struct Page { + number: usize, +} + +impl Page { + fn containing_address(address: VirtualAddress) -> Page { + match address { + addr if addr < 0o_400_000_000_000_0000 => Page { number: addr / PAGE_SIZE }, + addr if addr >= 0o177777_400_000_000_000_0000 => { + Page { number: (address / PAGE_SIZE) & 0o_777_777_777_777 } + } + _ => panic!("invalid address: 0x{:x}", address), + } + } + + pub fn start_address(&self) -> VirtualAddress { + if self.number >= 0x800000000 { + // sign extension necessary + (self.number << 12) | 0xffff_000000000000 + } else { + self.number << 12 + } + } + + 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 + } + + const fn p4_table(&self) -> Table { + Table(Page { number: 0o_777_777_777_777 }) + } + + fn p3_table(&self) -> Table { + Table(Page { number: 0o_777_777_777_000 | self.p4_index() }) + } + + fn p2_table(&self) -> Table { + Table(Page { number: 0o_777_777_000_000 | (self.p4_index() << 9) | self.p3_index() }) + } + + fn p1_table(&self) -> Table { + Table(Page { + number: 0o_777_000_000_000 | (self.p4_index() << 18) | (self.p3_index() << 9) | + self.p2_index(), + }) + } +} + +struct Table(Page); + +impl Table { + fn entry(&self, index: usize) -> TableEntry { + assert!(index < ENTRY_COUNT); + let entry_address = self.0.start_address() + index * ENTRY_SIZE; + unsafe { *(entry_address as *const _) } + } +} + +#[derive(Debug, Clone, Copy)] +struct TableEntry(u64); + +impl TableEntry { + fn is_unused(&self) -> bool { + self.0 == 0 + } + + fn set_unused(&mut self) { + self.0 = 0 + } + + fn set(&mut self, frame: Frame, flags: TableEntryFlags) { + self.0 = (((frame.number as u64) << 12) & 0x000fffff_fffff000) | flags.bits(); + } + + fn flags(&self) -> TableEntryFlags { + TableEntryFlags::from_bits_truncate(self.0) + } + + fn pointed_frame(&self) -> Frame { + Frame { number: ((self.0 & 0x000fffff_fffff000) >> 12) as usize } + } +} + +bitflags! { + flags TableEntryFlags: 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, + } +} From 90dba32898d0c4b9aa53508e747c5fbd5f8285e2 Mon Sep 17 00:00:00 2001 From: Philipp Oppermann Date: Sun, 29 Nov 2015 18:46:34 +0100 Subject: [PATCH 06/76] =?UTF-8?q?Add=20=E2=80=9CMapping=20Page=20Tables?= =?UTF-8?q?=E2=80=9D=20section=20and=20extract=20page=20table=20entry=20fl?= =?UTF-8?q?ags?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- posts/DRAFT-paging.md | 51 ++++++++++++++++++++++++++++++++++++++----- 1 file changed, 46 insertions(+), 5 deletions(-) diff --git a/posts/DRAFT-paging.md b/posts/DRAFT-paging.md index 5c345e84..891dd198 100644 --- a/posts/DRAFT-paging.md +++ b/posts/DRAFT-paging.md @@ -5,8 +5,8 @@ title: 'A Paging Module' ## Paging -## A Paging Module -Let's begin a `memory/paging/mod.rs` module: +## Modeling Page Tables +Let's begin a `memory/paging/mod.rs` module to model page tables: ```rust pub const PAGE_SIZE: usize = 4096; @@ -98,7 +98,7 @@ fn pointed_frame(&self) -> Frame { ``` First we mask bits 12-51 and then convert the physical address to the corresponding frame number (through `>> 12`). We don't need to respect any sign extension here since it only exists for virtual addresses. -To model the various flags, we will use the [bitflags] crate. Unfortunately the official version depends on the standard library as `no_std` is still unstable. But since it does not actually require any `std` functions, it's pretty easy to create a `no_std` version. You can find it here [here][bitflags fork]. To add it as a dependency add the following to your `Cargo.toml`: +To model the various flags, we will use the [bitflags] crate. Unfortunately the official version depends on the standard library as `no_std` is still unstable. But since it does not actually require any `std` functions, it's pretty easy to create a `no_std` version. You can find it here [here][bitflags fork]. To add it as a dependency, add the following to your `Cargo.toml`: [bitflags]: /TODO [bitflags fork]: /TODO @@ -108,10 +108,51 @@ To model the various flags, we will use the [bitflags] crate. Unfortunately the git = "https://github.com/phil-opp/bitflags.git" branch = "no_std" ``` +Note that you need a `#[macro_use]` above the `extern crate` definition. +Now we can model the various flags: -## Recursive Mapping -The trick is to map the `P4` table _recursively_: The last entry doesn't point to a `P3` table, instead it points to the `P4` table itself. Through this entry, we can access and modify page tables of all levels. It may seem a bit strange at first, but is a very clean and simple solution once you wrapped your head around it. +```rust +bitflags! { + flags TableEntryFlags: 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, + } +} +``` +To extract the flags we create a `TableEntryFlags::flags` method that uses [from_bits_truncate]: + +[from_bits_truncate]: /TODO + +```rust +fn flags(&self) -> TableEntryFlags { + TableEntryFlags::from_bits_truncate(self.0) +} +``` + +Now we can read page tables and retrieve the mapping information. But since we can't access page tables through their physical address, we need to map them to some virtual address, too. + +## Mapping Page Tables +So how do we map the page tables itself? We don't have that problem for the current P4, P3, and P2 table since they are part of the identity-mapped area, but we need a way to access future tables, too. + +One solution could be to identity map all page table. That way we would not need to differentiate virtual and physical address and could easily access the tables. But it makes creating page tables more complicated since we need a physical frame whose corresponding page isn't already used for something else. And it clutters the virtual address space and may even cause heap fragmentation. + +An alternative solution is to map the page tables only temporary. So to read/write a page table, we would map it to some free virtual address. We could use a small pool of such virtual addresses and reuse them for various tables. This method occupies only few virtual addresses and is thus a good solution for 32-bit systems, which have small address spaces. But it makes things much more complicated since the temporary mapping requires updating other page tables, which need to be mapped, too. So we need to make sure that the temporary addresses are always mapped, else it could cause an endless recursion. + +We will use another solution, which uses a trick called _recursive mapping_. + +### Recursive Mapping +The trick is to map the `P4` table recursively: The last entry doesn't point to a `P3` table, but to the `P4` table itself. Through this entry, all page tables are mapped to an unique virtual address. So we can access and modify page tables of all levels by just setting one `P4` entry once. It may seem a bit strange at first, but is a very clean and simple solution once you wrapped your head around it. + +TODO image To access for example the `P4` table itself, we use the address that chooses the 511th `P4` entry, the 511th `P3` entry, the 511th `P2` entry and the 511th `P1` entry. Thus we choose the same `P4` frame over and over again and finally end up on it, too. Through the offset (12 bits) we choose the desired entry. From 08a9743caac106e1700e78be1b0142f0ae5a9fe2 Mon Sep 17 00:00:00 2001 From: Philipp Oppermann Date: Sun, 29 Nov 2015 19:26:25 +0100 Subject: [PATCH 07/76] Describle implementation of recursive mapping --- posts/DRAFT-paging.md | 10 +++++++++- 1 file changed, 9 insertions(+), 1 deletion(-) diff --git a/posts/DRAFT-paging.md b/posts/DRAFT-paging.md index 891dd198..595e250d 100644 --- a/posts/DRAFT-paging.md +++ b/posts/DRAFT-paging.md @@ -162,7 +162,15 @@ When accessing a `P2` table, we only loop two times and then choose entries that The math checks out, too. If all page tables are used, there is 1 `P4` table, 511 `P3` tables (the last entry is used for the recursive mapping), `511*512` `P2` tables, and `511*512*512` `P1` tables. So there are `134217728` page tables altogether. Each page table occupies 4KiB, so we need `134217728 * 4KiB = 512GiB` to store them. That's exactly the amount of memory that can be accessed through one `P4` entry since `4KiB per page * 512 P1 entries * 512 P2 entries * 512 P3 entries = 512GiB`. -TODO: recursive map in assembly +### Implementation +To map the `P4` table recursively, we just need to point the 511th entry to the table itself. Of course we could do it in Rust, but it would require some way of retrieving the physical address of the `P4`. It's easier to just some lines to our assembly: + +```nasm +mov eax, p4_table +or eax, 0b11 ; present + writable +mov [p4_table + 511 * 8], eax +``` +I put it right after the `setup_page_tables` label, but you can add it wherever you like. ## Translating addresses Now we can use the recursive mapping to translate virtual address manually. We will create a function that takes a virtual address and returns the corresponding physical address. From cf5e2715aea84f9e41024e6bee99ac9289ab11d6 Mon Sep 17 00:00:00 2001 From: Philipp Oppermann Date: Sun, 29 Nov 2015 20:05:17 +0100 Subject: [PATCH 08/76] Add section about translating virtual addresses --- posts/DRAFT-paging.md | 90 +++++++++++++++++++++++++++++++++---------- 1 file changed, 69 insertions(+), 21 deletions(-) diff --git a/posts/DRAFT-paging.md b/posts/DRAFT-paging.md index 595e250d..a512981c 100644 --- a/posts/DRAFT-paging.md +++ b/posts/DRAFT-paging.md @@ -172,48 +172,96 @@ mov [p4_table + 511 * 8], eax ``` I put it right after the `setup_page_tables` label, but you can add it wherever you like. -## Translating addresses -Now we can use the recursive mapping to translate virtual address manually. We will create a function that takes a virtual address and returns the corresponding physical address. - -TODO - - -To get the page tables and corresponding indexes for a page, we add some methods for `Page`: +### The special addresses +Now we can use special virtual addresses to access the page tables. For example, the `P4` table is available at `0xfffffffffffff000`. So let's add some methods to the `Page` type to get the corresponding page tables: ```rust -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} - const fn p4_table(&self) -> Table { - Table(Page { number: 0o_777_777_777_777 } ) + Table(Page { number: 0o_777_777_777_777 }) } fn p3_table(&self) -> Table { - Table(Page { - number: 0o_777_777_777_000 | self.p4_index(), - }) + Table(Page { number: 0o_777_777_777_000 | self.p4_index() }) } fn p2_table(&self) -> Table { Table(Page { - number: 0o_777_777_000_000 | (self.p4_index() << 9) | - self.p3_index(), + number: 0o_777_777_000_000 | (self.p4_index() << 9) | + self.p3_index(), }) } fn p1_table(&self) -> Table { Table(Page { - number: 0o_777_000_000_000 | (self.p4_index() << 18) | - (self.p3_index() << 9) | self.p2_index(), + number: 0o_777_000_000_000 | (self.p4_index() << 18) | + (self.p3_index() << 9) | self.p2_index(), }) } ``` -We use the octal numbers since they make it easy to express the 9 bit table indexes. +We use the octal numbers since they make it easy to express the 9 bit table indexes. The `p*_index` methods are described below. The P4 table is the same for all addresses, so we can make the function `const`. The associated page has index 511 in all four pages, thus the four `777` blocks. The P3 table, however, is different for different P4 indexes. So the last block varies from `000` to `776`, dependent on the page's P4 index. The P2 table additionally depends on the P3 index and to get the P1 table we use the recursive mapping only once (thus only one `777` block). +The `p*_index` methods look like this: + +```rust +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 } +``` + +## Translating addresses +Now we can use the recursive mapping to translate virtual address manually. We will create a function that takes a virtual address and returns the corresponding physical address: + +```rust +pub fn translate(virtual_address: usize) -> Option { + let page = Page::containing_address(virtual_address); + let offset = virtual_address % PAGE_SIZE; + + let frame_number = { + let p4_entry = page.p4_table().entry(page.p4_index()); + assert!(!p4_entry.flags().contains(HUGE_PAGE)); + if !p4_entry.flags().contains(PRESENT) { + return None; + } + + let p3_entry = unsafe { page.p3_table() }.entry(page.p3_index()); + if !p3_entry.flags().contains(PRESENT) { + return None; + } + if p3_entry.flags().contains(HUGE_PAGE) { + // 1GiB page (address must be 1GiB aligned) + let start_frame_number = p3_entry.pointed_frame().number; + assert!(start_frame_number % (ENTRY_COUNT * ENTRY_COUNT) == 0); + start_frame_number + page.p2_index() * ENTRY_COUNT + page.p1_index() + } else { + // 2MiB or 4KiB page + let p2_entry = unsafe { page.p2_table() }.entry(page.p2_index()); + if !p2_entry.flags().contains(PRESENT) { + return None; + } + if p2_entry.flags().contains(HUGE_PAGE) { + // 2MiB page (address must be 2MiB aligned) + let start_frame_number = p2_entry.pointed_frame().number; + assert!(start_frame_number % ENTRY_COUNT == 0); + start_frame_number + page.p1_index() + } else { + // standard 4KiB page + let p1_entry = unsafe { page.p1_table() }.entry(page.p1_index()); + assert!(!p1_entry.flags().contains(HUGE_PAGE)); + if !p1_entry.flags().contains(PRESENT) { + return None; + } + p1_entry.pointed_frame().number + } + } + }; + Some(frame_number * PAGE_SIZE + offset) +} +``` +(It's just some naive code and feels quite repeative… I'm open for alternative solutions) + TODO ## Switching Page Tables From 882af6f14e01b169758d459a8d996d1264883e4c Mon Sep 17 00:00:00 2001 From: Philipp Oppermann Date: Mon, 30 Nov 2015 12:24:32 +0100 Subject: [PATCH 09/76] Make `p{1,2,3}_table` functions unsafe --- posts/DRAFT-paging.md | 8 +++++--- src/memory/paging/mod.rs | 12 +++++++++--- 2 files changed, 14 insertions(+), 6 deletions(-) diff --git a/posts/DRAFT-paging.md b/posts/DRAFT-paging.md index a512981c..5d4c0dbb 100644 --- a/posts/DRAFT-paging.md +++ b/posts/DRAFT-paging.md @@ -180,18 +180,18 @@ const fn p4_table(&self) -> Table { Table(Page { number: 0o_777_777_777_777 }) } -fn p3_table(&self) -> Table { +unsafe fn p3_table(&self) -> Table { Table(Page { number: 0o_777_777_777_000 | self.p4_index() }) } -fn p2_table(&self) -> Table { +unsafe fn p2_table(&self) -> Table { Table(Page { number: 0o_777_777_000_000 | (self.p4_index() << 9) | self.p3_index(), }) } -fn p1_table(&self) -> Table { +unsafe fn p1_table(&self) -> Table { Table(Page { number: 0o_777_000_000_000 | (self.p4_index() << 18) | (self.p3_index() << 9) | self.p2_index(), @@ -202,6 +202,8 @@ We use the octal numbers since they make it easy to express the 9 bit table inde The P4 table is the same for all addresses, so we can make the function `const`. The associated page has index 511 in all four pages, thus the four `777` blocks. The P3 table, however, is different for different P4 indexes. So the last block varies from `000` to `776`, dependent on the page's P4 index. The P2 table additionally depends on the P3 index and to get the P1 table we use the recursive mapping only once (thus only one `777` block). +Since the P3, P2, and P1 tables may not exist, the corresponding functions are marked `unsafe`. It's only safe to call them if the entry in the parent table is `PRESENT` and does not have the `HUGE_PAGE` bit set. Else we would interpret random memory as a page table and get wrong results. + The `p*_index` methods look like this: ```rust diff --git a/src/memory/paging/mod.rs b/src/memory/paging/mod.rs index 9d710605..d6cf0b22 100644 --- a/src/memory/paging/mod.rs +++ b/src/memory/paging/mod.rs @@ -135,15 +135,21 @@ impl Page { Table(Page { number: 0o_777_777_777_777 }) } - fn p3_table(&self) -> Table { + /// # Safety + /// Only valid if the corresponding entry in the parent table is PRESENT and not HUGE_PAGE. + unsafe fn p3_table(&self) -> Table { Table(Page { number: 0o_777_777_777_000 | self.p4_index() }) } - fn p2_table(&self) -> Table { + /// # Safety + /// Only valid if the corresponding entry in the parent table is PRESENT and not HUGE_PAGE. + unsafe fn p2_table(&self) -> Table { Table(Page { number: 0o_777_777_000_000 | (self.p4_index() << 9) | self.p3_index() }) } - fn p1_table(&self) -> Table { + /// # Safety + /// Only valid if the corresponding entry in the parent table is PRESENT and not HUGE_PAGE. + unsafe fn p1_table(&self) -> Table { Table(Page { number: 0o_777_000_000_000 | (self.p4_index() << 18) | (self.p3_index() << 9) | self.p2_index(), From 907f51bb5bddde7a0369ae367d548a893d789f58 Mon Sep 17 00:00:00 2001 From: Philipp Oppermann Date: Mon, 30 Nov 2015 12:43:00 +0100 Subject: [PATCH 10/76] Add section about modifying table entries --- posts/DRAFT-paging.md | 24 ++++++++++++++++++++++++ src/memory/paging/mod.rs | 19 +++++++++++-------- 2 files changed, 35 insertions(+), 8 deletions(-) diff --git a/posts/DRAFT-paging.md b/posts/DRAFT-paging.md index 5d4c0dbb..61c73433 100644 --- a/posts/DRAFT-paging.md +++ b/posts/DRAFT-paging.md @@ -266,6 +266,30 @@ pub fn translate(virtual_address: usize) -> Option { TODO +## Modifying Entries +To modify page table entries, we add a `set_entry` function to `Table`: + +```rust +fn set_entry(&mut self, index: usize, value: TableEntry) { + assert!(index < ENTRY_COUNT); + let entry_address = self.0.start_address() + index * ENTRY_SIZE; + unsafe { *(entry_address as *mut _) = value } +} +``` + +And to create new entries, we add some `TableEntry` constructors: + +```rust +fn ununsed() -> TableEntry { + TableEntry(0) +} + +fn new(frame: Frame, flags: TableEntryFlags) -> TableEntry { + let frame_addr = (frame.number << 12) & 0x000fffff_fffff000; + TableEntry((frame_addr as u64) | flags.bits()) +} +``` + ## Switching Page Tables ## Mapping Pages diff --git a/src/memory/paging/mod.rs b/src/memory/paging/mod.rs index d6cf0b22..974d0393 100644 --- a/src/memory/paging/mod.rs +++ b/src/memory/paging/mod.rs @@ -165,22 +165,25 @@ impl Table { let entry_address = self.0.start_address() + index * ENTRY_SIZE; unsafe { *(entry_address as *const _) } } + + fn set_entry(&mut self, index: usize, value: TableEntry) { + assert!(index < ENTRY_COUNT); + let entry_address = self.0.start_address() + index * ENTRY_SIZE; + unsafe { *(entry_address as *mut _) = value } + } } #[derive(Debug, Clone, Copy)] struct TableEntry(u64); impl TableEntry { - fn is_unused(&self) -> bool { - self.0 == 0 + fn ununsed() -> TableEntry { + TableEntry(0) } - fn set_unused(&mut self) { - self.0 = 0 - } - - fn set(&mut self, frame: Frame, flags: TableEntryFlags) { - self.0 = (((frame.number as u64) << 12) & 0x000fffff_fffff000) | flags.bits(); + fn new(frame: Frame, flags: TableEntryFlags) -> TableEntry { + let frame_addr = (frame.number << 12) & 0x000fffff_fffff000; + TableEntry((frame_addr as u64) | flags.bits()) } fn flags(&self) -> TableEntryFlags { From 540c398ec391e87f39349f3e79464a701b309612 Mon Sep 17 00:00:00 2001 From: Philipp Oppermann Date: Mon, 30 Nov 2015 16:29:39 +0100 Subject: [PATCH 11/76] Alternative, more advanced design --- src/memory/paging/tables.rs | 256 ++++++++++++++++++++++++++++++++++++ 1 file changed, 256 insertions(+) create mode 100644 src/memory/paging/tables.rs diff --git a/src/memory/paging/tables.rs b/src/memory/paging/tables.rs new file mode 100644 index 00000000..7278f4a6 --- /dev/null +++ b/src/memory/paging/tables.rs @@ -0,0 +1,256 @@ +use core::marker::PhantomData; + +pub const fn P4(page: &Page) -> Table { + Table { + table_page: Page { number: 0o_777_777_777_777 }, + target_page_number: page.number, + _phantom: PhantomData, + } +} + + +pub fn translate(virtual_address: usize) -> Option { + let page = Page::containing_address(virtual_address); + let offset = virtual_address % PAGE_SIZE; + + let frame_number = { + let p3 = match P4(&page).next_table() { + None => return None, + Some(t) => t, + }; + + if p3.entry().flags().contains(PRESENT | HUGE_PAGE) { + // 1GiB page (address must be 1GiB aligned) + let start_frame_number = p3.entry().pointed_frame().number; + assert!(start_frame_number % (ENTRY_COUNT * ENTRY_COUNT) == 0); + start_frame_number + Table::::index_of(&page) * ENTRY_COUNT + + Table::::index_of(&page) + } else { + // 2MiB or 4KiB page + let p2 = match p3.next_table() { + None => return None, + Some(t) => t, + }; + + if p2.entry().flags().contains(PRESENT | HUGE_PAGE) { + // 2MiB page (address must be 2MiB aligned) + let start_frame_number = p2.entry().pointed_frame().number; + assert!(start_frame_number % ENTRY_COUNT == 0); + start_frame_number + Table::::index_of(&page) + } else { + // standard 4KiB page + let p1 = match p2.next_table() { + None => return None, + Some(t) => t, + }; + p1.entry().pointed_frame().number + } + } + }; + Some(frame_number * PAGE_SIZE + offset) +} + + +pub fn map_to(page: &Page, frame: Frame, flags: TableEntryFlags, allocator: &mut A) + where A: FrameAllocator +{ + let mut p3 = P4(page).next_table_create(allocator); + let mut p2 = p3.next_table_create(allocator); + let mut p1 = p2.next_table_create(allocator); + + assert!(!p1.entry().flags().contains(PRESENT)); + p1.set_entry(TableEntry::new(frame, flags)); +} + +trait TableLevel{ + fn level_number() -> usize; +} +pub enum Level1 {} +pub enum Level2 {} +pub enum Level3 {} +pub enum Level4 {} + +impl TableLevel for Level4 { + fn level_number() -> usize { + 4 + } +} +impl TableLevel for Level3 { + fn level_number() -> usize { + 3 + } +} +impl TableLevel for Level2 { + fn level_number() -> usize { + 2 + } +} +impl TableLevel for Level1 { + fn level_number() -> usize { + 1 + } +} + +trait HierachicalLevel: TableLevel { + type NextLevel: TableLevel; +} + +impl HierachicalLevel for Level4 { + type NextLevel = Level3; +} + +impl HierachicalLevel for Level3 { + type NextLevel = Level2; +} + +impl HierachicalLevel for Level2 { + type NextLevel = Level1; +} + +impl Table where L: TableLevel +{ + pub fn index_of(page: &Page) -> usize { + Self::index_of_page_number(page.number) + } + + fn index_of_page_number(page_number: usize) -> usize { + let s = (L::level_number() - 1) * 9; + (page_number >> s) & 0o777 + } + + fn index(&self) -> usize { + Self::index_of_page_number(self.target_page_number) + } +} + +use memory::{Frame, FrameAllocator}; + +pub const PAGE_SIZE: usize = 4096; +const ENTRY_SIZE: usize = 8; +const ENTRY_COUNT: usize = 512; + +pub type PhysicalAddress = usize; +pub type VirtualAddress = usize; + + +pub struct Page { + number: usize, +} + +impl Page { + fn containing_address(address: VirtualAddress) -> Page { + match address { + addr if addr < 0o_400_000_000_000_0000 => Page { number: addr / PAGE_SIZE }, + addr if addr >= 0o177777_400_000_000_000_0000 => { + Page { number: (address / PAGE_SIZE) & 0o_777_777_777_777 } + } + _ => panic!("invalid address: 0x{:x}", address), + } + } + + pub fn start_address(&self) -> VirtualAddress { + if self.number >= 0x800000000 { + // sign extension necessary + (self.number << 12) | 0xffff_000000000000 + } else { + self.number << 12 + } + } +} + +pub struct Table { + table_page: Page, + target_page_number: usize, + _phantom: PhantomData, +} + +impl Table where L: TableLevel +{ + fn entry(&self) -> TableEntry { + let entry_address = self.table_page.start_address() + self.index() * ENTRY_SIZE; + unsafe { *(entry_address as *const _) } + } + + fn set_entry(&mut self, value: TableEntry) { + let entry_address = self.table_page.start_address() + self.index() * ENTRY_SIZE; + unsafe { *(entry_address as *mut _) = value } + } + + fn zero(&mut self) { + let page = self.table_page.start_address() as *mut [TableEntry; ENTRY_COUNT]; + unsafe { *page = [TableEntry::unused(); ENTRY_COUNT] }; + } +} + +impl Table where L: HierachicalLevel +{ + fn next_table_internal(&self) -> Table { + Table { + table_page: Page { + number: ((self.target_page_number << 9) & 0o_777_777_777_777) | self.index(), + }, + target_page_number: self.target_page_number, + _phantom: PhantomData, + } + } + + fn next_table(&self) -> Option> { + if self.entry().flags().contains(PRESENT) { + Some(self.next_table_internal()) + } else { + None + } + } + + fn next_table_create(&mut self, allocator: &mut A) -> Table + where A: FrameAllocator + { + match self.next_table() { + Some(table) => table, + None => { + let frame = allocator.allocate_frame().expect("no frames available"); + self.set_entry(TableEntry::new(frame, PRESENT | WRITABLE)); + let mut next_table = self.next_table_internal(); + next_table.zero(); + next_table + } + } + } +} + +#[derive(Debug, Clone, Copy)] +struct TableEntry(u64); + +impl TableEntry { + const fn unused() -> TableEntry { + TableEntry(0) + } + + fn new(frame: Frame, flags: TableEntryFlags) -> TableEntry { + let frame_addr = (frame.number << 12) & 0x000fffff_fffff000; + TableEntry((frame_addr as u64) | flags.bits()) + } + + fn flags(&self) -> TableEntryFlags { + TableEntryFlags::from_bits_truncate(self.0) + } + + fn pointed_frame(&self) -> Frame { + Frame { number: ((self.0 & 0x000fffff_fffff000) >> 12) as usize } + } +} + +bitflags! { + flags TableEntryFlags: 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, + } +} From ae49ab5072cc690f3a3691a63d4b8f710618e77f Mon Sep 17 00:00:00 2001 From: Philipp Oppermann Date: Thu, 3 Dec 2015 17:27:53 +0100 Subject: [PATCH 12/76] Add alternative paging module with new design --- src/memory/mod.rs | 3 +- src/memory/paging_new/entry.rs | 41 ++++++++++++++++++++ src/memory/paging_new/levels.rs | 27 +++++++++++++ src/memory/paging_new/mod.rs | 49 +++++++++++++++++++++++ src/memory/paging_new/table.rs | 62 ++++++++++++++++++++++++++++++ src/memory/paging_new/translate.rs | 43 +++++++++++++++++++++ 6 files changed, 224 insertions(+), 1 deletion(-) create mode 100644 src/memory/paging_new/entry.rs create mode 100644 src/memory/paging_new/levels.rs create mode 100644 src/memory/paging_new/mod.rs create mode 100644 src/memory/paging_new/table.rs create mode 100644 src/memory/paging_new/translate.rs diff --git a/src/memory/mod.rs b/src/memory/mod.rs index 062cf380..2225308f 100644 --- a/src/memory/mod.rs +++ b/src/memory/mod.rs @@ -1,6 +1,7 @@ pub use self::area_frame_allocator::AreaFrameAllocator; -mod paging; +pub mod paging; +pub mod paging_new; mod area_frame_allocator; pub const PAGE_SIZE: usize = 4096; diff --git a/src/memory/paging_new/entry.rs b/src/memory/paging_new/entry.rs new file mode 100644 index 00000000..585d06a6 --- /dev/null +++ b/src/memory/paging_new/entry.rs @@ -0,0 +1,41 @@ +use memory::Frame; + +pub struct Entry(u64); + +impl Entry { + pub fn 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) -> Frame { + Frame { number: ((self.0 & 0x000fffff_fffff000) >> 12) as usize } + } + + pub fn set(&mut self, frame: Frame, flags: EntryFlags) { + let frame_addr = (frame.number << 12) & 0x000fffff_fffff000; + self.0 = (frame_addr as u64) | flags.bits(); + } +} + +bitflags! { + 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, + } +} diff --git a/src/memory/paging_new/levels.rs b/src/memory/paging_new/levels.rs new file mode 100644 index 00000000..ca28b8d3 --- /dev/null +++ b/src/memory/paging_new/levels.rs @@ -0,0 +1,27 @@ +pub trait TableLevel {} + +pub struct Level4; +pub struct Level3; +pub struct Level2; +pub struct Level1; + +impl TableLevel for Level4 {} +impl TableLevel for Level3 {} +impl TableLevel for Level2 {} +impl TableLevel for Level1 {} + +pub trait HierachicalLevel: TableLevel { + type NextLevel: TableLevel; +} + +impl HierachicalLevel for Level4 { + type NextLevel = Level3; +} + +impl HierachicalLevel for Level3 { + type NextLevel = Level2; +} + +impl HierachicalLevel for Level2 { + type NextLevel = Level1; +} diff --git a/src/memory/paging_new/mod.rs b/src/memory/paging_new/mod.rs new file mode 100644 index 00000000..7685f86f --- /dev/null +++ b/src/memory/paging_new/mod.rs @@ -0,0 +1,49 @@ +mod entry; +mod table; +mod levels; +mod translate; + +pub const PAGE_SIZE: usize = 4096; +const ENTRY_SIZE: usize = 8; +const ENTRY_COUNT: usize = 512; + +pub type PhysicalAddress = usize; +pub type VirtualAddress = usize; + +pub struct Page { + number: usize, +} + +impl Page { + fn containing_address(address: VirtualAddress) -> Page { + match address { + addr if addr < 0o_400_000_000_000_0000 => Page { number: addr / PAGE_SIZE }, + addr if addr >= 0o177777_400_000_000_000_0000 => { + Page { number: (address / PAGE_SIZE) & 0o_777_777_777_777 } + } + _ => panic!("invalid address: 0x{:x}", address), + } + } + + pub fn start_address(&self) -> VirtualAddress { + if self.number >= 0x800000000 { + // sign extension necessary + (self.number << 12) | 0xffff_000000000000 + } else { + self.number << 12 + } + } + + 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 + } +} diff --git a/src/memory/paging_new/table.rs b/src/memory/paging_new/table.rs new file mode 100644 index 00000000..cb4b3c49 --- /dev/null +++ b/src/memory/paging_new/table.rs @@ -0,0 +1,62 @@ +use super::{ENTRY_COUNT, Page}; +use super::entry::{Entry, PRESENT, HUGE_PAGE}; +use super::levels::{TableLevel, HierachicalLevel, Level4}; +use core::ops::{Index, IndexMut}; +use core::marker::PhantomData; + +pub const P4: *const Table = 0xffffffff_fffff000 as *const _; + +pub struct Table { + entries: [Entry; ENTRY_COUNT], + _phantom: PhantomData, +} + +impl Index for Table where L: TableLevel +{ + type Output = Entry; + + fn index(&self, index: usize) -> &Entry { + &self.entries[index] + } +} + +impl IndexMut for Table where L: TableLevel +{ + fn index_mut(&mut self, index: usize) -> &mut Entry { + &mut self.entries[index] + } +} + +impl Table where L: TableLevel +{ + pub fn zero(&mut self) { + for entry in self.entries.iter_mut() { + entry.set_unused(); + } + } +} + +impl Table where L: HierachicalLevel +{ + pub fn next_table(&self, index: usize) -> Option<&Table> { + self.next_table_address(index).map(|t| unsafe { &*(t as *const _) }) + } + + pub fn next_table_mut(&mut self, index: usize) -> Option<&mut Table> { + self.next_table_address(index).map(|t| unsafe { &mut *(t as *mut _) }) + } + + fn next_table_address(&self, index: usize) -> Option { + let entry_flags = self[index].flags(); + if entry_flags.contains(PRESENT) && !entry_flags.contains(HUGE_PAGE) { + let table_page = Page::containing_address(self as *const _ as usize); + assert!(table_page.number >= 0o_777_000_000_000); + let next_table_page = Page { + number: ((table_page.number << 9) & 0o_777_777_777_777) | index, + }; + Some(next_table_page.start_address()) + } else { + None + } + } +} diff --git a/src/memory/paging_new/translate.rs b/src/memory/paging_new/translate.rs new file mode 100644 index 00000000..d9b3ac32 --- /dev/null +++ b/src/memory/paging_new/translate.rs @@ -0,0 +1,43 @@ +use super::{VirtualAddress, PhysicalAddress, Page, PAGE_SIZE, ENTRY_COUNT}; +use super::table::{Table, P4}; +use super::entry::{PRESENT, HUGE_PAGE}; +use memory::Frame; + + +pub fn translate(virtual_address: usize) -> Option { + let page = Page::containing_address(virtual_address); + let offset = virtual_address % PAGE_SIZE; + + let p4 = unsafe { &*P4 }; + + let huge_page = || { + p4.next_table(page.p4_index()) + .and_then(|p3| { + // 1GiB page? + if p3[page.p3_index()].flags().contains(HUGE_PAGE | PRESENT) { + let start_frame_number = p3[page.p3_index()].pointed_frame().number; + // address must be 1GiB aligned + assert!(start_frame_number % (ENTRY_COUNT * ENTRY_COUNT) == 0); + return Some(start_frame_number + page.p2_index() * ENTRY_COUNT + page.p1_index()); + } + if let Some(p2) = p3.next_table(page.p3_index()) { + // 2MiB page? + if p2[page.p2_index()].flags().contains(HUGE_PAGE | PRESENT) { + let start_frame_number = p2[page.p2_index()].pointed_frame().number; + // address must be 2MiB aligned + assert!(start_frame_number % ENTRY_COUNT == 0); + return Some(start_frame_number + page.p1_index()); + } + } + None + }) + .map(|start_frame_number| Frame { number: start_frame_number }) + }; + + p4.next_table(page.p4_index()) + .and_then(|p3| p3.next_table(page.p3_index())) + .and_then(|p2| p2.next_table(page.p2_index())) + .map(|p1| p1[page.p1_index()].pointed_frame()) + .or_else(huge_page) + .map(|frame| frame.number * PAGE_SIZE + offset) +} From ace53fad91511207af4378e51016968c6468cf32 Mon Sep 17 00:00:00 2001 From: Philipp Oppermann Date: Thu, 3 Dec 2015 18:36:53 +0100 Subject: [PATCH 13/76] Updates for the old paging code --- src/memory/paging/mod.rs | 85 ++++++++++++++++++------------------- src/memory/paging/tables.rs | 2 +- 2 files changed, 42 insertions(+), 45 deletions(-) diff --git a/src/memory/paging/mod.rs b/src/memory/paging/mod.rs index 974d0393..5421f472 100644 --- a/src/memory/paging/mod.rs +++ b/src/memory/paging/mod.rs @@ -1,4 +1,4 @@ -use memory::Frame; +use memory::{Frame, FrameAllocator}; pub const PAGE_SIZE: usize = 4096; const ENTRY_SIZE: usize = 8; @@ -7,48 +7,6 @@ const ENTRY_COUNT: usize = 512; pub type PhysicalAddress = usize; pub type VirtualAddress = usize; -// pub fn translate(virtual_address: usize) -> Option { -// let page = Page::containing_address(virtual_address); -// let offset = virtual_address % PAGE_SIZE; -// -// let p4_entry = page.p4_table().entry(page.p4_index()); -// assert!(!p4_entry.flags().contains(HUGE_PAGE)); -// if !p4_entry.flags().contains(PRESENT) { -// return None; -// } -// -// let p3_entry = page.p3_table().entry(page.p3_index()); -// if !p3_entry.flags().contains(PRESENT) { -// return None; -// } -// if p3_entry.flags().contains(HUGE_PAGE) { -// 1GiB page (address must be 1GiB aligned) -// let start_frame_number = p3_entry.pointed_frame().number; -// assert!(start_frame_number % (ENTRY_COUNT * ENTRY_COUNT) == 0); -// let frame_number = start_frame_number + page.p2_index() * ENTRY_COUNT + page.p1_index(); -// return Some(frame_number * PAGE_SIZE + offset); -// } -// -// let p2_entry = page.p2_table().entry(page.p2_index()); -// if !p2_entry.flags().contains(PRESENT) { -// return None; -// } -// if p2_entry.flags().contains(HUGE_PAGE) { -// 2MiB page (address must be 2MiB aligned) -// let start_frame_number = p2_entry.pointed_frame().number; -// assert!(start_frame_number % ENTRY_COUNT == 0); -// let frame_number = start_frame_number + page.p1_index(); -// return Some(frame_number * PAGE_SIZE + offset); -// } -// -// let p1_entry = page.p1_table().entry(page.p1_index()); -// assert!(!p1_entry.flags().contains(HUGE_PAGE)); -// if !p1_entry.flags().contains(PRESENT) { -// return None; -// } -// Some(p1_entry.pointed_frame().number * PAGE_SIZE + offset) -// } - pub fn translate(virtual_address: usize) -> Option { let page = Page::containing_address(virtual_address); let offset = virtual_address % PAGE_SIZE; @@ -94,6 +52,37 @@ pub fn translate(virtual_address: usize) -> Option { Some(frame_number * PAGE_SIZE + offset) } +pub fn map_to(page: &Page, frame: Frame, flags: TableEntryFlags, allocator: &mut A) + where A: FrameAllocator +{ + let p4_index = page.p4_index(); + let p3_index = page.p3_index(); + let p2_index = page.p2_index(); + let p1_index = page.p1_index(); + + let mut p4 = page.p4_table(); + if !p4.entry(p4_index).flags().contains(PRESENT) { + let frame = allocator.allocate_frame().expect("no frames available"); + p4.set_entry(p4_index, TableEntry::new(frame, PRESENT | WRITABLE)); + unsafe { page.p3_table() }.zero(); + } + let mut p3 = unsafe { page.p3_table() }; + if !p3.entry(p3_index).flags().contains(PRESENT) { + let frame = allocator.allocate_frame().expect("no frames available"); + p3.set_entry(p3_index, TableEntry::new(frame, PRESENT | WRITABLE)); + unsafe { page.p2_table() }.zero(); + } + let mut p2 = unsafe { page.p2_table() }; + if !p2.entry(p2_index).flags().contains(PRESENT) { + let frame = allocator.allocate_frame().expect("no frames available"); + p2.set_entry(p2_index, TableEntry::new(frame, PRESENT | WRITABLE)); + unsafe { page.p1_table() }.zero(); + } + let mut p1 = unsafe { page.p1_table() }; + assert!(!p1.entry(p1_index).flags().contains(PRESENT)); + p1.set_entry(p1_index, TableEntry::new(frame, flags)); +} + pub struct Page { number: usize, } @@ -171,13 +160,18 @@ impl Table { let entry_address = self.0.start_address() + index * ENTRY_SIZE; unsafe { *(entry_address as *mut _) = value } } + + fn zero(&mut self) { + let page = self.0.start_address() as *mut [TableEntry; ENTRY_COUNT]; + unsafe { *page = [TableEntry::unused(); ENTRY_COUNT] }; + } } #[derive(Debug, Clone, Copy)] struct TableEntry(u64); impl TableEntry { - fn ununsed() -> TableEntry { + const fn unused() -> TableEntry { TableEntry(0) } @@ -209,3 +203,6 @@ bitflags! { const NO_EXECUTE = 1 << 63, } } + + +mod tables; diff --git a/src/memory/paging/tables.rs b/src/memory/paging/tables.rs index 7278f4a6..2e554f5d 100644 --- a/src/memory/paging/tables.rs +++ b/src/memory/paging/tables.rs @@ -187,7 +187,7 @@ impl Table where L: HierachicalLevel fn next_table_internal(&self) -> Table { Table { table_page: Page { - number: ((self.target_page_number << 9) & 0o_777_777_777_777) | self.index(), + number: ((self.table_page.number << 9) & 0o_777_777_777_777) | self.index(), }, target_page_number: self.target_page_number, _phantom: PhantomData, From c7fe1348f07a1d35818fe474f7eeff97f2cc49c0 Mon Sep 17 00:00:00 2001 From: Philipp Oppermann Date: Thu, 3 Dec 2015 18:38:15 +0100 Subject: [PATCH 14/76] Old attempts for a new design --- src/memory/paging/multilevel.rs | 52 +++++++++++++ src/memory/paging/multilevel2.rs | 125 +++++++++++++++++++++++++++++++ 2 files changed, 177 insertions(+) create mode 100644 src/memory/paging/multilevel.rs create mode 100644 src/memory/paging/multilevel2.rs diff --git a/src/memory/paging/multilevel.rs b/src/memory/paging/multilevel.rs new file mode 100644 index 00000000..50dc0995 --- /dev/null +++ b/src/memory/paging/multilevel.rs @@ -0,0 +1,52 @@ +use core::marker::PhantomData; +use super::{VirtualAddress, Page, ENTRY_COUNT}; +use super::table::{Entry, Table, PRESENT}; +use super::levels::{TableLevel, HierachicalLevel, Level4, Level3, Level2, Level1}; + +pub fn P4_entry(address: VirtualAddress) -> EntryRef { + let p4_page = Page { number: 0o_777_777_777_777 }; + let p4 = p4_page.start_address() as *mut Table; + EntryRef { + target_address: address, + table: p4, + _phantom: PhantomData, + } +} + +pub struct EntryRef { + target_address: VirtualAddress, + table: *mut Table, + _phantom: PhantomData, +} + +impl EntryRef where L: HierachicalLevel +{ + pub fn next_level(&self) -> Option> { + if self.entry().flags().contains(PRESENT) { + let next_table_page = { + let table_page = Page::containing_address(self.table as usize); + let index = table_index::(self.target_address); + Page { number: ((table_page.number << 9) & 0o_777_777_777_777) | index } + }; + let next_table = next_table_page.start_address() as *mut Table; + Some(EntryRef { + target_address: self.target_address, + table: next_table, + _phantom: PhantomData, + }) + } else { + None + } + } + + fn entry(&self) -> &Entry { + unsafe { &(*self.table).0[table_index::(self.target_address)] } + } +} + +fn table_index(address: VirtualAddress) -> usize + where L: TableLevel +{ + let shift = 12 + (L::level_number() - 1) * 9; + (address >> shift) & 0o777 +} diff --git a/src/memory/paging/multilevel2.rs b/src/memory/paging/multilevel2.rs new file mode 100644 index 00000000..5a844386 --- /dev/null +++ b/src/memory/paging/multilevel2.rs @@ -0,0 +1,125 @@ +use core::marker::PhantomData; + +pub const fn P4(page: &Page) -> Table { + Table { + table_page: Page { number: 0o_777_777_777_777 }, + target_page_number: page.number, + _phantom: PhantomData, + } +} + +impl Table where L: TableLevel +{ + pub fn index_of(page: &Page) -> usize { + Self::index_of_page_number(page.number) + } + + fn index_of_page_number(page_number: usize) -> usize { + let s = (L::level_number() - 1) * 9; + (page_number >> s) & 0o777 + } + + fn index(&self) -> usize { + Self::index_of_page_number(self.target_page_number) + } +} + +use memory::{Frame, FrameAllocator}; + + +pub struct Table { + table_page: Page, + target_page_number: usize, + _phantom: PhantomData, +} + +impl Table where L: TableLevel +{ + fn entry(&self) -> TableEntry { + let entry_address = self.table_page.start_address() + self.index() * ENTRY_SIZE; + unsafe { *(entry_address as *const _) } + } + + fn set_entry(&mut self, value: TableEntry) { + let entry_address = self.table_page.start_address() + self.index() * ENTRY_SIZE; + unsafe { *(entry_address as *mut _) = value } + } + + fn zero(&mut self) { + let page = self.table_page.start_address() as *mut [TableEntry; ENTRY_COUNT]; + unsafe { *page = [TableEntry::unused(); ENTRY_COUNT] }; + } +} + +impl Table where L: HierachicalLevel +{ + fn next_table_internal(&self) -> Table { + Table { + table_page: Page { + number: ((self.table_page.number << 9) & 0o_777_777_777_777) | self.index(), + }, + target_page_number: self.target_page_number, + _phantom: PhantomData, + } + } + + fn next_table(&self) -> Option> { + if self.entry().flags().contains(PRESENT) { + Some(self.next_table_internal()) + } else { + None + } + } + + fn next_table_create(&mut self, allocator: &mut A) -> Table + where A: FrameAllocator + { + match self.next_table() { + Some(table) => table, + None => { + let frame = allocator.allocate_frame().expect("no frames available"); + self.set_entry(TableEntry::new(frame, PRESENT | WRITABLE)); + let mut next_table = self.next_table_internal(); + next_table.zero(); + next_table + } + } + } +} + +#[derive(Debug, Clone, Copy)] +struct TableEntry(u64); + +impl TableEntry { + const fn unused() -> TableEntry { + TableEntry(0) + } + + fn new(frame: Frame, flags: TableEntryFlags) -> TableEntry { + let frame_addr = (frame.number << 12) & 0x000fffff_fffff000; + TableEntry((frame_addr as u64) | flags.bits()) + } + + fn flags(&self) -> TableEntryFlags { + TableEntryFlags::from_bits_truncate(self.0) + } + + fn pointed_frame(&self) -> Frame { + Frame { number: ((self.0 & 0x000fffff_fffff000) >> 12) as usize } + } +} + +bitflags! { + flags TableEntryFlags: 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, + } +} From 204a9d9c9d149f198fa40d69e2834e08745b27cc Mon Sep 17 00:00:00 2001 From: Philipp Oppermann Date: Thu, 3 Dec 2015 18:39:04 +0100 Subject: [PATCH 15/76] Add some sections (now outdated) --- posts/DRAFT-paging.md | 42 ++++++++++++++++++++++++++++++++++++++---- 1 file changed, 38 insertions(+), 4 deletions(-) diff --git a/posts/DRAFT-paging.md b/posts/DRAFT-paging.md index 61c73433..10c5c521 100644 --- a/posts/DRAFT-paging.md +++ b/posts/DRAFT-paging.md @@ -143,7 +143,7 @@ Now we can read page tables and retrieve the mapping information. But since we c ## Mapping Page Tables So how do we map the page tables itself? We don't have that problem for the current P4, P3, and P2 table since they are part of the identity-mapped area, but we need a way to access future tables, too. -One solution could be to identity map all page table. That way we would not need to differentiate virtual and physical address and could easily access the tables. But it makes creating page tables more complicated since we need a physical frame whose corresponding page isn't already used for something else. And it clutters the virtual address space and may even cause heap fragmentation. +One solution could be to identity map all page table. That way we would not need to differentiate virtual and physical address and could easily access the tables. But it makes creating page tables more complicated since we need a physical frame whose corresponding page isn't already used for something else. And it clutters the virtual address space and makes it impossible to map page tables in address spaces of user processes (since we can't just occupy some random pages there). An alternative solution is to map the page tables only temporary. So to read/write a page table, we would map it to some free virtual address. We could use a small pool of such virtual addresses and reuse them for various tables. This method occupies only few virtual addresses and is thus a good solution for 32-bit systems, which have small address spaces. But it makes things much more complicated since the temporary mapping requires updating other page tables, which need to be mapped, too. So we need to make sure that the temporary addresses are always mapped, else it could cause an endless recursion. @@ -280,7 +280,7 @@ fn set_entry(&mut self, index: usize, value: TableEntry) { And to create new entries, we add some `TableEntry` constructors: ```rust -fn ununsed() -> TableEntry { +const fn unused() -> TableEntry { TableEntry(0) } @@ -290,8 +290,42 @@ fn new(frame: Frame, flags: TableEntryFlags) -> TableEntry { } ``` -## Switching Page Tables - ## Mapping Pages +To map + +```rust +pub fn map_to(page: &Page, frame: Frame, flags: TableEntryFlags, + allocator: &mut A) where A: FrameAllocator +{ + let p4_index = page.p4_index(); + let p3_index = page.p3_index(); + let p2_index = page.p2_index(); + let p1_index = page.p1_index(); + + let mut p4 = page.p4_table(); + if !p4.entry(p4_index).flags().contains(PRESENT) { + let frame = allocator.allocate_frame().expect("no frames available"); + p4.set_entry(p4_index, TableEntry::new(frame, PRESENT | WRITABLE)); + unsafe { page.p3_table() }.zero(); + } + let mut p3 = unsafe { page.p3_table() }; + if !p3.entry(p3_index).flags().contains(PRESENT) { + let frame = allocator.allocate_frame().expect("no frames available"); + p3.set_entry(p3_index, TableEntry::new(frame, PRESENT | WRITABLE)); + unsafe { page.p2_table() }.zero(); + } + let mut p2 = unsafe { page.p2_table() }; + if !p2.entry(p2_index).flags().contains(PRESENT) { + let frame = allocator.allocate_frame().expect("no frames available"); + p2.set_entry(p2_index, TableEntry::new(frame, PRESENT | WRITABLE)); + unsafe { page.p1_table() }.zero(); + } + let mut p1 = unsafe { page.p1_table() }; + assert!(!p1.entry(p1_index).flags().contains(PRESENT)); + p1.set_entry(p1_index, TableEntry::new(frame, flags)); +} +``` ## Unmapping Pages + +## Switching Page Tables From b7debed3b761312c1159aecd7cdbe745ba9e8fc3 Mon Sep 17 00:00:00 2001 From: Philipp Oppermann Date: Thu, 3 Dec 2015 18:39:34 +0100 Subject: [PATCH 16/76] Increase stack size to 4096*2 --- src/arch/x86_64/boot.asm | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/arch/x86_64/boot.asm b/src/arch/x86_64/boot.asm index 4abb2899..7035fc17 100644 --- a/src/arch/x86_64/boot.asm +++ b/src/arch/x86_64/boot.asm @@ -156,7 +156,7 @@ p3_table: p2_table: resb 4096 stack_bottom: - resb 4096 + resb 4096 * 2 stack_top: section .rodata From 562221d725a138d3af6171fba5c957be9e24f7b3 Mon Sep 17 00:00:00 2001 From: Philipp Oppermann Date: Thu, 3 Dec 2015 18:40:10 +0100 Subject: [PATCH 17/76] Add some unstable features and some printing tests --- src/lib.rs | 8 ++++++++ 1 file changed, 8 insertions(+) diff --git a/src/lib.rs b/src/lib.rs index 8949a2a7..653c5350 100644 --- a/src/lib.rs +++ b/src/lib.rs @@ -14,6 +14,7 @@ #![feature(no_std, lang_items)] #![feature(const_fn, unique, core_str_ext, iter_cmp, optin_builtin_traits)] +#![feature(core_intrinsics, core_slice_ext)] #![no_std] extern crate rlibc; @@ -68,6 +69,13 @@ pub extern fn rust_main(multiboot_information_address: usize) { } } + //println!("outer {}", {println!("inner"); "NO DEADLOCK"}); + println!("{:?}", memory::paging::translate(0)); + println!("{:?}", memory::paging::translate(12345)); + //for i in 0.. { + //println!("0o{:o}", memory::paging::translate1(0o_000_000_000_0000 + i << 21).unwrap()); + //} + loop{} } From a8df7b2e4d50279eaab9bf0ee6552f033045585a Mon Sep 17 00:00:00 2001 From: Philipp Oppermann Date: Thu, 3 Dec 2015 21:17:18 +0100 Subject: [PATCH 18/76] Finish basics of new paging module --- src/memory/mod.rs | 3 +- src/memory/{paging_new => paging}/entry.rs | 0 src/memory/paging/mapping.rs | 24 ++ src/memory/paging/mod.rs | 192 ++----------- src/memory/paging/multilevel.rs | 52 ---- src/memory/paging/multilevel2.rs | 125 --------- src/memory/{paging_new => paging}/table.rs | 87 ++++-- src/memory/paging/tables.rs | 256 ------------------ .../{paging_new => paging}/translate.rs | 5 +- src/memory/paging_new/levels.rs | 27 -- src/memory/paging_new/mod.rs | 49 ---- 11 files changed, 122 insertions(+), 698 deletions(-) rename src/memory/{paging_new => paging}/entry.rs (100%) create mode 100644 src/memory/paging/mapping.rs delete mode 100644 src/memory/paging/multilevel.rs delete mode 100644 src/memory/paging/multilevel2.rs rename src/memory/{paging_new => paging}/table.rs (53%) delete mode 100644 src/memory/paging/tables.rs rename src/memory/{paging_new => paging}/translate.rs (94%) delete mode 100644 src/memory/paging_new/levels.rs delete mode 100644 src/memory/paging_new/mod.rs diff --git a/src/memory/mod.rs b/src/memory/mod.rs index 2225308f..57335343 100644 --- a/src/memory/mod.rs +++ b/src/memory/mod.rs @@ -1,7 +1,6 @@ pub use self::area_frame_allocator::AreaFrameAllocator; pub mod paging; -pub mod paging_new; mod area_frame_allocator; pub const PAGE_SIZE: usize = 4096; @@ -13,7 +12,7 @@ pub struct Frame { impl Frame { fn containing_address(address: usize) -> Frame { - Frame{ number: address / PAGE_SIZE } + Frame { number: address / PAGE_SIZE } } } diff --git a/src/memory/paging_new/entry.rs b/src/memory/paging/entry.rs similarity index 100% rename from src/memory/paging_new/entry.rs rename to src/memory/paging/entry.rs diff --git a/src/memory/paging/mapping.rs b/src/memory/paging/mapping.rs new file mode 100644 index 00000000..8a6af29f --- /dev/null +++ b/src/memory/paging/mapping.rs @@ -0,0 +1,24 @@ +use memory::Frame; +use super::Page; +use super::entry::{EntryFlags, PRESENT}; +use memory::FrameAllocator; +use super::table::P4; + +pub fn map(page: &Page, flags: EntryFlags, allocator: &mut A) + where A: FrameAllocator +{ + let frame = allocator.allocate_frame().expect("out of memory"); + map_to(page, frame, flags, allocator) +} + +pub fn map_to(page: &Page, frame: Frame, flags: EntryFlags, allocator: &mut A) + where A: FrameAllocator +{ + let p4 = unsafe { &mut *P4 }; + 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()].flags().contains(PRESENT)); + p1[page.p1_index()].set(frame, flags | PRESENT); +} diff --git a/src/memory/paging/mod.rs b/src/memory/paging/mod.rs index 5421f472..5130b982 100644 --- a/src/memory/paging/mod.rs +++ b/src/memory/paging/mod.rs @@ -1,88 +1,38 @@ -use memory::{Frame, FrameAllocator}; +mod entry; +mod table; +pub mod translate; +pub mod mapping; + +pub fn test(frame_allocator: &mut A) + where A: super::FrameAllocator +{ + use self::entry::PRESENT; + mapping::map(&Page::containing_address(0xdeadbeaa000), + PRESENT, + frame_allocator); + mapping::map(&Page::containing_address(0xdeadbeab000), + PRESENT, + frame_allocator); + mapping::map(&Page::containing_address(0xdeadbeac000), + PRESENT, + frame_allocator); + mapping::map(&Page::containing_address(0xdeadbead000), + PRESENT, + frame_allocator); + mapping::map(&Page::containing_address(0xcafebeaf000), + PRESENT, + frame_allocator); + mapping::map(&Page::containing_address(0x0), + PRESENT, + frame_allocator); +} pub const PAGE_SIZE: usize = 4096; -const ENTRY_SIZE: usize = 8; const ENTRY_COUNT: usize = 512; pub type PhysicalAddress = usize; pub type VirtualAddress = usize; -pub fn translate(virtual_address: usize) -> Option { - let page = Page::containing_address(virtual_address); - let offset = virtual_address % PAGE_SIZE; - - let frame_number = { - let p4_entry = page.p4_table().entry(page.p4_index()); - assert!(!p4_entry.flags().contains(HUGE_PAGE)); - if !p4_entry.flags().contains(PRESENT) { - return None; - } - - let p3_entry = unsafe { page.p3_table() }.entry(page.p3_index()); - if !p3_entry.flags().contains(PRESENT) { - return None; - } - if p3_entry.flags().contains(HUGE_PAGE) { - // 1GiB page (address must be 1GiB aligned) - let start_frame_number = p3_entry.pointed_frame().number; - assert!(start_frame_number % (ENTRY_COUNT * ENTRY_COUNT) == 0); - start_frame_number + page.p2_index() * ENTRY_COUNT + page.p1_index() - } else { - // 2MiB or 4KiB page - let p2_entry = unsafe { page.p2_table() }.entry(page.p2_index()); - if !p2_entry.flags().contains(PRESENT) { - return None; - } - if p2_entry.flags().contains(HUGE_PAGE) { - // 2MiB page (address must be 2MiB aligned) - let start_frame_number = p2_entry.pointed_frame().number; - assert!(start_frame_number % ENTRY_COUNT == 0); - start_frame_number + page.p1_index() - } else { - // standard 4KiB page - let p1_entry = unsafe { page.p1_table() }.entry(page.p1_index()); - assert!(!p1_entry.flags().contains(HUGE_PAGE)); - if !p1_entry.flags().contains(PRESENT) { - return None; - } - p1_entry.pointed_frame().number - } - } - }; - Some(frame_number * PAGE_SIZE + offset) -} - -pub fn map_to(page: &Page, frame: Frame, flags: TableEntryFlags, allocator: &mut A) - where A: FrameAllocator -{ - let p4_index = page.p4_index(); - let p3_index = page.p3_index(); - let p2_index = page.p2_index(); - let p1_index = page.p1_index(); - - let mut p4 = page.p4_table(); - if !p4.entry(p4_index).flags().contains(PRESENT) { - let frame = allocator.allocate_frame().expect("no frames available"); - p4.set_entry(p4_index, TableEntry::new(frame, PRESENT | WRITABLE)); - unsafe { page.p3_table() }.zero(); - } - let mut p3 = unsafe { page.p3_table() }; - if !p3.entry(p3_index).flags().contains(PRESENT) { - let frame = allocator.allocate_frame().expect("no frames available"); - p3.set_entry(p3_index, TableEntry::new(frame, PRESENT | WRITABLE)); - unsafe { page.p2_table() }.zero(); - } - let mut p2 = unsafe { page.p2_table() }; - if !p2.entry(p2_index).flags().contains(PRESENT) { - let frame = allocator.allocate_frame().expect("no frames available"); - p2.set_entry(p2_index, TableEntry::new(frame, PRESENT | WRITABLE)); - unsafe { page.p1_table() }.zero(); - } - let mut p1 = unsafe { page.p1_table() }; - assert!(!p1.entry(p1_index).flags().contains(PRESENT)); - p1.set_entry(p1_index, TableEntry::new(frame, flags)); -} - pub struct Page { number: usize, } @@ -119,90 +69,4 @@ impl Page { fn p1_index(&self) -> usize { (self.number >> 0) & 0o777 } - - const fn p4_table(&self) -> Table { - Table(Page { number: 0o_777_777_777_777 }) - } - - /// # Safety - /// Only valid if the corresponding entry in the parent table is PRESENT and not HUGE_PAGE. - unsafe fn p3_table(&self) -> Table { - Table(Page { number: 0o_777_777_777_000 | self.p4_index() }) - } - - /// # Safety - /// Only valid if the corresponding entry in the parent table is PRESENT and not HUGE_PAGE. - unsafe fn p2_table(&self) -> Table { - Table(Page { number: 0o_777_777_000_000 | (self.p4_index() << 9) | self.p3_index() }) - } - - /// # Safety - /// Only valid if the corresponding entry in the parent table is PRESENT and not HUGE_PAGE. - unsafe fn p1_table(&self) -> Table { - Table(Page { - number: 0o_777_000_000_000 | (self.p4_index() << 18) | (self.p3_index() << 9) | - self.p2_index(), - }) - } } - -struct Table(Page); - -impl Table { - fn entry(&self, index: usize) -> TableEntry { - assert!(index < ENTRY_COUNT); - let entry_address = self.0.start_address() + index * ENTRY_SIZE; - unsafe { *(entry_address as *const _) } - } - - fn set_entry(&mut self, index: usize, value: TableEntry) { - assert!(index < ENTRY_COUNT); - let entry_address = self.0.start_address() + index * ENTRY_SIZE; - unsafe { *(entry_address as *mut _) = value } - } - - fn zero(&mut self) { - let page = self.0.start_address() as *mut [TableEntry; ENTRY_COUNT]; - unsafe { *page = [TableEntry::unused(); ENTRY_COUNT] }; - } -} - -#[derive(Debug, Clone, Copy)] -struct TableEntry(u64); - -impl TableEntry { - const fn unused() -> TableEntry { - TableEntry(0) - } - - fn new(frame: Frame, flags: TableEntryFlags) -> TableEntry { - let frame_addr = (frame.number << 12) & 0x000fffff_fffff000; - TableEntry((frame_addr as u64) | flags.bits()) - } - - fn flags(&self) -> TableEntryFlags { - TableEntryFlags::from_bits_truncate(self.0) - } - - fn pointed_frame(&self) -> Frame { - Frame { number: ((self.0 & 0x000fffff_fffff000) >> 12) as usize } - } -} - -bitflags! { - flags TableEntryFlags: 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, - } -} - - -mod tables; diff --git a/src/memory/paging/multilevel.rs b/src/memory/paging/multilevel.rs deleted file mode 100644 index 50dc0995..00000000 --- a/src/memory/paging/multilevel.rs +++ /dev/null @@ -1,52 +0,0 @@ -use core::marker::PhantomData; -use super::{VirtualAddress, Page, ENTRY_COUNT}; -use super::table::{Entry, Table, PRESENT}; -use super::levels::{TableLevel, HierachicalLevel, Level4, Level3, Level2, Level1}; - -pub fn P4_entry(address: VirtualAddress) -> EntryRef { - let p4_page = Page { number: 0o_777_777_777_777 }; - let p4 = p4_page.start_address() as *mut Table; - EntryRef { - target_address: address, - table: p4, - _phantom: PhantomData, - } -} - -pub struct EntryRef { - target_address: VirtualAddress, - table: *mut Table, - _phantom: PhantomData, -} - -impl EntryRef where L: HierachicalLevel -{ - pub fn next_level(&self) -> Option> { - if self.entry().flags().contains(PRESENT) { - let next_table_page = { - let table_page = Page::containing_address(self.table as usize); - let index = table_index::(self.target_address); - Page { number: ((table_page.number << 9) & 0o_777_777_777_777) | index } - }; - let next_table = next_table_page.start_address() as *mut Table; - Some(EntryRef { - target_address: self.target_address, - table: next_table, - _phantom: PhantomData, - }) - } else { - None - } - } - - fn entry(&self) -> &Entry { - unsafe { &(*self.table).0[table_index::(self.target_address)] } - } -} - -fn table_index(address: VirtualAddress) -> usize - where L: TableLevel -{ - let shift = 12 + (L::level_number() - 1) * 9; - (address >> shift) & 0o777 -} diff --git a/src/memory/paging/multilevel2.rs b/src/memory/paging/multilevel2.rs deleted file mode 100644 index 5a844386..00000000 --- a/src/memory/paging/multilevel2.rs +++ /dev/null @@ -1,125 +0,0 @@ -use core::marker::PhantomData; - -pub const fn P4(page: &Page) -> Table { - Table { - table_page: Page { number: 0o_777_777_777_777 }, - target_page_number: page.number, - _phantom: PhantomData, - } -} - -impl Table where L: TableLevel -{ - pub fn index_of(page: &Page) -> usize { - Self::index_of_page_number(page.number) - } - - fn index_of_page_number(page_number: usize) -> usize { - let s = (L::level_number() - 1) * 9; - (page_number >> s) & 0o777 - } - - fn index(&self) -> usize { - Self::index_of_page_number(self.target_page_number) - } -} - -use memory::{Frame, FrameAllocator}; - - -pub struct Table { - table_page: Page, - target_page_number: usize, - _phantom: PhantomData, -} - -impl Table where L: TableLevel -{ - fn entry(&self) -> TableEntry { - let entry_address = self.table_page.start_address() + self.index() * ENTRY_SIZE; - unsafe { *(entry_address as *const _) } - } - - fn set_entry(&mut self, value: TableEntry) { - let entry_address = self.table_page.start_address() + self.index() * ENTRY_SIZE; - unsafe { *(entry_address as *mut _) = value } - } - - fn zero(&mut self) { - let page = self.table_page.start_address() as *mut [TableEntry; ENTRY_COUNT]; - unsafe { *page = [TableEntry::unused(); ENTRY_COUNT] }; - } -} - -impl Table where L: HierachicalLevel -{ - fn next_table_internal(&self) -> Table { - Table { - table_page: Page { - number: ((self.table_page.number << 9) & 0o_777_777_777_777) | self.index(), - }, - target_page_number: self.target_page_number, - _phantom: PhantomData, - } - } - - fn next_table(&self) -> Option> { - if self.entry().flags().contains(PRESENT) { - Some(self.next_table_internal()) - } else { - None - } - } - - fn next_table_create(&mut self, allocator: &mut A) -> Table - where A: FrameAllocator - { - match self.next_table() { - Some(table) => table, - None => { - let frame = allocator.allocate_frame().expect("no frames available"); - self.set_entry(TableEntry::new(frame, PRESENT | WRITABLE)); - let mut next_table = self.next_table_internal(); - next_table.zero(); - next_table - } - } - } -} - -#[derive(Debug, Clone, Copy)] -struct TableEntry(u64); - -impl TableEntry { - const fn unused() -> TableEntry { - TableEntry(0) - } - - fn new(frame: Frame, flags: TableEntryFlags) -> TableEntry { - let frame_addr = (frame.number << 12) & 0x000fffff_fffff000; - TableEntry((frame_addr as u64) | flags.bits()) - } - - fn flags(&self) -> TableEntryFlags { - TableEntryFlags::from_bits_truncate(self.0) - } - - fn pointed_frame(&self) -> Frame { - Frame { number: ((self.0 & 0x000fffff_fffff000) >> 12) as usize } - } -} - -bitflags! { - flags TableEntryFlags: 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, - } -} diff --git a/src/memory/paging_new/table.rs b/src/memory/paging/table.rs similarity index 53% rename from src/memory/paging_new/table.rs rename to src/memory/paging/table.rs index cb4b3c49..dcbc4c80 100644 --- a/src/memory/paging_new/table.rs +++ b/src/memory/paging/table.rs @@ -1,32 +1,16 @@ -use super::{ENTRY_COUNT, Page}; -use super::entry::{Entry, PRESENT, HUGE_PAGE}; -use super::levels::{TableLevel, HierachicalLevel, Level4}; +use memory::FrameAllocator; +use memory::paging::{ENTRY_COUNT, Page}; +use memory::paging::entry::*; use core::ops::{Index, IndexMut}; use core::marker::PhantomData; -pub const P4: *const Table = 0xffffffff_fffff000 as *const _; +pub const P4: *mut Table = 0xffffffff_fffff000 as *mut _; pub struct Table { entries: [Entry; ENTRY_COUNT], _phantom: PhantomData, } -impl Index for Table where L: TableLevel -{ - type Output = Entry; - - fn index(&self, index: usize) -> &Entry { - &self.entries[index] - } -} - -impl IndexMut for Table where L: TableLevel -{ - fn index_mut(&mut self, index: usize) -> &mut Entry { - &mut self.entries[index] - } -} - impl Table where L: TableLevel { pub fn zero(&mut self) { @@ -46,6 +30,22 @@ impl Table where L: HierachicalLevel self.next_table_address(index).map(|t| unsafe { &mut *(t as *mut _) }) } + pub fn next_table_create(&mut self, + index: usize, + allocator: &mut A) + -> &mut Table + where A: FrameAllocator + { + if let None = self.next_table_address(index) { + 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() + } + fn next_table_address(&self, index: usize) -> Option { let entry_flags = self[index].flags(); if entry_flags.contains(PRESENT) && !entry_flags.contains(HUGE_PAGE) { @@ -60,3 +60,50 @@ impl Table where L: HierachicalLevel } } } + +impl Index for Table where L: TableLevel +{ + type Output = Entry; + + fn index(&self, index: usize) -> &Entry { + &self.entries[index] + } +} + +impl IndexMut for Table where L: TableLevel +{ + fn index_mut(&mut self, index: usize) -> &mut Entry { + &mut self.entries[index] + } +} + +pub trait TableLevel {} + +pub enum Level4 {} +#[allow(dead_code)] +enum Level3 {} +#[allow(dead_code)] +enum Level2 {} +#[allow(dead_code)] +enum Level1 {} + +impl TableLevel for Level4 {} +impl TableLevel for Level3 {} +impl TableLevel for Level2 {} +impl TableLevel for Level1 {} + +trait HierachicalLevel: TableLevel { + type NextLevel: TableLevel; +} + +impl HierachicalLevel for Level4 { + type NextLevel = Level3; +} + +impl HierachicalLevel for Level3 { + type NextLevel = Level2; +} + +impl HierachicalLevel for Level2 { + type NextLevel = Level1; +} diff --git a/src/memory/paging/tables.rs b/src/memory/paging/tables.rs deleted file mode 100644 index 2e554f5d..00000000 --- a/src/memory/paging/tables.rs +++ /dev/null @@ -1,256 +0,0 @@ -use core::marker::PhantomData; - -pub const fn P4(page: &Page) -> Table { - Table { - table_page: Page { number: 0o_777_777_777_777 }, - target_page_number: page.number, - _phantom: PhantomData, - } -} - - -pub fn translate(virtual_address: usize) -> Option { - let page = Page::containing_address(virtual_address); - let offset = virtual_address % PAGE_SIZE; - - let frame_number = { - let p3 = match P4(&page).next_table() { - None => return None, - Some(t) => t, - }; - - if p3.entry().flags().contains(PRESENT | HUGE_PAGE) { - // 1GiB page (address must be 1GiB aligned) - let start_frame_number = p3.entry().pointed_frame().number; - assert!(start_frame_number % (ENTRY_COUNT * ENTRY_COUNT) == 0); - start_frame_number + Table::::index_of(&page) * ENTRY_COUNT + - Table::::index_of(&page) - } else { - // 2MiB or 4KiB page - let p2 = match p3.next_table() { - None => return None, - Some(t) => t, - }; - - if p2.entry().flags().contains(PRESENT | HUGE_PAGE) { - // 2MiB page (address must be 2MiB aligned) - let start_frame_number = p2.entry().pointed_frame().number; - assert!(start_frame_number % ENTRY_COUNT == 0); - start_frame_number + Table::::index_of(&page) - } else { - // standard 4KiB page - let p1 = match p2.next_table() { - None => return None, - Some(t) => t, - }; - p1.entry().pointed_frame().number - } - } - }; - Some(frame_number * PAGE_SIZE + offset) -} - - -pub fn map_to(page: &Page, frame: Frame, flags: TableEntryFlags, allocator: &mut A) - where A: FrameAllocator -{ - let mut p3 = P4(page).next_table_create(allocator); - let mut p2 = p3.next_table_create(allocator); - let mut p1 = p2.next_table_create(allocator); - - assert!(!p1.entry().flags().contains(PRESENT)); - p1.set_entry(TableEntry::new(frame, flags)); -} - -trait TableLevel{ - fn level_number() -> usize; -} -pub enum Level1 {} -pub enum Level2 {} -pub enum Level3 {} -pub enum Level4 {} - -impl TableLevel for Level4 { - fn level_number() -> usize { - 4 - } -} -impl TableLevel for Level3 { - fn level_number() -> usize { - 3 - } -} -impl TableLevel for Level2 { - fn level_number() -> usize { - 2 - } -} -impl TableLevel for Level1 { - fn level_number() -> usize { - 1 - } -} - -trait HierachicalLevel: TableLevel { - type NextLevel: TableLevel; -} - -impl HierachicalLevel for Level4 { - type NextLevel = Level3; -} - -impl HierachicalLevel for Level3 { - type NextLevel = Level2; -} - -impl HierachicalLevel for Level2 { - type NextLevel = Level1; -} - -impl Table where L: TableLevel -{ - pub fn index_of(page: &Page) -> usize { - Self::index_of_page_number(page.number) - } - - fn index_of_page_number(page_number: usize) -> usize { - let s = (L::level_number() - 1) * 9; - (page_number >> s) & 0o777 - } - - fn index(&self) -> usize { - Self::index_of_page_number(self.target_page_number) - } -} - -use memory::{Frame, FrameAllocator}; - -pub const PAGE_SIZE: usize = 4096; -const ENTRY_SIZE: usize = 8; -const ENTRY_COUNT: usize = 512; - -pub type PhysicalAddress = usize; -pub type VirtualAddress = usize; - - -pub struct Page { - number: usize, -} - -impl Page { - fn containing_address(address: VirtualAddress) -> Page { - match address { - addr if addr < 0o_400_000_000_000_0000 => Page { number: addr / PAGE_SIZE }, - addr if addr >= 0o177777_400_000_000_000_0000 => { - Page { number: (address / PAGE_SIZE) & 0o_777_777_777_777 } - } - _ => panic!("invalid address: 0x{:x}", address), - } - } - - pub fn start_address(&self) -> VirtualAddress { - if self.number >= 0x800000000 { - // sign extension necessary - (self.number << 12) | 0xffff_000000000000 - } else { - self.number << 12 - } - } -} - -pub struct Table { - table_page: Page, - target_page_number: usize, - _phantom: PhantomData, -} - -impl Table where L: TableLevel -{ - fn entry(&self) -> TableEntry { - let entry_address = self.table_page.start_address() + self.index() * ENTRY_SIZE; - unsafe { *(entry_address as *const _) } - } - - fn set_entry(&mut self, value: TableEntry) { - let entry_address = self.table_page.start_address() + self.index() * ENTRY_SIZE; - unsafe { *(entry_address as *mut _) = value } - } - - fn zero(&mut self) { - let page = self.table_page.start_address() as *mut [TableEntry; ENTRY_COUNT]; - unsafe { *page = [TableEntry::unused(); ENTRY_COUNT] }; - } -} - -impl Table where L: HierachicalLevel -{ - fn next_table_internal(&self) -> Table { - Table { - table_page: Page { - number: ((self.table_page.number << 9) & 0o_777_777_777_777) | self.index(), - }, - target_page_number: self.target_page_number, - _phantom: PhantomData, - } - } - - fn next_table(&self) -> Option> { - if self.entry().flags().contains(PRESENT) { - Some(self.next_table_internal()) - } else { - None - } - } - - fn next_table_create(&mut self, allocator: &mut A) -> Table - where A: FrameAllocator - { - match self.next_table() { - Some(table) => table, - None => { - let frame = allocator.allocate_frame().expect("no frames available"); - self.set_entry(TableEntry::new(frame, PRESENT | WRITABLE)); - let mut next_table = self.next_table_internal(); - next_table.zero(); - next_table - } - } - } -} - -#[derive(Debug, Clone, Copy)] -struct TableEntry(u64); - -impl TableEntry { - const fn unused() -> TableEntry { - TableEntry(0) - } - - fn new(frame: Frame, flags: TableEntryFlags) -> TableEntry { - let frame_addr = (frame.number << 12) & 0x000fffff_fffff000; - TableEntry((frame_addr as u64) | flags.bits()) - } - - fn flags(&self) -> TableEntryFlags { - TableEntryFlags::from_bits_truncate(self.0) - } - - fn pointed_frame(&self) -> Frame { - Frame { number: ((self.0 & 0x000fffff_fffff000) >> 12) as usize } - } -} - -bitflags! { - flags TableEntryFlags: 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, - } -} diff --git a/src/memory/paging_new/translate.rs b/src/memory/paging/translate.rs similarity index 94% rename from src/memory/paging_new/translate.rs rename to src/memory/paging/translate.rs index d9b3ac32..28190aa7 100644 --- a/src/memory/paging_new/translate.rs +++ b/src/memory/paging/translate.rs @@ -1,10 +1,9 @@ use super::{VirtualAddress, PhysicalAddress, Page, PAGE_SIZE, ENTRY_COUNT}; -use super::table::{Table, P4}; +use super::table::P4; use super::entry::{PRESENT, HUGE_PAGE}; use memory::Frame; - -pub fn translate(virtual_address: usize) -> Option { +pub fn translate(virtual_address: VirtualAddress) -> Option { let page = Page::containing_address(virtual_address); let offset = virtual_address % PAGE_SIZE; diff --git a/src/memory/paging_new/levels.rs b/src/memory/paging_new/levels.rs deleted file mode 100644 index ca28b8d3..00000000 --- a/src/memory/paging_new/levels.rs +++ /dev/null @@ -1,27 +0,0 @@ -pub trait TableLevel {} - -pub struct Level4; -pub struct Level3; -pub struct Level2; -pub struct Level1; - -impl TableLevel for Level4 {} -impl TableLevel for Level3 {} -impl TableLevel for Level2 {} -impl TableLevel for Level1 {} - -pub trait HierachicalLevel: TableLevel { - type NextLevel: TableLevel; -} - -impl HierachicalLevel for Level4 { - type NextLevel = Level3; -} - -impl HierachicalLevel for Level3 { - type NextLevel = Level2; -} - -impl HierachicalLevel for Level2 { - type NextLevel = Level1; -} diff --git a/src/memory/paging_new/mod.rs b/src/memory/paging_new/mod.rs deleted file mode 100644 index 7685f86f..00000000 --- a/src/memory/paging_new/mod.rs +++ /dev/null @@ -1,49 +0,0 @@ -mod entry; -mod table; -mod levels; -mod translate; - -pub const PAGE_SIZE: usize = 4096; -const ENTRY_SIZE: usize = 8; -const ENTRY_COUNT: usize = 512; - -pub type PhysicalAddress = usize; -pub type VirtualAddress = usize; - -pub struct Page { - number: usize, -} - -impl Page { - fn containing_address(address: VirtualAddress) -> Page { - match address { - addr if addr < 0o_400_000_000_000_0000 => Page { number: addr / PAGE_SIZE }, - addr if addr >= 0o177777_400_000_000_000_0000 => { - Page { number: (address / PAGE_SIZE) & 0o_777_777_777_777 } - } - _ => panic!("invalid address: 0x{:x}", address), - } - } - - pub fn start_address(&self) -> VirtualAddress { - if self.number >= 0x800000000 { - // sign extension necessary - (self.number << 12) | 0xffff_000000000000 - } else { - self.number << 12 - } - } - - 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 - } -} From 44fd5f682de224f7a3c268f7fe89e6edd034246a Mon Sep 17 00:00:00 2001 From: Philipp Oppermann Date: Thu, 3 Dec 2015 21:20:38 +0100 Subject: [PATCH 19/76] Some new test printlns --- src/lib.rs | 29 ++++++++++++++++------------- 1 file changed, 16 insertions(+), 13 deletions(-) diff --git a/src/lib.rs b/src/lib.rs index 653c5350..2b5eee75 100644 --- a/src/lib.rs +++ b/src/lib.rs @@ -61,20 +61,23 @@ pub extern fn rust_main(multiboot_information_address: 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()); - for i in 0.. { - use memory::FrameAllocator; - if let None = frame_allocator.allocate_frame() { - println!("allocated {} frames", i); - break; - } - } - //println!("outer {}", {println!("inner"); "NO DEADLOCK"}); - println!("{:?}", memory::paging::translate(0)); - println!("{:?}", memory::paging::translate(12345)); - //for i in 0.. { - //println!("0o{:o}", memory::paging::translate1(0o_000_000_000_0000 + i << 21).unwrap()); - //} + // println!("outer {}", {println!("inner"); "NO DEADLOCK"}); + /*println!("{:?}", memory::paging::translate::translate(0));*/ + + println!("{:?}", memory::paging::translate::translate(0)); + println!("{:?}", memory::paging::translate::translate(0x40000000)); + println!("{:?}", memory::paging::translate::translate(0x40000000 - 1)); + println!("{:?}", memory::paging::translate::translate(0xdeadbeaa000)); + println!("{:?}", memory::paging::translate::translate(0xcafebeaf000)); + memory::paging::test(&mut frame_allocator); + println!("{:x}", memory::paging::translate::translate(0xdeadbeaa000).unwrap()); + println!("{:x}", memory::paging::translate::translate(0xdeadbeab000).unwrap()); + println!("{:x}", memory::paging::translate::translate(0xdeadbeac000).unwrap()); + println!("{:x}", memory::paging::translate::translate(0xdeadbead000).unwrap()); + println!("{:x}", memory::paging::translate::translate(0xcafebeaf000).unwrap()); + + loop{} } From 6c5b932fbcbc6ff9cb6fa4f2db901026f0d64785 Mon Sep 17 00:00:00 2001 From: Philipp Oppermann Date: Fri, 4 Dec 2015 11:50:42 +0100 Subject: [PATCH 20/76] Split translate into `translate_page` and rest --- src/memory/paging/translate.rs | 6 ++++-- 1 file changed, 4 insertions(+), 2 deletions(-) diff --git a/src/memory/paging/translate.rs b/src/memory/paging/translate.rs index 28190aa7..78bc84a2 100644 --- a/src/memory/paging/translate.rs +++ b/src/memory/paging/translate.rs @@ -4,9 +4,12 @@ use super::entry::{PRESENT, HUGE_PAGE}; use memory::Frame; pub fn translate(virtual_address: VirtualAddress) -> Option { - let page = Page::containing_address(virtual_address); let offset = virtual_address % PAGE_SIZE; + translate_page(Page::containing_address(virtual_address)) + .map(|frame| frame.number * PAGE_SIZE + offset) +} +fn translate_page(page: Page) -> Option { let p4 = unsafe { &*P4 }; let huge_page = || { @@ -38,5 +41,4 @@ pub fn translate(virtual_address: VirtualAddress) -> Option { .and_then(|p2| p2.next_table(page.p2_index())) .map(|p1| p1[page.p1_index()].pointed_frame()) .or_else(huge_page) - .map(|frame| frame.number * PAGE_SIZE + offset) } From 6535aa017dc72adfb2f6f84ff2198f0225c91ac0 Mon Sep 17 00:00:00 2001 From: Philipp Oppermann Date: Fri, 4 Dec 2015 11:52:44 +0100 Subject: [PATCH 21/76] Add lock module (name is temporary) --- src/memory/paging/lock.rs | 144 ++++++++++++++++++++++++++++++++++++++ src/memory/paging/mod.rs | 1 + 2 files changed, 145 insertions(+) create mode 100644 src/memory/paging/lock.rs diff --git a/src/memory/paging/lock.rs b/src/memory/paging/lock.rs new file mode 100644 index 00000000..f3fd5837 --- /dev/null +++ b/src/memory/paging/lock.rs @@ -0,0 +1,144 @@ +use memory::{Frame, FrameAllocator}; +use memory::paging::{VirtualAddress, PhysicalAddress, Page, PAGE_SIZE, ENTRY_COUNT}; +use memory::paging::table::{Table, P4, Level4}; +use memory::paging::entry::{EntryFlags, PRESENT, WRITABLE, HUGE_PAGE}; +use x86::tlb; + +pub struct PageTable { + p4_frame: Frame, // recursive mapped +} + +impl PageTable { + pub fn create_new_on_identity_mapped_frame(&self, identity_mapped_frame: Frame) -> PageTable { + let page_address = Page { number: identity_mapped_frame.number }.start_address(); + // frame must be identity mapped + assert!(self.read(|lock| lock.translate(page_address)) == Some(page_address)); + + let table = unsafe { &mut *(page_address as *mut Table) }; + table[511].set(Frame { number: identity_mapped_frame.number }, WRITABLE); + PageTable { p4_frame: identity_mapped_frame } + } + + pub fn read(&self, f: F) -> R + where F: FnOnce(&Lock) -> R + { + let p4_address = 0o177777_777_777_777_777_7770 as *mut usize; + let backup = unsafe { *p4_address }; + let ret; + if Frame::containing_address(backup) == self.p4_frame { + ret = f(&Lock { _private: () }); + } else { + unsafe { *p4_address = (self.p4_frame.number << 12) | 0b11 }; + ret = f(&Lock { _private: () }); + unsafe { *p4_address = backup }; + } + ret + } + + pub fn modify(&mut self, f: F) + where F: FnOnce(&mut Lock) + { + let p4_address = 0o177777_777_777_777_777_7770 as *mut usize; + let backup = unsafe { *p4_address }; + if Frame::containing_address(backup) == self.p4_frame { + f(&mut Lock { _private: () }); + } else { + unsafe { *p4_address = (self.p4_frame.number << 12) | 0b11 }; + f(&mut Lock { _private: () }); + unsafe { *p4_address = backup }; + } + } +} + +pub struct Lock { + _private: (), +} + +impl Lock { + pub fn translate(&self, virtual_address: VirtualAddress) -> Option { + 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 { + let p4 = unsafe { &*P4 }; + + let huge_page = || { + p4.next_table(page.p4_index()) + .and_then(|p3| { + // 1GiB page? + if p3[page.p3_index()].flags().contains(HUGE_PAGE | PRESENT) { + let start_frame_number = p3[page.p3_index()].pointed_frame().number; + // address must be 1GiB aligned + assert!(start_frame_number % (ENTRY_COUNT * ENTRY_COUNT) == 0); + return Some(start_frame_number + page.p2_index() * ENTRY_COUNT + + page.p1_index()); + } + if let Some(p2) = p3.next_table(page.p3_index()) { + // 2MiB page? + if p2[page.p2_index()].flags().contains(HUGE_PAGE | PRESENT) { + let start_frame_number = p2[page.p2_index()].pointed_frame().number; + // address must be 2MiB aligned + assert!(start_frame_number % ENTRY_COUNT == 0); + return Some(start_frame_number + page.p1_index()); + } + } + None + }) + .map(|start_frame_number| Frame { number: start_frame_number }) + }; + + p4.next_table(page.p4_index()) + .and_then(|p3| p3.next_table(page.p3_index())) + .and_then(|p2| p2.next_table(page.p2_index())) + .map(|p1| p1[page.p1_index()].pointed_frame()) + .or_else(huge_page) + } + + pub fn map(&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 map_to(&mut self, page: &Page, frame: Frame, flags: EntryFlags, allocator: &mut A) + where A: FrameAllocator + { + let p4 = unsafe { &mut *P4 }; + 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()].flags().contains(PRESENT)); + p1[page.p1_index()].set(frame, flags | PRESENT); + } + + pub fn identity_map(&mut self, frame: Frame, flags: EntryFlags, allocator: &mut A) + where A: FrameAllocator + { + let page = Page { number: frame.number }; + self.map_to(&page, frame, flags, allocator) + } + + + fn unmap(&mut self, page: &Page, allocator: &mut A) + where A: FrameAllocator + { + assert!(self.translate(page.start_address()).is_some()); + + let p4 = unsafe { &mut *P4 }; + let p1 = p4.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())) + .unwrap(); + + assert!(!p1[page.p1_index()].flags().contains(PRESENT)); + let frame = p1[page.p1_index()].pointed_frame(); + p1[page.p1_index()].set_unused(); + unsafe { tlb::flush(page.start_address()) }; + // TODO free p(1,2,3) table if empty + allocator.deallocate_frame(frame); + } +} diff --git a/src/memory/paging/mod.rs b/src/memory/paging/mod.rs index 5130b982..ce40a3d7 100644 --- a/src/memory/paging/mod.rs +++ b/src/memory/paging/mod.rs @@ -2,6 +2,7 @@ mod entry; mod table; pub mod translate; pub mod mapping; +pub mod lock; pub fn test(frame_allocator: &mut A) where A: super::FrameAllocator From 2d9b6195875596b2a0f6f89da068a10975b15808 Mon Sep 17 00:00:00 2001 From: Philipp Oppermann Date: Fri, 4 Dec 2015 11:53:08 +0100 Subject: [PATCH 22/76] Begin updating post --- posts/DRAFT-paging.md | 6 ------ 1 file changed, 6 deletions(-) diff --git a/posts/DRAFT-paging.md b/posts/DRAFT-paging.md index 10c5c521..8c1599ca 100644 --- a/posts/DRAFT-paging.md +++ b/posts/DRAFT-paging.md @@ -10,7 +10,6 @@ Let's begin a `memory/paging/mod.rs` module to model page tables: ```rust pub const PAGE_SIZE: usize = 4096; -const ENTRY_SIZE: usize = 8; const ENTRY_COUNT: usize = 512; pub type PhysicalAddress = usize; @@ -19,11 +18,6 @@ pub type VirtualAddress = usize; pub struct Page { number: usize, } - -struct Table(Page); - -#[derive(Debug, Clone, Copy)] -struct TableEntry(u64); ``` We define constants for the page size, the size of an entry in a page table, and the number of entries per table. To make future function signatures more expressive, we can use the type aliases `PhysicalAddress` and `VirtualAddress`. The `Page` struct is similar to the `Frame` struct in the [previous post], but represents a virtual page instead of a physical frame. From 7e5da6c8973ce1f0608853c0af78abd8ac85a325 Mon Sep 17 00:00:00 2001 From: Philipp Oppermann Date: Fri, 4 Dec 2015 13:11:18 +0100 Subject: [PATCH 23/76] Modify `pointed_frame` to return an Option --- src/memory/paging/entry.rs | 8 ++++++-- src/memory/paging/lock.rs | 13 +++++++------ src/memory/paging/translate.rs | 6 +++--- 3 files changed, 16 insertions(+), 11 deletions(-) diff --git a/src/memory/paging/entry.rs b/src/memory/paging/entry.rs index 585d06a6..7c739e07 100644 --- a/src/memory/paging/entry.rs +++ b/src/memory/paging/entry.rs @@ -15,8 +15,12 @@ impl Entry { EntryFlags::from_bits_truncate(self.0) } - pub fn pointed_frame(&self) -> Frame { - Frame { number: ((self.0 & 0x000fffff_fffff000) >> 12) as usize } + pub fn pointed_frame(&self) -> Option { + if self.flags().contains(PRESENT) { + Some(Frame { number: ((self.0 & 0x000fffff_fffff000) >> 12) as usize }) + } else { + None + } } pub fn set(&mut self, frame: Frame, flags: EntryFlags) { diff --git a/src/memory/paging/lock.rs b/src/memory/paging/lock.rs index f3fd5837..9a3b6e96 100644 --- a/src/memory/paging/lock.rs +++ b/src/memory/paging/lock.rs @@ -69,7 +69,7 @@ impl Lock { .and_then(|p3| { // 1GiB page? if p3[page.p3_index()].flags().contains(HUGE_PAGE | PRESENT) { - let start_frame_number = p3[page.p3_index()].pointed_frame().number; + let start_frame_number = p3[page.p3_index()].pointed_frame().unwrap().number; // address must be 1GiB aligned assert!(start_frame_number % (ENTRY_COUNT * ENTRY_COUNT) == 0); return Some(start_frame_number + page.p2_index() * ENTRY_COUNT + @@ -78,7 +78,10 @@ impl Lock { if let Some(p2) = p3.next_table(page.p3_index()) { // 2MiB page? if p2[page.p2_index()].flags().contains(HUGE_PAGE | PRESENT) { - let start_frame_number = p2[page.p2_index()].pointed_frame().number; + let start_frame_number = p2[page.p2_index()] + .pointed_frame() + .unwrap() + .number; // address must be 2MiB aligned assert!(start_frame_number % ENTRY_COUNT == 0); return Some(start_frame_number + page.p1_index()); @@ -92,7 +95,7 @@ impl Lock { p4.next_table(page.p4_index()) .and_then(|p3| p3.next_table(page.p3_index())) .and_then(|p2| p2.next_table(page.p2_index())) - .map(|p1| p1[page.p1_index()].pointed_frame()) + .and_then(|p1| p1[page.p1_index()].pointed_frame()) .or_else(huge_page) } @@ -133,9 +136,7 @@ impl Lock { .and_then(|p3| p3.next_table_mut(page.p3_index())) .and_then(|p2| p2.next_table_mut(page.p2_index())) .unwrap(); - - assert!(!p1[page.p1_index()].flags().contains(PRESENT)); - let frame = p1[page.p1_index()].pointed_frame(); + let frame = p1[page.p1_index()].pointed_frame().unwrap(); p1[page.p1_index()].set_unused(); unsafe { tlb::flush(page.start_address()) }; // TODO free p(1,2,3) table if empty diff --git a/src/memory/paging/translate.rs b/src/memory/paging/translate.rs index 78bc84a2..97d8b58d 100644 --- a/src/memory/paging/translate.rs +++ b/src/memory/paging/translate.rs @@ -17,7 +17,7 @@ fn translate_page(page: Page) -> Option { .and_then(|p3| { // 1GiB page? if p3[page.p3_index()].flags().contains(HUGE_PAGE | PRESENT) { - let start_frame_number = p3[page.p3_index()].pointed_frame().number; + let start_frame_number = p3[page.p3_index()].pointed_frame().unwrap().number; // address must be 1GiB aligned assert!(start_frame_number % (ENTRY_COUNT * ENTRY_COUNT) == 0); return Some(start_frame_number + page.p2_index() * ENTRY_COUNT + page.p1_index()); @@ -25,7 +25,7 @@ fn translate_page(page: Page) -> Option { if let Some(p2) = p3.next_table(page.p3_index()) { // 2MiB page? if p2[page.p2_index()].flags().contains(HUGE_PAGE | PRESENT) { - let start_frame_number = p2[page.p2_index()].pointed_frame().number; + let start_frame_number = p2[page.p2_index()].pointed_frame().unwrap().number; // address must be 2MiB aligned assert!(start_frame_number % ENTRY_COUNT == 0); return Some(start_frame_number + page.p1_index()); @@ -39,6 +39,6 @@ fn translate_page(page: Page) -> Option { p4.next_table(page.p4_index()) .and_then(|p3| p3.next_table(page.p3_index())) .and_then(|p2| p2.next_table(page.p2_index())) - .map(|p1| p1[page.p1_index()].pointed_frame()) + .and_then(|p1| p1[page.p1_index()].pointed_frame()) .or_else(huge_page) } From 51f83717d68052f1b2678949cc9a34718ae9e2c7 Mon Sep 17 00:00:00 2001 From: Philipp Oppermann Date: Fri, 4 Dec 2015 13:15:37 +0100 Subject: [PATCH 24/76] Use Frame::containing_address --- src/memory/paging/entry.rs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/memory/paging/entry.rs b/src/memory/paging/entry.rs index 7c739e07..b6cc8488 100644 --- a/src/memory/paging/entry.rs +++ b/src/memory/paging/entry.rs @@ -17,7 +17,7 @@ impl Entry { pub fn pointed_frame(&self) -> Option { if self.flags().contains(PRESENT) { - Some(Frame { number: ((self.0 & 0x000fffff_fffff000) >> 12) as usize }) + Some(Frame::containing_address(self.0 as usize & 0x000fffff_fffff000)) } else { None } From 799067f8b1ec52525be9ef5ca3b011c9733f7eaa Mon Sep 17 00:00:00 2001 From: Philipp Oppermann Date: Fri, 4 Dec 2015 13:35:40 +0100 Subject: [PATCH 25/76] Add `Frame::start_address` --- src/memory/paging/entry.rs | 11 +++++++++-- 1 file changed, 9 insertions(+), 2 deletions(-) diff --git a/src/memory/paging/entry.rs b/src/memory/paging/entry.rs index b6cc8488..e08ba9a5 100644 --- a/src/memory/paging/entry.rs +++ b/src/memory/paging/entry.rs @@ -1,4 +1,5 @@ use memory::Frame; +use memory::paging::PhysicalAddress; pub struct Entry(u64); @@ -24,8 +25,8 @@ impl Entry { } pub fn set(&mut self, frame: Frame, flags: EntryFlags) { - let frame_addr = (frame.number << 12) & 0x000fffff_fffff000; - self.0 = (frame_addr as u64) | flags.bits(); + assert!(frame.start_address() & !0x000fffff_fffff000 == 0); + self.0 = (frame.start_address() as u64) | flags.bits(); } } @@ -43,3 +44,9 @@ bitflags! { const NO_EXECUTE = 1 << 63, } } + +impl Frame { + fn start_address(&self) -> PhysicalAddress { + self.number << 12 + } +} From 4a54a24145a9899219026b4bea3d933278b6b8fc Mon Sep 17 00:00:00 2001 From: Philipp Oppermann Date: Fri, 4 Dec 2015 13:38:41 +0100 Subject: [PATCH 26/76] Reorganize imports --- src/memory/paging/table.rs | 4 +++- 1 file changed, 3 insertions(+), 1 deletion(-) diff --git a/src/memory/paging/table.rs b/src/memory/paging/table.rs index dcbc4c80..d374bbd6 100644 --- a/src/memory/paging/table.rs +++ b/src/memory/paging/table.rs @@ -1,5 +1,5 @@ use memory::FrameAllocator; -use memory::paging::{ENTRY_COUNT, Page}; +use memory::paging::ENTRY_COUNT; use memory::paging::entry::*; use core::ops::{Index, IndexMut}; use core::marker::PhantomData; @@ -47,6 +47,8 @@ impl Table where L: HierachicalLevel } fn next_table_address(&self, index: usize) -> Option { + use memory::paging::Page; + let entry_flags = self[index].flags(); if entry_flags.contains(PRESENT) && !entry_flags.contains(HUGE_PAGE) { let table_page = Page::containing_address(self as *const _ as usize); From e88de41914aaf5743722c39e50721a6cf58473d7 Mon Sep 17 00:00:00 2001 From: Philipp Oppermann Date: Fri, 4 Dec 2015 16:24:23 +0100 Subject: [PATCH 27/76] Calculate next_table_address through directly through addresses --- src/memory/paging/table.rs | 10 ++-------- 1 file changed, 2 insertions(+), 8 deletions(-) diff --git a/src/memory/paging/table.rs b/src/memory/paging/table.rs index d374bbd6..ecb4bc3f 100644 --- a/src/memory/paging/table.rs +++ b/src/memory/paging/table.rs @@ -47,16 +47,10 @@ impl Table where L: HierachicalLevel } fn next_table_address(&self, index: usize) -> Option { - use memory::paging::Page; - let entry_flags = self[index].flags(); if entry_flags.contains(PRESENT) && !entry_flags.contains(HUGE_PAGE) { - let table_page = Page::containing_address(self as *const _ as usize); - assert!(table_page.number >= 0o_777_000_000_000); - let next_table_page = Page { - number: ((table_page.number << 9) & 0o_777_777_777_777) | index, - }; - Some(next_table_page.start_address()) + let table_address = self as *const _ as usize; + Some((table_address << 9) | (index << 12)) } else { None } From 7005cd7819b731239a5171a98a04e6e31c2f5ef4 Mon Sep 17 00:00:00 2001 From: Philipp Oppermann Date: Fri, 4 Dec 2015 16:24:56 +0100 Subject: [PATCH 28/76] Start rewriting post for new design --- posts/DRAFT-paging.md | 423 ++++++++++++++++++++++++++---------------- 1 file changed, 267 insertions(+), 156 deletions(-) diff --git a/posts/DRAFT-paging.md b/posts/DRAFT-paging.md index 8c1599ca..3a1fd0f4 100644 --- a/posts/DRAFT-paging.md +++ b/posts/DRAFT-paging.md @@ -5,8 +5,8 @@ title: 'A Paging Module' ## Paging -## Modeling Page Tables -Let's begin a `memory/paging/mod.rs` module to model page tables: +## A Basic Paging Module +Let's create a basic `memory/paging/mod.rs` module: ```rust pub const PAGE_SIZE: usize = 4096; @@ -19,24 +19,279 @@ pub struct Page { number: usize, } ``` -We define constants for the page size, the size of an entry in a page table, and the number of entries per table. To make future function signatures more expressive, we can use the type aliases `PhysicalAddress` and `VirtualAddress`. The `Page` struct is similar to the `Frame` struct in the [previous post], but represents a virtual page instead of a physical frame. +We define constants for the page size and the number of entries per table. To make future function signatures more expressive, we can use the type aliases `PhysicalAddress` and `VirtualAddress`. The `Page` struct is similar to the `Frame` struct in the [previous post], but represents a virtual page instead of a physical frame. [previous post]: {{ page.previous.url }} -The `Table` struct represents a P4, P3, P2, or P1 table. It's a newtype wrapper around the `Page` that contains the table. And the `TableEntry` type represents an 8 byte large page table entry. - -To get the i-th entry of a `Table`, we add a `entry()` method: +### Page Table Entries +To model page table entries, we create a new `entry` submodule: ```rust -fn entry(&self, index: usize) -> TableEntry { - assert!(index < ENTRY_COUNT); - let entry_address = self.0.start_address() + index * ENTRY_SIZE; - unsafe { *(entry_address as *const _) } +use memory::Frame; // needed later + +pub struct Entry(u64); + +impl Entry { + pub fn unused(&self) -> bool { + self.0 == 0 + } + + pub fn set_unused(&mut self) { + self.0 = 0; + } } ``` -The `start_address` function is covered below. We're doing manual pointer arithmetic in this function and need an `unsafe` block to convince Rust that there's a valid `TableEntry` at the given address. For this to be safe, we need to make sure that we only construct valid `Table` structs in the future. +We define that an unused entry is completely 0. That allows us to distinguish unused entries from other non-present entries in the future. For example, we could define one of the available bits as the `swapped_out` bit for pages that are swapped to disk. -TODO formulierung for this to be safe +Next we will model the contained physical address and the various flags. Remember, entries have the following format: + +Bit(s) | Name | Meaning +--------------------- | ------ | ---------------------------------- +0 | present | the page is currently in memory +1 | writable | it's allowed to write to this page +2 | user accessible | if not set, only kernel mode code can access this page +3 | write through caching | writes go directly to memory +4 | disable cache | no cache is used for this page +5 | accessed | the CPU sets this bit when this page is used +6 | dirty | the CPU sets this bit when a write to this page occurs +7 | huge page/null | must be 0 in P1 and P4, creates a 1GiB page in P3, creates a 2MiB page in P2 +8 | global | page isn't flushed from caches on address space switch (PGE bit of CR4 register must be set) +9-11 | available | can be used freely by the OS +12-51 | physical address | the page aligned 52bit physical address of the frame or the next page table +52-62 | available | can be used freely by the OS +63 | no execute | forbid executing code on this page (the NXE bit in the EFER register must be set) + +To model the various flags, we will use the [bitflags] crate. Unfortunately the official version depends on the standard library as `no_std` is still unstable. But since it does not actually require any `std` functions, it's pretty easy to create a `no_std` version. You can find it here [here][bitflags fork]. To add it as a dependency, add the following to your `Cargo.toml`: + +[bitflags]: https://github.com/rust-lang-nursery/bitflags +[bitflags fork]: https://github.com/phil-opp/bitflags/tree/no_std + +```toml +[dependencies.bitflags] +git = "https://github.com/phil-opp/bitflags.git" +branch = "no_std" +``` +Note that you need a `#[macro_use]` above the `extern crate` definition. + +Now we can model the various flags: + +```rust +bitflags! { + 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, + } +} +``` +To extract the flags from the entry we create a `TableEntryFlags::flags` method that uses [from_bits_truncate]: + +[from_bits_truncate]: https://doc.rust-lang.org/bitflags/bitflags/macro.bitflags!.html#methods + +```rust +pub fn flags(&self) -> EntryFlags { + EntryFlags::from_bits_truncate(self.0) +} +``` +This allows us to check for flags through the `contains()` function. For example, `flags().contains(PRESENT | WRITABLE)` returns true if the entry contains _both_ flags. + +To extract the physical address, we add a `pointed_frame` method: + +```rust +pub fn pointed_frame(&self) -> Option { + if self.flags().contains(PRESENT) { + Some(Frame::containing_address(self.0 as usize & 0x000fffff_fffff000)) + } else { + None + } +} +``` +If the entry is present, we mask bits 12–51 and return the corresponding frame. If the entry is not present, it does not point to a valid frame so we return `None`. + +To modify entries, we add a `set` method that updates the flags and the pointed frame: + +```rust +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(); +} +``` +The start address of a frame should be page aligned and smaller than 2^52 (since x86 uses 52bit physical addresses). Since an invalid address could mess up the entry, we add an assertion. To actually set the entry, we just need to `or` the start address and the flag bits. + +The missing `start_address` function is pretty simple: + +```rust +use memory::paging::PhysicalAddress; + +impl Frame { + fn start_address(&self) -> PhysicalAddress { + self.number << 12 + } +} +``` +Since we only need it in the entry submodule, we put it in a new `impl Frame` block in `entry.rs`. + +## Page Tables +To model page tables, we create a basic `Table` struct in a new `table` submodule: + +```rust +use memory::paging::entry::*; +use memory::paging::ENTRY_COUNT; + +pub struct Table { + entries: [Entry; ENTRY_COUNT], +} +``` +It's just an array of 512 page table entries. + +To make the `Table` indexable itself, we can implement the `Index` and `IndexMut` traits: + +```rust +use core::ops::{Index, IndexMut}; + +impl Index for Table { + type Output = Entry; + + fn index(&self, index: usize) -> &Entry { + &self.entries[index] + } +} + +impl IndexMut for Table { + fn index_mut(&mut self, index: usize) -> &mut Entry { + &mut self.entries[index] + } +} +``` +Now it's possible to get the 42th entry through `some_table[42]`. Of course we could replace `usize` with `u32` or even `u16` here but it would cause more numerical conversions (`x as u16`). + +Let's add a method that sets all entries to unused. We will need it when we create new page tables in the future. The method looks like this: + +```rust +pub fn zero(&mut self) { + for entry in self.entries.iter_mut() { + entry.set_unused(); + } +} +``` + +Now we can read page tables and retrieve the mapping information. We can also update them through the `IndexMut` trait and the `Entry::set` method. But how do we get references to the various page tables? + +We could read the `CR3` register to get the physical address of the P4 table and read its entries to get the P3 addresses. The P3 entries then point to the P2 tables and so on. But this method only works for identity-mapped pages. But in the future we will create new page tables, which aren't in the identity-mapped area anymore. Since we can't access them through their physical address, we need a way to map them to virtual addresses. + +## Mapping Page Tables +So how do we map the page tables itself? We don't have that problem for the current P4, P3, and P2 table since they are part of the identity-mapped area, but we need a way to access future tables, too. + +One solution is to identity map all page table. That way we would not need to differentiate virtual and physical address and could easily access the tables. But it clutters the virtual address space and increases fragmentation. And it makes creating page tables much more complicated since we need a physical frame whose corresponding page isn't already used for something else. + +An alternative solution is to map the page tables only temporary. So to read/write a page table, we would map it to some free virtual address. We could use a small pool of such virtual addresses and reuse them for various tables. This method occupies only few virtual addresses and is thus a good solution for 32-bit systems, which have small address spaces. But it makes things much more complicated since the temporary mapping requires updating other page tables, which need to be mapped, too. + +We will use another solution, which uses a trick called _recursive mapping_. + +### Recursive Mapping +The trick is to map the P4 table recursively: The last entry doesn't point to a P3 table, but to the P4 table itself. Through this entry, all page tables are mapped to an unique virtual address. + +TODO image + +To access for example the P4 table itself, we use the address that chooses the 511th P4 entry, the 511th P3 entry, the 511th P2 entry and the 511th P1 entry. Thus we choose the same P4 frame over and over again and finally end up on it, too. Through the offset (12 bits) we choose the desired entry. + +To access a P3 table, we do the same but choose the real P4 index instead of the fourth loop. So if we like to access the 42th P3 table, we use the address that chooses the 511th entry in the P4, P3, and P2 table, but the 42th P1 entry. + +When accessing a P2 table, we only loop two times and then choose entries that correspond to the P4 and P3 table of the desired P2 table. And accessing a P1 table just loops once and then uses the corresponding P4, P3, and P2 entries. + +So we can access and modify page tables of all levels by just setting one P4 entry once. It may seem a bit strange at first, but is a very clean and simple solution once you wrapped your head around it. + +The math checks out, too. If all page tables are used, there is 1 P4 table, 511 P3 tables (the last entry is used for the recursive mapping), `511*512` P2 tables, and `511*512*512` P1 tables. So there are `134217728` page tables altogether. Each page table occupies 4KiB, so we need `134217728 * 4KiB = 512GiB` to store them. That's exactly the amount of memory that can be accessed through one P4 entry since `4KiB per page * 512 P1 entries * 512 P2 entries * 512 P3 entries = 512GiB`. + +### Implementation +To map the P4 table recursively, we just need to point the 511th entry to the table itself. Of course we could do it in Rust, but it would require some unsafe pointer fiddling. It's easier to just add some lines to our boot assembly: + +```nasm +mov eax, p4_table +or eax, 0b11 ; present + writable +mov [p4_table + 511 * 8], eax +``` +I put it right after the `setup_page_tables` label, but you can add it wherever you like. + +### The special addresses +Now we can use special virtual addresses to access the page tables. The P4 table is available at `0xfffffffffffff000`. Let's add a P4 constant to the `table` submodule: + +```rust +pub const P4: *mut Table = 0xffffffff_fffff000 as *mut _; +``` + +Let's switch to the octal system, since it makes more sense for the other special addresses. The P4 address from above is equivalent to `0o177777_777_777_777_777_0000` in octal. You can see that is has index `777` in all tables and offset `0000`. The `177777` bits on the left are the sign extension bits, which are copies of the 47th bit. They are required because x86 only uses 48bit virtual addresses. + +The other tables can be accessed through the following addresses: + +Table | Address | Indexes +----- | ------------------------------- | ---------------------------------- +P4 | `0o177777_777_777_777_777_0000` | – +P3 | `0o177777_777_777_777_XXX_0000` | `XXX` is the P4 index +P2 | `0o177777_777_777_XXX_YYY_0000` | like above, and `YYY` is the P3 index +P1 | `0o177777_777_XXX_YYY_ZZZ_0000` | like above, and `ZZZ` is the P2 index + +If we look closely, we can see that the P3 address is equal to `(P4 << 9) | XXX_0000`. And the P2 address is calculated through `(P3 << 9) | YYY_0000`. So to get the next address, we need to shift it 9 bits to the left and add the table index. As a formula: + +``` +next_table_address = (table_address << 9) | (index << 12) +``` + +So let's add it as a `Table` method: + +```rust +fn next_table_address(&self, index: usize) -> Option { + 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 + } +} +``` +The next table address is only valid if the corresponding entry is present and does not create a huge page. Then we can do some pointer casting to get the table address and use the formula to calculate the next address. + +If the index is out of bounds, the function will panic since Rust checks array bounds. The panic is desired here since a wrong index should not be possible and indicates a bug. + +To convert the address into references, we add two functions: + +```rust +pub fn next_table(&self, index: usize) -> Option<&Table> { + self.next_table_address(index) + .map(|t| unsafe { &*(t as *const _) }) +} + +pub fn next_table_mut(&mut self, index: usize) -> Option<&mut Table> { + self.next_table_address(index) + .map(|t| unsafe { &mut *(t as *mut _) }) +} +``` +We convert the address into raw pointers and then convert them into references in `unsafe` blocks. Now we can start at the `P4` constant and use these functions to access the lower tables. And we don't even need `unsafe` blocks to do it! + +Right now, your alarm bells should be ringing. Thanks to Rust, everything we've done before in this post was completely safe. But we just introduced two unsafe blocks to convince Rust that there are valid tables at the specified addresses. Can we really be sure? + +First, these addresses are only valid if the P4 table is mapped recursively. Since the paging module will be the only module that modifies page tables, we can introduce an invariant for the module: + +> _The 511th entry of the active P4 table must always be mapped to the active P4 table itself._ + +So if we switch to another P4 table at some time, it needs to be identity mapped _before_ it becomes active. As long as we obey this invariant, we can safely use the special addresses. But even with this invariant, there is a big problem with the two methods: + +_What happens if we call them on a P1 table?_ + +Well, they would calculate the address of the next table (which does not exist) and treat it as a page table. Either they construct an invalid address (if `XXX < 400`) or access the mapped page itself. That way, we could easily corrupt memory or cause CPU exceptions by accident. So these two functions are not _safe_ in Rust terms. Thus we need to make them `unsafe` functions if we don't find some clever solution. + +## Some Clever Solution +We can use Rust's type system to statically guarantee that the methods can only be called on P4, P3, and P2 tables. + +# OLD ### Sign Extension The `Page::start_address` method doesn't exist yet. But it should be a simple `page.number * PAGE_SIZE`, right? Well, if the x86_64 architecture had true 64bit addresses, yes. But in reality the addresses are just 48bit long and the other bits are just _sign extension_, i.e. a copy of the most significant bit. That means that the address calculated by `page.number * PAGE_SIZE` is wrong if the 47th bit is used. Some examples: @@ -62,150 +317,6 @@ pub fn start_address(&self) -> VirtualAddress { ``` The `0x800000000` is the start address of the higher half without the last four 0s (because it's a page _number_). -### Table entries -Now we can get a `TableEntry` through the `entry` function. Now we need to extract the relevant information. - -Remember, a page table entry looks like this: - -Bit(s) | Name | Meaning ---------------------- | ------ | ---------------------------------- -0 | present | the page is currently in memory -1 | writable | it's allowed to write to this page -2 | user accessible | if not set, only kernel mode code can access this page -3 | write through caching | writes go directly to memory -4 | disable cache | no cache is used for this page -5 | accessed | the CPU sets this bit when this page is used -6 | dirty | the CPU sets this bit when a write to this page occurs -7 | huge page/null | must be 0 in P1 and P4, creates a 1GiB page in P3, creates a 2MiB page in P2 -8 | global | page isn't flushed from caches on address space switch (PGE bit of CR4 register must be set) -9-11 | available | can be used freely by the OS -12-51 | physical address | the page aligned 52bit physical address of the frame or the next page table -52-62 | available | can be used freely by the OS -63 | no execute | forbid executing code on this page (the NXE bit in the EFER register must be set) - -To extract the physical address we add a `TableEntry::pointed_frame` method: - -```rust -fn pointed_frame(&self) -> Frame { - Frame { number: ((self.0 & 0x000fffff_fffff000) >> 12) as usize } -} -``` -First we mask bits 12-51 and then convert the physical address to the corresponding frame number (through `>> 12`). We don't need to respect any sign extension here since it only exists for virtual addresses. - -To model the various flags, we will use the [bitflags] crate. Unfortunately the official version depends on the standard library as `no_std` is still unstable. But since it does not actually require any `std` functions, it's pretty easy to create a `no_std` version. You can find it here [here][bitflags fork]. To add it as a dependency, add the following to your `Cargo.toml`: - -[bitflags]: /TODO -[bitflags fork]: /TODO - -```toml -[dependencies.bitflags] -git = "https://github.com/phil-opp/bitflags.git" -branch = "no_std" -``` -Note that you need a `#[macro_use]` above the `extern crate` definition. - -Now we can model the various flags: - -```rust -bitflags! { - flags TableEntryFlags: 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, - } -} -``` -To extract the flags we create a `TableEntryFlags::flags` method that uses [from_bits_truncate]: - -[from_bits_truncate]: /TODO - -```rust -fn flags(&self) -> TableEntryFlags { - TableEntryFlags::from_bits_truncate(self.0) -} -``` - -Now we can read page tables and retrieve the mapping information. But since we can't access page tables through their physical address, we need to map them to some virtual address, too. - -## Mapping Page Tables -So how do we map the page tables itself? We don't have that problem for the current P4, P3, and P2 table since they are part of the identity-mapped area, but we need a way to access future tables, too. - -One solution could be to identity map all page table. That way we would not need to differentiate virtual and physical address and could easily access the tables. But it makes creating page tables more complicated since we need a physical frame whose corresponding page isn't already used for something else. And it clutters the virtual address space and makes it impossible to map page tables in address spaces of user processes (since we can't just occupy some random pages there). - -An alternative solution is to map the page tables only temporary. So to read/write a page table, we would map it to some free virtual address. We could use a small pool of such virtual addresses and reuse them for various tables. This method occupies only few virtual addresses and is thus a good solution for 32-bit systems, which have small address spaces. But it makes things much more complicated since the temporary mapping requires updating other page tables, which need to be mapped, too. So we need to make sure that the temporary addresses are always mapped, else it could cause an endless recursion. - -We will use another solution, which uses a trick called _recursive mapping_. - -### Recursive Mapping -The trick is to map the `P4` table recursively: The last entry doesn't point to a `P3` table, but to the `P4` table itself. Through this entry, all page tables are mapped to an unique virtual address. So we can access and modify page tables of all levels by just setting one `P4` entry once. It may seem a bit strange at first, but is a very clean and simple solution once you wrapped your head around it. - -TODO image - -To access for example the `P4` table itself, we use the address that chooses the 511th `P4` entry, the 511th `P3` entry, the 511th `P2` entry and the 511th `P1` entry. Thus we choose the same `P4` frame over and over again and finally end up on it, too. Through the offset (12 bits) we choose the desired entry. - -To access a `P3` table, we do the same but choose the real `P4` index instead of the fourth loop. So if we like to access the 42th `P3` table, we use the address that chooses the 511th entry in the `P4`, `P3`, and `P2` table, but the 42th `P1` entry. - -When accessing a `P2` table, we only loop two times and then choose entries that correspond to the `P4` and `P3` table of the desired `P2` table. And accessing a `P1` table just loops once and then uses the corresponding `P4`, `P3`, and `P2` entries. - -The math checks out, too. If all page tables are used, there is 1 `P4` table, 511 `P3` tables (the last entry is used for the recursive mapping), `511*512` `P2` tables, and `511*512*512` `P1` tables. So there are `134217728` page tables altogether. Each page table occupies 4KiB, so we need `134217728 * 4KiB = 512GiB` to store them. That's exactly the amount of memory that can be accessed through one `P4` entry since `4KiB per page * 512 P1 entries * 512 P2 entries * 512 P3 entries = 512GiB`. - -### Implementation -To map the `P4` table recursively, we just need to point the 511th entry to the table itself. Of course we could do it in Rust, but it would require some way of retrieving the physical address of the `P4`. It's easier to just some lines to our assembly: - -```nasm -mov eax, p4_table -or eax, 0b11 ; present + writable -mov [p4_table + 511 * 8], eax -``` -I put it right after the `setup_page_tables` label, but you can add it wherever you like. - -### The special addresses -Now we can use special virtual addresses to access the page tables. For example, the `P4` table is available at `0xfffffffffffff000`. So let's add some methods to the `Page` type to get the corresponding page tables: - -```rust -const fn p4_table(&self) -> Table { - Table(Page { number: 0o_777_777_777_777 }) -} - -unsafe fn p3_table(&self) -> Table { - Table(Page { number: 0o_777_777_777_000 | self.p4_index() }) -} - -unsafe fn p2_table(&self) -> Table { - Table(Page { - number: 0o_777_777_000_000 | (self.p4_index() << 9) | - self.p3_index(), - }) -} - -unsafe fn p1_table(&self) -> Table { - Table(Page { - number: 0o_777_000_000_000 | (self.p4_index() << 18) | - (self.p3_index() << 9) | self.p2_index(), - }) -} -``` -We use the octal numbers since they make it easy to express the 9 bit table indexes. The `p*_index` methods are described below. - -The P4 table is the same for all addresses, so we can make the function `const`. The associated page has index 511 in all four pages, thus the four `777` blocks. The P3 table, however, is different for different P4 indexes. So the last block varies from `000` to `776`, dependent on the page's P4 index. The P2 table additionally depends on the P3 index and to get the P1 table we use the recursive mapping only once (thus only one `777` block). - -Since the P3, P2, and P1 tables may not exist, the corresponding functions are marked `unsafe`. It's only safe to call them if the entry in the parent table is `PRESENT` and does not have the `HUGE_PAGE` bit set. Else we would interpret random memory as a page table and get wrong results. - -The `p*_index` methods look like this: - -```rust -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 } -``` ## Translating addresses Now we can use the recursive mapping to translate virtual address manually. We will create a function that takes a virtual address and returns the corresponding physical address: From b1a2c8caadd2c852f460076f4ecca0e26f419e88 Mon Sep 17 00:00:00 2001 From: Philipp Oppermann Date: Fri, 4 Dec 2015 19:41:00 +0100 Subject: [PATCH 29/76] Rename PhantomData field --- src/memory/paging/table.rs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/memory/paging/table.rs b/src/memory/paging/table.rs index ecb4bc3f..915953df 100644 --- a/src/memory/paging/table.rs +++ b/src/memory/paging/table.rs @@ -8,7 +8,7 @@ pub const P4: *mut Table = 0xffffffff_fffff000 as *mut _; pub struct Table { entries: [Entry; ENTRY_COUNT], - _phantom: PhantomData, + level: PhantomData, } impl Table where L: TableLevel From bd05aa7715db092630a8b70ac1a9b96dc60a720a Mon Sep 17 00:00:00 2001 From: Philipp Oppermann Date: Fri, 4 Dec 2015 19:41:23 +0100 Subject: [PATCH 30/76] Add section about type system magic --- posts/DRAFT-paging.md | 112 +++++++++++++++++++++++++++++++++++++++++- 1 file changed, 110 insertions(+), 2 deletions(-) diff --git a/posts/DRAFT-paging.md b/posts/DRAFT-paging.md index 3a1fd0f4..cd288ac5 100644 --- a/posts/DRAFT-paging.md +++ b/posts/DRAFT-paging.md @@ -286,10 +286,118 @@ So if we switch to another P4 table at some time, it needs to be identity mapped _What happens if we call them on a P1 table?_ -Well, they would calculate the address of the next table (which does not exist) and treat it as a page table. Either they construct an invalid address (if `XXX < 400`) or access the mapped page itself. That way, we could easily corrupt memory or cause CPU exceptions by accident. So these two functions are not _safe_ in Rust terms. Thus we need to make them `unsafe` functions if we don't find some clever solution. +Well, they would calculate the address of the next table (which does not exist) and treat it as a page table. Either they construct an invalid address (if `XXX < 400`) or access the mapped page itself. That way, we could easily corrupt memory or cause CPU exceptions by accident. So these two functions are not _safe_ in Rust terms. Thus we need to make them `unsafe` functions unless we find some clever solution. ## Some Clever Solution -We can use Rust's type system to statically guarantee that the methods can only be called on P4, P3, and P2 tables. +We can use Rust's type system to statically guarantee that the methods can only be called on P4, P3, and P2 tables. The idea is to add a `Level` parameter to the `Table` type and implement the `next_table` methods only for level 4, 3, and 2. + +To model the levels we use a trait and empty enums: + +```rust +pub trait TableLevel {} + +pub enum Level4 {} +enum Level3 {} +enum Level2 {} +enum Level1 {} + +impl TableLevel for Level4 {} +impl TableLevel for Level3 {} +impl TableLevel for Level2 {} +impl TableLevel for Level1 {} +``` +An empty enum has size zero and disappears completely after compiling. Unlike an empty struct, it's not possible to instantiate an empty enum. Since we will use `TableLevel` and `Level4` in exported types, they need to be public as well. + +To differentiate the P1 table from the other tables, we introduce a `HierachicalLevel` trait, which is a subtrait of `TableLevel`. But we implement it only for the levels 4, 3, and 2: + +```rust +trait HierachicalLevel: TableLevel {} + +impl HierachicalLevel for Level4 {} +impl HierachicalLevel for Level3 {} +impl HierachicalLevel for Level2 {} +``` + +Now we add the level parameter to the `Table` type: + +```rust +pub struct Table { + entries: [Entry; ENTRY_COUNT], + level: PhantomData, +} +``` +We need to use [PhantomData] here because unused type parameters are not allowed in Rust. + +[PhantomData]: https://doc.rust-lang.org/std/marker/struct.PhantomData.html#unused-type-parameters + +Since we changed the `Table` type, we need to update every use of it: + +```rust +pub const P4: *mut Table = 0xffffffff_fffff000 as *mut _; +... +impl Table where L: TableLevel +{ + pub fn zero(&mut self) {...} +} + +impl Table where L: HierachicalLevel +{ + pub fn next_table(&self, index: usize) -> Option<&Table> {...} + + pub fn next_table_mut(&mut self, index: usize) -> Option<&mut Table> {...} + + fn next_table_address(&self, index: usize) -> Option {...} +} + +impl Index for Table where L: TableLevel {...} + +impl IndexMut for Table where L: TableLevel {...} +``` +Now the `next_table` methods are only available for P4, P3, and P2 tables. But they have the incomplete return type `Table` now. What should we fill in for the `???`? + +For a P4 table we would like to return a `Table`, for a P3 table a `Table`, and for a P2 table a `Table`. So we want to return a table of the _next level_. So let's add a associated `NextLevel` type to the `HierachicalLevel` trait: + +```rust +trait HierachicalLevel: TableLevel { + type NextLevel: TableLevel; +} + +impl HierachicalLevel for Level4 { + type NextLevel = Level3; +} + +impl HierachicalLevel for Level3 { + type NextLevel = Level2; +} + +impl HierachicalLevel for Level2 { + type NextLevel = Level1; +} +``` + +Now we can replace the `Table` types with `Table` types and our code works as intended. You can try it with a simple test function: + +```rust +fn test() { + let p4 = unsafe { &*P4 }; + p4.next_table(42) + .and_then(|p3| p3.next_table(1337)) + .and_then(|p2| p2.next_table(0xdeadbeaf)) + .and_then(|p1| p1.next_table(0xcafebabe)) +} +``` +Most of the indexes are completely out of bounds, so it would panic if it's called. But we don't need to call it since it already fails at compile time: + +``` +error: no method named `next_table` found for type + `&memory::paging::table::Table` + in the current scope +``` +Now remember that this is bare metal kernel code. We just used type system magic to make low-level page table manipulations safer. Rust is just awesome! + + + + # OLD From c52f58cc4ccec8371b45f3adf45832810e528a70 Mon Sep 17 00:00:00 2001 From: Philipp Oppermann Date: Sat, 5 Dec 2015 01:00:13 +0100 Subject: [PATCH 31/76] Rename and use `is_unused()` --- posts/DRAFT-paging.md | 2 +- src/memory/paging/entry.rs | 2 +- src/memory/paging/mapping.rs | 2 +- 3 files changed, 3 insertions(+), 3 deletions(-) diff --git a/posts/DRAFT-paging.md b/posts/DRAFT-paging.md index cd288ac5..33c5bcae 100644 --- a/posts/DRAFT-paging.md +++ b/posts/DRAFT-paging.md @@ -32,7 +32,7 @@ use memory::Frame; // needed later pub struct Entry(u64); impl Entry { - pub fn unused(&self) -> bool { + pub fn is_unused(&self) -> bool { self.0 == 0 } diff --git a/src/memory/paging/entry.rs b/src/memory/paging/entry.rs index e08ba9a5..1048f469 100644 --- a/src/memory/paging/entry.rs +++ b/src/memory/paging/entry.rs @@ -4,7 +4,7 @@ use memory::paging::PhysicalAddress; pub struct Entry(u64); impl Entry { - pub fn unused(&self) -> bool { + pub fn is_unused(&self) -> bool { self.0 == 0 } diff --git a/src/memory/paging/mapping.rs b/src/memory/paging/mapping.rs index 8a6af29f..faa7c602 100644 --- a/src/memory/paging/mapping.rs +++ b/src/memory/paging/mapping.rs @@ -19,6 +19,6 @@ pub fn map_to(page: &Page, frame: Frame, flags: EntryFlags, allocator: &mut A 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()].flags().contains(PRESENT)); + assert!(p1[page.p1_index()].is_unused()); p1[page.p1_index()].set(frame, flags | PRESENT); } From cdaad86a061b8bb253f982ead972c8c7d7d0e80f Mon Sep 17 00:00:00 2001 From: Philipp Oppermann Date: Sat, 5 Dec 2015 01:01:33 +0100 Subject: [PATCH 32/76] Change semantics of `start_address` and `containing_address` The page numbers now respect the memory hole between higher and lower half. --- src/memory/paging/mod.rs | 19 +++++-------------- 1 file changed, 5 insertions(+), 14 deletions(-) diff --git a/src/memory/paging/mod.rs b/src/memory/paging/mod.rs index ce40a3d7..25abc8fc 100644 --- a/src/memory/paging/mod.rs +++ b/src/memory/paging/mod.rs @@ -40,22 +40,13 @@ pub struct Page { impl Page { fn containing_address(address: VirtualAddress) -> Page { - match address { - addr if addr < 0o_400_000_000_000_0000 => Page { number: addr / PAGE_SIZE }, - addr if addr >= 0o177777_400_000_000_000_0000 => { - Page { number: (address / PAGE_SIZE) & 0o_777_777_777_777 } - } - _ => panic!("invalid address: 0x{:x}", address), - } + assert!(address < 0x0000_8000_0000_0000 || address >= 0xffff_8000_0000_0000, + "invalid address: 0x{:x}", address); + Page { number: address / PAGE_SIZE } } - pub fn start_address(&self) -> VirtualAddress { - if self.number >= 0x800000000 { - // sign extension necessary - (self.number << 12) | 0xffff_000000000000 - } else { - self.number << 12 - } + fn start_address(&self) -> VirtualAddress { + self.number * PAGE_SIZE } fn p4_index(&self) -> usize { From 4aeda180a17e3f4602d6a85e22f977d2086760b9 Mon Sep 17 00:00:00 2001 From: Philipp Oppermann Date: Sat, 5 Dec 2015 01:03:00 +0100 Subject: [PATCH 33/76] Remove old content and add skeletons for new sections --- posts/DRAFT-paging.md | 231 +++++++++++++++++++----------------------- 1 file changed, 106 insertions(+), 125 deletions(-) diff --git a/posts/DRAFT-paging.md b/posts/DRAFT-paging.md index 33c5bcae..4dcdf8a0 100644 --- a/posts/DRAFT-paging.md +++ b/posts/DRAFT-paging.md @@ -138,7 +138,7 @@ impl Frame { ``` Since we only need it in the entry submodule, we put it in a new `impl Frame` block in `entry.rs`. -## Page Tables +### Page Tables To model page tables, we create a basic `Table` struct in a new `table` submodule: ```rust @@ -220,7 +220,6 @@ mov [p4_table + 511 * 8], eax ``` I put it right after the `setup_page_tables` label, but you can add it wherever you like. -### The special addresses Now we can use special virtual addresses to access the page tables. The P4 table is available at `0xfffffffffffff000`. Let's add a P4 constant to the `table` submodule: ```rust @@ -393,152 +392,134 @@ error: no method named `next_table` found for type `&memory::paging::table::Table` in the current scope ``` -Now remember that this is bare metal kernel code. We just used type system magic to make low-level page table manipulations safer. Rust is just awesome! +Remember that this is bare metal kernel code. We just used type system magic to make low-level page table manipulations safer. Rust is just awesome! - - - - -# OLD - -### Sign Extension -The `Page::start_address` method doesn't exist yet. But it should be a simple `page.number * PAGE_SIZE`, right? Well, if the x86_64 architecture had true 64bit addresses, yes. But in reality the addresses are just 48bit long and the other bits are just _sign extension_, i.e. a copy of the most significant bit. That means that the address calculated by `page.number * PAGE_SIZE` is wrong if the 47th bit is used. Some examples: - -``` -invalid address: 0x0000_800000000000 - sign extension | 48bit address -valid sign extension: 0xffff_800000000000 -``` -TODO graphic - -So the address space is split into two halves: the _higher half_ containing addresses with sign extension and the _lower half_ containing addresses without. And our `Page::start_address` method needs to respect this: +## Translating Addresses +Now let's do something useful with our new module. We will create a function that translates a virtual address to the corresponding physical address. We add it to the `paging/mod.rs` module: ```rust -pub fn start_address(&self) -> VirtualAddress { - if self.number >= 0x800000000 { - // sign extension necessary - (self.number << 12) | 0xffff_000000000000 - } else { - self.number << 12 - } -} -``` -The `0x800000000` is the start address of the higher half without the last four 0s (because it's a page _number_). - - -## Translating addresses -Now we can use the recursive mapping to translate virtual address manually. We will create a function that takes a virtual address and returns the corresponding physical address: - -```rust -pub fn translate(virtual_address: usize) -> Option { - let page = Page::containing_address(virtual_address); +pub fn translate(virtual_address: VirtualAddress) -> Option { let offset = virtual_address % PAGE_SIZE; - - let frame_number = { - let p4_entry = page.p4_table().entry(page.p4_index()); - assert!(!p4_entry.flags().contains(HUGE_PAGE)); - if !p4_entry.flags().contains(PRESENT) { - return None; - } - - let p3_entry = unsafe { page.p3_table() }.entry(page.p3_index()); - if !p3_entry.flags().contains(PRESENT) { - return None; - } - if p3_entry.flags().contains(HUGE_PAGE) { - // 1GiB page (address must be 1GiB aligned) - let start_frame_number = p3_entry.pointed_frame().number; - assert!(start_frame_number % (ENTRY_COUNT * ENTRY_COUNT) == 0); - start_frame_number + page.p2_index() * ENTRY_COUNT + page.p1_index() - } else { - // 2MiB or 4KiB page - let p2_entry = unsafe { page.p2_table() }.entry(page.p2_index()); - if !p2_entry.flags().contains(PRESENT) { - return None; - } - if p2_entry.flags().contains(HUGE_PAGE) { - // 2MiB page (address must be 2MiB aligned) - let start_frame_number = p2_entry.pointed_frame().number; - assert!(start_frame_number % ENTRY_COUNT == 0); - start_frame_number + page.p1_index() - } else { - // standard 4KiB page - let p1_entry = unsafe { page.p1_table() }.entry(page.p1_index()); - assert!(!p1_entry.flags().contains(HUGE_PAGE)); - if !p1_entry.flags().contains(PRESENT) { - return None; - } - p1_entry.pointed_frame().number - } - } - }; - Some(frame_number * PAGE_SIZE + offset) + translate_page(Page::containing_address(virtual_address)) + .map(|frame| frame.number * PAGE_SIZE + offset) } ``` -(It's just some naive code and feels quite repeative… I'm open for alternative solutions) +It uses two functions we haven't defined yet: `Page::containing_address` and `translate_page`. Let's start with the former: +```rust +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 } +} +``` +The assertion is needed because there can be invalid addresses. Addresses on x86 are just 48-bit long and the other bits are just _sign extension_, i.e. a copy of the most significant bit. For example: + +``` +invalid address: 0x0000_8000_0000_0000 +valid address: 0xffff_8000_0000_0000 + └── bit 47 +``` +So the address space is split into two halves: the _higher half_ containing addresses with sign extension and the _lower half_ containing addresses without. Everything in between is invalid. + +TODO: The `translate_page` function looks like this: + +```rust +fn translate_page(page: Page) -> Option { + let p4 = unsafe { &*P4 }; + + let huge_page = || None; // TODO + + p4.next_table(page.p4_index()) + .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) +} +``` TODO -## Modifying Entries -To modify page table entries, we add a `set_entry` function to `Table`: +### Safety +TODO lock/controller + +### Huge Pages + +The `huge_page` closure looks like this: ```rust -fn set_entry(&mut self, index: usize, value: TableEntry) { - assert!(index < ENTRY_COUNT); - let entry_address = self.0.start_address() + index * ENTRY_SIZE; - unsafe { *(entry_address as *mut _) = value } -} -``` - -And to create new entries, we add some `TableEntry` constructors: - -```rust -const fn unused() -> TableEntry { - TableEntry(0) -} - -fn new(frame: Frame, flags: TableEntryFlags) -> TableEntry { - let frame_addr = (frame.number << 12) & 0x000fffff_fffff000; - TableEntry((frame_addr as u64) | flags.bits()) -} +let huge_page = || { + p4.next_table(page.p4_index()) + .and_then(|p3| { + // 1GiB page? + if p3[page.p3_index()].flags().contains(HUGE_PAGE | PRESENT) { + let start_frame_number = p3[page.p3_index()].pointed_frame().unwrap().number; + // address must be 1GiB aligned + assert!(start_frame_number % (ENTRY_COUNT * ENTRY_COUNT) == 0); + return Some(start_frame_number + page.p2_index() * ENTRY_COUNT + page.p1_index()); + } + if let Some(p2) = p3.next_table(page.p3_index()) { + // 2MiB page? + if p2[page.p2_index()].flags().contains(HUGE_PAGE | PRESENT) { + let start_frame_number = p2[page.p2_index()].pointed_frame().unwrap().number; + // address must be 2MiB aligned + assert!(start_frame_number % ENTRY_COUNT == 0); + return Some(start_frame_number + page.p1_index()); + } + } + None + }) + .map(|start_frame_number| Frame { number: start_frame_number }) +}; ``` +TODO + FIXME (ugly) ## Mapping Pages -To map +TODO lock etc ```rust -pub fn map_to(page: &Page, frame: Frame, flags: TableEntryFlags, - allocator: &mut A) where A: FrameAllocator +pub fn map_to(page: &Page, frame: Frame, flags: EntryFlags, allocator: &mut A) + where A: FrameAllocator { - let p4_index = page.p4_index(); - let p3_index = page.p3_index(); - let p2_index = page.p2_index(); - let p1_index = page.p1_index(); + let p4 = unsafe { &mut *P4 }; + 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); - let mut p4 = page.p4_table(); - if !p4.entry(p4_index).flags().contains(PRESENT) { + assert!(p1[page.p1_index()].is_unused()); + p1[page.p1_index()].set(frame, flags | PRESENT); +} +``` + +```rust +pub fn next_table_create(&mut self, + index: usize, + allocator: &mut A) + -> &mut Table + where A: FrameAllocator +{ + if let None = self.next_table_address(index) { + assert!(!self.entries[index].flags().contains(HUGE_PAGE), + "mapping code does not support huge pages"); let frame = allocator.allocate_frame().expect("no frames available"); - p4.set_entry(p4_index, TableEntry::new(frame, PRESENT | WRITABLE)); - unsafe { page.p3_table() }.zero(); + self.entries[index].set(frame, PRESENT | WRITABLE); + self.next_table_mut(index).unwrap().zero(); } - let mut p3 = unsafe { page.p3_table() }; - if !p3.entry(p3_index).flags().contains(PRESENT) { - let frame = allocator.allocate_frame().expect("no frames available"); - p3.set_entry(p3_index, TableEntry::new(frame, PRESENT | WRITABLE)); - unsafe { page.p2_table() }.zero(); - } - let mut p2 = unsafe { page.p2_table() }; - if !p2.entry(p2_index).flags().contains(PRESENT) { - let frame = allocator.allocate_frame().expect("no frames available"); - p2.set_entry(p2_index, TableEntry::new(frame, PRESENT | WRITABLE)); - unsafe { page.p1_table() }.zero(); - } - let mut p1 = unsafe { page.p1_table() }; - assert!(!p1.entry(p1_index).flags().contains(PRESENT)); - p1.set_entry(p1_index, TableEntry::new(frame, flags)); + self.next_table_mut(index).unwrap() +} +``` + +```rust +pub fn map(page: &Page, flags: EntryFlags, allocator: &mut A) + where A: FrameAllocator +{ + let frame = allocator.allocate_frame().expect("out of memory"); + map_to(page, frame, flags, allocator) } ``` ## Unmapping Pages +TODO + +## Modifying Inactive Tables ## Switching Page Tables From f912dfa506c8a576b5b6b6b3c20eb8aa06c9067d Mon Sep 17 00:00:00 2001 From: Philipp Oppermann Date: Sun, 6 Dec 2015 14:58:43 +0100 Subject: [PATCH 34/76] Improve huge_page closure --- src/memory/paging/translate.rs | 28 +++++++++++++++++----------- 1 file changed, 17 insertions(+), 11 deletions(-) diff --git a/src/memory/paging/translate.rs b/src/memory/paging/translate.rs index 97d8b58d..05a3fcb4 100644 --- a/src/memory/paging/translate.rs +++ b/src/memory/paging/translate.rs @@ -15,25 +15,31 @@ fn translate_page(page: Page) -> Option { let huge_page = || { p4.next_table(page.p4_index()) .and_then(|p3| { + let p3_entry = &p3[page.p3_index()]; // 1GiB page? - if p3[page.p3_index()].flags().contains(HUGE_PAGE | PRESENT) { - let start_frame_number = p3[page.p3_index()].pointed_frame().unwrap().number; - // address must be 1GiB aligned - assert!(start_frame_number % (ENTRY_COUNT * ENTRY_COUNT) == 0); - return Some(start_frame_number + page.p2_index() * ENTRY_COUNT + page.p1_index()); + 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 p2[page.p2_index()].flags().contains(HUGE_PAGE | PRESENT) { - let start_frame_number = p2[page.p2_index()].pointed_frame().unwrap().number; - // address must be 2MiB aligned - assert!(start_frame_number % ENTRY_COUNT == 0); - return Some(start_frame_number + page.p1_index()); + 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 }) - .map(|start_frame_number| Frame { number: start_frame_number }) }; p4.next_table(page.p4_index()) From 5113fce8f7414cf62f3cc21782193934d6d6543e Mon Sep 17 00:00:00 2001 From: Philipp Oppermann Date: Sun, 6 Dec 2015 17:29:10 +0100 Subject: [PATCH 35/76] Big update and many new sections... --- posts/DRAFT-paging.md | 204 ++++++++++++++++++++++++++++++++++-------- 1 file changed, 166 insertions(+), 38 deletions(-) diff --git a/posts/DRAFT-paging.md b/posts/DRAFT-paging.md index 4dcdf8a0..39c79f59 100644 --- a/posts/DRAFT-paging.md +++ b/posts/DRAFT-paging.md @@ -422,59 +422,67 @@ valid address: 0xffff_8000_0000_0000 ``` So the address space is split into two halves: the _higher half_ containing addresses with sign extension and the _lower half_ containing addresses without. Everything in between is invalid. -TODO: The `translate_page` function looks like this: +The other function, `translate_page`, looks like this: ```rust -fn translate_page(page: Page) -> Option { - let p4 = unsafe { &*P4 }; +pub fn translate_page(page: Page) -> Option { + let p3 = unsafe { &*P4 }.next_table(page.p4_index()); - let huge_page = || None; // TODO + let huge_page = || { + // TODO + }; - p4.next_table(page.p4_index()) - .and_then(|p3| p3.next_table(page.p3_index())) + 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) } ``` -TODO +We use an unsafe block to convert the raw `P4` pointer to a reference. Then we use the [Option::and_then] function to go through the four table levels. If some entry along the way is `None`, we check if the page is a huge page through the (unimplemented) `huge_page` closure. + +[Option::and_then]: https://doc.rust-lang.org/nightly/core/option/enum.Option.html#method.and_then ### Safety -TODO lock/controller +We would use an `unsafe` block to convert the raw `P4` pointer into a shared reference. It's safe because we don't create any `&mut` references to the table right now and don't switch the P4 table either. ### Huge Pages -The `huge_page` closure looks like this: +The `huge_page` closure calculates the corresponding frame if huge pages are used. Its content looks like this: ```rust -let huge_page = || { - p4.next_table(page.p4_index()) - .and_then(|p3| { - // 1GiB page? - if p3[page.p3_index()].flags().contains(HUGE_PAGE | PRESENT) { - let start_frame_number = p3[page.p3_index()].pointed_frame().unwrap().number; +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(start_frame_number + page.p2_index() * ENTRY_COUNT + page.p1_index()); + 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()) { - // 2MiB page? - if p2[page.p2_index()].flags().contains(HUGE_PAGE | PRESENT) { - let start_frame_number = p2[page.p2_index()].pointed_frame().unwrap().number; + } + 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(start_frame_number + page.p1_index()); + assert!(start_frame.number % ENTRY_COUNT == 0); + return Some(Frame { + number: start_frame.number + page.p1_index() + }); } } - None - }) - .map(|start_frame_number| Frame { number: start_frame_number }) -}; + } + None + }) ``` -TODO + FIXME (ugly) +This function is much longer and more complex than the `translate_page` function itself. To avoid this complexity in the future, we will only work with standard 4KiB pages from now on. ## Mapping Pages -TODO lock etc +Let's add a function that maps a `Page` to some `Frame`: ```rust pub fn map_to(page: &Page, frame: Frame, flags: EntryFlags, allocator: &mut A) @@ -489,6 +497,7 @@ pub fn map_to(page: &Page, frame: Frame, flags: EntryFlags, allocator: &mut A p1[page.p1_index()].set(frame, flags | PRESENT); } ``` +The `Table::next_table_create` method doesn't exist yet. It should return the next table if it exists, or create a new one. Therefor we need the `FrameAllocator` from the [previous post] and the `Table::zero` method: ```rust pub fn next_table_create(&mut self, @@ -497,7 +506,7 @@ pub fn next_table_create(&mut self, -> &mut Table where A: FrameAllocator { - if let None = self.next_table_address(index) { + 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"); @@ -507,19 +516,138 @@ pub fn next_table_create(&mut self, self.next_table_mut(index).unwrap() } ``` +We can use `unwrap()` here since the next table definitely exists. + +### Safety +We used an `unsafe` block in `map_to` to convert the raw `P4` pointer to a `&mut` reference. That's bad. It's now possible that the `&mut` reference is not exclusive, which breaks Rust's guarantees. It's only a matter time before we run into a data race. For example, imagine that one thread maps an entry to `frame_A` and another thread (on the same core) tries to map the same entry to `frame_B`. + +The problem is that there's no clear _owner_ for the page tables. So let's define the ownership! + +### Page Table Ownership +We define the following: + +> A page table owns all of its subtables. + +We already obey this rule: To get a reference to a table, we need to lend it from its parent table through the `next_table` method. But who owns the P4 table? + +> The recursively mapped P4 table is owned by a `RecursivePageTable` struct. + +We just defined some random owner for the P4 table. But it will solve our problems. So let's create it: ```rust -pub fn map(page: &Page, flags: EntryFlags, allocator: &mut A) - where A: FrameAllocator -{ - let frame = allocator.allocate_frame().expect("out of memory"); - map_to(page, frame, flags, allocator) +pub struct RecursivePageTable { + p4: Unique>, +} +``` +We can't store the `Table` directly because it needs to be at a special memory location (like the [VGA text buffer]). We could use a raw pointer or `&mut` instead of [Unique], but Unique indicates ownership better. + +[VGA text buffer]: http://os.phil-opp.com/printing-to-screen.html#the-text-buffer +[Unique]: https://doc.rust-lang.org/nightly/core/ptr/struct.Unique.html + +We add some functions to get a P4 reference: + +```rust +fn p4(&self) -> &Table { + unsafe { self.p4.get() } +} + +fn p4_mut(&mut self) -> &mut Table { + unsafe { self.p4.get_mut() } } ``` -## Unmapping Pages +Since we will only create valid P4 pointers, the `unsafe` blocks are safe. However, we don't make these functions public since they could be used to make page tables invalid. Only the higher level functions (such as `translate` or `map_to`) should be usable from other modules. + +Now we can make the `map_to` and `translate` functions safe by making them methods of `RecursivePageTable`: + +```rust +impl RecursivePageTable { + fn p4(&self) -> &Table {...} + + fn p4_mut(&mut self) -> &mut Table {...} + + pub fn translate(&self, virtual_address: VirtualAddress) + -> Option + { + ... + self.translate_page(...).map(...) + } + + fn translate_page(&self, page: Page) -> Option { + let p3 = self.p4().next_table(...); + ... + } + + pub fn map_to(&mut self, + page: &Page, + frame: Frame, + flags: EntryFlags, + allocator: &mut A) + where A: FrameAllocator + { + let mut p3 = self.p4_mut().next_table_create(...); + ... + } +} +``` +Now the `p4()` and `p4_mut()` methods should be the only methods containing an `unsafe` block. + +### More Mapping Functions + +For convenience, we add a `map` method that just picks a free frame for us: + +```rust +pub fn map(&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) +} +``` + +We also add a `identity_map` function to make it easier to remap the kernel in the next post: + +```rust +pub fn identity_map(&mut self, + frame: Frame, + flags: EntryFlags, + allocator: &mut A) + where A: FrameAllocator +{ + let page = Page { number: frame.number }; + self.map_to(&page, frame, flags, allocator) +} +``` + +### Unmapping Pages + TODO -## Modifying Inactive Tables +```rust +fn unmap(&mut self, page: &Page, allocator: &mut A) + where A: FrameAllocator +{ + assert!(self.translate(page.start_address()).is_some()); -## Switching Page Tables + 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())) + .unwrap(); + let frame = p1[page.p1_index()].pointed_frame().unwrap(); + p1[page.p1_index()].set_unused(); + unsafe { tlb::flush(page.start_address()) }; + // TODO free p(1,2,3) table if empty + allocator.deallocate_frame(frame); +} +``` +TODO +Huge pages… + +## Testing it + + +## What's next? +In the next post we will extend this module and add a function to modify inactive page tables. Through that function, we will create a new page table hierarchy that maps the kernel correctly using 4KiB pages. Then we will switch to the new table to get a safer kernel environment. + +Afterwards, we will use this paging module to build a heap allocator. This will allow us to use allocation and collection types such as `Box` and `Vec`. From c5fac2647ea1b7cd69efa558a52344b5b612120b Mon Sep 17 00:00:00 2001 From: Philipp Oppermann Date: Sun, 6 Dec 2015 17:43:18 +0100 Subject: [PATCH 36/76] Merge lock.rs into mod.rs --- src/memory/paging/lock.rs | 145 --------------------------------- src/memory/paging/mod.rs | 165 ++++++++++++++++++++++++++++++++++++-- 2 files changed, 160 insertions(+), 150 deletions(-) delete mode 100644 src/memory/paging/lock.rs diff --git a/src/memory/paging/lock.rs b/src/memory/paging/lock.rs deleted file mode 100644 index 9a3b6e96..00000000 --- a/src/memory/paging/lock.rs +++ /dev/null @@ -1,145 +0,0 @@ -use memory::{Frame, FrameAllocator}; -use memory::paging::{VirtualAddress, PhysicalAddress, Page, PAGE_SIZE, ENTRY_COUNT}; -use memory::paging::table::{Table, P4, Level4}; -use memory::paging::entry::{EntryFlags, PRESENT, WRITABLE, HUGE_PAGE}; -use x86::tlb; - -pub struct PageTable { - p4_frame: Frame, // recursive mapped -} - -impl PageTable { - pub fn create_new_on_identity_mapped_frame(&self, identity_mapped_frame: Frame) -> PageTable { - let page_address = Page { number: identity_mapped_frame.number }.start_address(); - // frame must be identity mapped - assert!(self.read(|lock| lock.translate(page_address)) == Some(page_address)); - - let table = unsafe { &mut *(page_address as *mut Table) }; - table[511].set(Frame { number: identity_mapped_frame.number }, WRITABLE); - PageTable { p4_frame: identity_mapped_frame } - } - - pub fn read(&self, f: F) -> R - where F: FnOnce(&Lock) -> R - { - let p4_address = 0o177777_777_777_777_777_7770 as *mut usize; - let backup = unsafe { *p4_address }; - let ret; - if Frame::containing_address(backup) == self.p4_frame { - ret = f(&Lock { _private: () }); - } else { - unsafe { *p4_address = (self.p4_frame.number << 12) | 0b11 }; - ret = f(&Lock { _private: () }); - unsafe { *p4_address = backup }; - } - ret - } - - pub fn modify(&mut self, f: F) - where F: FnOnce(&mut Lock) - { - let p4_address = 0o177777_777_777_777_777_7770 as *mut usize; - let backup = unsafe { *p4_address }; - if Frame::containing_address(backup) == self.p4_frame { - f(&mut Lock { _private: () }); - } else { - unsafe { *p4_address = (self.p4_frame.number << 12) | 0b11 }; - f(&mut Lock { _private: () }); - unsafe { *p4_address = backup }; - } - } -} - -pub struct Lock { - _private: (), -} - -impl Lock { - pub fn translate(&self, virtual_address: VirtualAddress) -> Option { - 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 { - let p4 = unsafe { &*P4 }; - - let huge_page = || { - p4.next_table(page.p4_index()) - .and_then(|p3| { - // 1GiB page? - if p3[page.p3_index()].flags().contains(HUGE_PAGE | PRESENT) { - let start_frame_number = p3[page.p3_index()].pointed_frame().unwrap().number; - // address must be 1GiB aligned - assert!(start_frame_number % (ENTRY_COUNT * ENTRY_COUNT) == 0); - return Some(start_frame_number + page.p2_index() * ENTRY_COUNT + - page.p1_index()); - } - if let Some(p2) = p3.next_table(page.p3_index()) { - // 2MiB page? - if p2[page.p2_index()].flags().contains(HUGE_PAGE | PRESENT) { - let start_frame_number = p2[page.p2_index()] - .pointed_frame() - .unwrap() - .number; - // address must be 2MiB aligned - assert!(start_frame_number % ENTRY_COUNT == 0); - return Some(start_frame_number + page.p1_index()); - } - } - None - }) - .map(|start_frame_number| Frame { number: start_frame_number }) - }; - - p4.next_table(page.p4_index()) - .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(&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 map_to(&mut self, page: &Page, frame: Frame, flags: EntryFlags, allocator: &mut A) - where A: FrameAllocator - { - let p4 = unsafe { &mut *P4 }; - 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()].flags().contains(PRESENT)); - p1[page.p1_index()].set(frame, flags | PRESENT); - } - - pub fn identity_map(&mut self, frame: Frame, flags: EntryFlags, allocator: &mut A) - where A: FrameAllocator - { - let page = Page { number: frame.number }; - self.map_to(&page, frame, flags, allocator) - } - - - fn unmap(&mut self, page: &Page, allocator: &mut A) - where A: FrameAllocator - { - assert!(self.translate(page.start_address()).is_some()); - - let p4 = unsafe { &mut *P4 }; - let p1 = p4.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())) - .unwrap(); - let frame = p1[page.p1_index()].pointed_frame().unwrap(); - p1[page.p1_index()].set_unused(); - unsafe { tlb::flush(page.start_address()) }; - // TODO free p(1,2,3) table if empty - allocator.deallocate_frame(frame); - } -} diff --git a/src/memory/paging/mod.rs b/src/memory/paging/mod.rs index 25abc8fc..ef89368f 100644 --- a/src/memory/paging/mod.rs +++ b/src/memory/paging/mod.rs @@ -1,8 +1,12 @@ +use core::ptr::Unique; +use memory::{Frame, FrameAllocator}; +use self::table::{Table, Level4}; +use self::entry::*; + mod entry; mod table; pub mod translate; pub mod mapping; -pub mod lock; pub fn test(frame_allocator: &mut A) where A: super::FrameAllocator @@ -23,9 +27,7 @@ pub fn test(frame_allocator: &mut A) mapping::map(&Page::containing_address(0xcafebeaf000), PRESENT, frame_allocator); - mapping::map(&Page::containing_address(0x0), - PRESENT, - frame_allocator); + mapping::map(&Page::containing_address(0x0), PRESENT, frame_allocator); } pub const PAGE_SIZE: usize = 4096; @@ -41,7 +43,8 @@ pub struct Page { impl Page { fn containing_address(address: VirtualAddress) -> Page { assert!(address < 0x0000_8000_0000_0000 || address >= 0xffff_8000_0000_0000, - "invalid address: 0x{:x}", address); + "invalid address: 0x{:x}", + address); Page { number: address / PAGE_SIZE } } @@ -62,3 +65,155 @@ impl Page { (self.number >> 0) & 0o777 } } + +pub struct RecursivePageTable { + p4: Unique>, +} + +impl RecursivePageTable { + fn p4(&self) -> &Table { + unsafe { self.p4.get() } + } + + fn p4_mut(&mut self) -> &mut Table { + unsafe { self.p4.get_mut() } + } + + pub fn translate(&self, virtual_address: VirtualAddress) -> Option { + 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 { + 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(&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 map_to(&mut self, page: &Page, frame: Frame, flags: EntryFlags, allocator: &mut A) + where A: FrameAllocator + { + let mut p3 = self.p4_mut().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()].flags().contains(PRESENT)); + p1[page.p1_index()].set(frame, flags | PRESENT); + } + + pub fn identity_map(&mut self, frame: Frame, flags: EntryFlags, allocator: &mut A) + where A: FrameAllocator + { + let page = Page { number: frame.number }; + self.map_to(&page, frame, flags, allocator) + } + + + fn unmap(&mut self, page: &Page, allocator: &mut A) + where A: FrameAllocator + { + use x86::tlb; + + 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())) + .unwrap(); + let frame = p1[page.p1_index()].pointed_frame().unwrap(); + p1[page.p1_index()].set_unused(); + unsafe { tlb::flush(page.start_address()) }; + // TODO free p(1,2,3) table if empty + allocator.deallocate_frame(frame); + } +} + +pub struct InactivePageTable { + p4_frame: Frame, // recursive mapped +} + +impl InactivePageTable { + pub fn create_new_on_identity_mapped_frame(&self, + identity_mapped_frame: Frame) + -> InactivePageTable { + let page_address = Page { number: identity_mapped_frame.number }.start_address(); + // frame must be identity mapped + assert!(self.read(|lock| lock.translate(page_address)) == Some(page_address)); + + let table = unsafe { &mut *(page_address as *mut Table) }; + table[511].set(Frame { number: identity_mapped_frame.number }, WRITABLE); + InactivePageTable { p4_frame: identity_mapped_frame } + } + + pub fn read(&self, f: F) -> R + where F: FnOnce(&RecursivePageTable) -> R + { + self.activate_temporary(|pt| f(pt)) + } + + pub fn modify(&mut self, f: F) + where F: FnOnce(&mut RecursivePageTable) + { + self.activate_temporary(f) + } + + fn activate_temporary(&self, f: F) -> R + where F: FnOnce(&mut RecursivePageTable) -> R + { + use memory::paging::table::P4; + + let mut page_table = RecursivePageTable { p4: unsafe { Unique::new(P4) } }; + + let backup = page_table.p4()[511].pointed_frame().unwrap(); + if backup == self.p4_frame { + f(&mut page_table) + } else { + page_table.p4_mut()[511] + .set(Frame { number: self.p4_frame.number }, PRESENT | WRITABLE); + let ret = f(&mut page_table); + page_table.p4_mut()[511].set(backup, PRESENT | WRITABLE); + ret + } + } +} From 05873d9dae24976233b0060a31642bab326c2a84 Mon Sep 17 00:00:00 2001 From: Philipp Oppermann Date: Sun, 6 Dec 2015 17:43:39 +0100 Subject: [PATCH 37/76] Improve next_table_create --- src/memory/paging/table.rs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/memory/paging/table.rs b/src/memory/paging/table.rs index 915953df..e81f671d 100644 --- a/src/memory/paging/table.rs +++ b/src/memory/paging/table.rs @@ -36,7 +36,7 @@ impl Table where L: HierachicalLevel -> &mut Table where A: FrameAllocator { - if let None = self.next_table_address(index) { + 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"); From 92194d835482caaee9cd2419414cb16c6ff3283e Mon Sep 17 00:00:00 2001 From: Philipp Oppermann Date: Sun, 6 Dec 2015 17:43:59 +0100 Subject: [PATCH 38/76] Remove unused feature and import --- src/lib.rs | 2 +- src/memory/paging/translate.rs | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/src/lib.rs b/src/lib.rs index 2b5eee75..49ffa893 100644 --- a/src/lib.rs +++ b/src/lib.rs @@ -14,7 +14,7 @@ #![feature(no_std, lang_items)] #![feature(const_fn, unique, core_str_ext, iter_cmp, optin_builtin_traits)] -#![feature(core_intrinsics, core_slice_ext)] +#![feature(core_slice_ext)] #![no_std] extern crate rlibc; diff --git a/src/memory/paging/translate.rs b/src/memory/paging/translate.rs index 05a3fcb4..af5b69ff 100644 --- a/src/memory/paging/translate.rs +++ b/src/memory/paging/translate.rs @@ -1,6 +1,6 @@ use super::{VirtualAddress, PhysicalAddress, Page, PAGE_SIZE, ENTRY_COUNT}; use super::table::P4; -use super::entry::{PRESENT, HUGE_PAGE}; +use super::entry::HUGE_PAGE; use memory::Frame; pub fn translate(virtual_address: VirtualAddress) -> Option { From 557738e70569b6d533a953f64334e87c6bcd2b53 Mon Sep 17 00:00:00 2001 From: Philipp Oppermann Date: Mon, 7 Dec 2015 02:34:07 +0100 Subject: [PATCH 39/76] Add a RecursivePageTable::new function --- src/memory/paging/mod.rs | 7 +++++++ 1 file changed, 7 insertions(+) diff --git a/src/memory/paging/mod.rs b/src/memory/paging/mod.rs index ef89368f..9badb5a4 100644 --- a/src/memory/paging/mod.rs +++ b/src/memory/paging/mod.rs @@ -71,6 +71,13 @@ pub struct RecursivePageTable { } impl RecursivePageTable { + pub unsafe fn new() -> RecursivePageTable { + use self::table::P4; + RecursivePageTable { + p4: Unique::new(P4), + } + } + fn p4(&self) -> &Table { unsafe { self.p4.get() } } From a279609c2692802c2034b8b0c1ef1b728744f273 Mon Sep 17 00:00:00 2001 From: Philipp Oppermann Date: Mon, 7 Dec 2015 02:34:52 +0100 Subject: [PATCH 40/76] Add images, TODOs, RecursivePageTable::new, and many improvements --- posts/DRAFT-paging.md | 39 ++++++++++++++++++++++++++++++++------- 1 file changed, 32 insertions(+), 7 deletions(-) diff --git a/posts/DRAFT-paging.md b/posts/DRAFT-paging.md index 39c79f59..0190d3bf 100644 --- a/posts/DRAFT-paging.md +++ b/posts/DRAFT-paging.md @@ -3,8 +3,12 @@ layout: post title: 'A Paging Module' --- +TODO + ## Paging +TODO + ## A Basic Paging Module Let's create a basic `memory/paging/mod.rs` module: @@ -198,13 +202,17 @@ We will use another solution, which uses a trick called _recursive mapping_. ### Recursive Mapping The trick is to map the P4 table recursively: The last entry doesn't point to a P3 table, but to the P4 table itself. Through this entry, all page tables are mapped to an unique virtual address. -TODO image +![access P4 table through recursive paging]({{ site.url }}/images/recursive_mapping_access_p4.svg) To access for example the P4 table itself, we use the address that chooses the 511th P4 entry, the 511th P3 entry, the 511th P2 entry and the 511th P1 entry. Thus we choose the same P4 frame over and over again and finally end up on it, too. Through the offset (12 bits) we choose the desired entry. +![access P3 table through recursive paging]({{ site.url }}/images/recursive_mapping_access_p3.svg) + To access a P3 table, we do the same but choose the real P4 index instead of the fourth loop. So if we like to access the 42th P3 table, we use the address that chooses the 511th entry in the P4, P3, and P2 table, but the 42th P1 entry. -When accessing a P2 table, we only loop two times and then choose entries that correspond to the P4 and P3 table of the desired P2 table. And accessing a P1 table just loops once and then uses the corresponding P4, P3, and P2 entries. +When accessing a P2 table, we only loop two times and then choose entries that correspond to the P4 and P3 table of the desired P2 table. And accessing a P1 table just loops once and then uses the corresponding P4, P3, and P2 entries: + +![access P1 table through recursive paging]({{ site.url }}/images/recursive_mapping_access_p1.svg) So we can access and modify page tables of all levels by just setting one P4 entry once. It may seem a bit strange at first, but is a very clean and simple solution once you wrapped your head around it. @@ -285,7 +293,9 @@ So if we switch to another P4 table at some time, it needs to be identity mapped _What happens if we call them on a P1 table?_ -Well, they would calculate the address of the next table (which does not exist) and treat it as a page table. Either they construct an invalid address (if `XXX < 400`) or access the mapped page itself. That way, we could easily corrupt memory or cause CPU exceptions by accident. So these two functions are not _safe_ in Rust terms. Thus we need to make them `unsafe` functions unless we find some clever solution. +Well, they would calculate the address of the next table (which does not exist) and treat it as a page table. Either they construct an invalid address (if `XXX < 400`)[^fn-invalid-address] or access the mapped page itself. That way, we could easily corrupt memory or cause CPU exceptions by accident. So these two functions are not _safe_ in Rust terms. Thus we need to make them `unsafe` functions unless we find some clever solution. + +[^fn-invalid-address]: If the `XXX` part of the address is smaller than `0o400`, it's binary representation doesn't start with `1`. But the sign extension bits, which should be a copy of that bit, are `1` instead of `0`. Thus the address is not valid. ## Some Clever Solution We can use Rust's type system to statically guarantee that the methods can only be called on P4, P3, and P2 tables. The idea is to add a `Level` parameter to the `Table` type and implement the `next_table` methods only for level 4, 3, and 2. @@ -354,7 +364,9 @@ impl IndexMut for Table where L: TableLevel {...} ``` Now the `next_table` methods are only available for P4, P3, and P2 tables. But they have the incomplete return type `Table` now. What should we fill in for the `???`? -For a P4 table we would like to return a `Table`, for a P3 table a `Table`, and for a P2 table a `Table`. So we want to return a table of the _next level_. So let's add a associated `NextLevel` type to the `HierachicalLevel` trait: +For a P4 table we would like to return a `Table`, for a P3 table a `Table`, and for a P2 table a `Table`. So we want to return a table of the _next level_. + +We can define the next level by adding an associated type to the `HierachicalLevel` trait: ```rust trait HierachicalLevel: TableLevel { @@ -443,7 +455,7 @@ We use an unsafe block to convert the raw `P4` pointer to a reference. Then we u [Option::and_then]: https://doc.rust-lang.org/nightly/core/option/enum.Option.html#method.and_then ### Safety -We would use an `unsafe` block to convert the raw `P4` pointer into a shared reference. It's safe because we don't create any `&mut` references to the table right now and don't switch the P4 table either. +We use an `unsafe` block to convert the raw `P4` pointer into a shared reference. It's safe because we don't create any `&mut` references to the table right now and don't switch the P4 table either. But as soon as we do something like that, we have to revisit this method. ### Huge Pages @@ -482,6 +494,8 @@ p3.and_then(|p3| { This function is much longer and more complex than the `translate_page` function itself. To avoid this complexity in the future, we will only work with standard 4KiB pages from now on. ## Mapping Pages +TODO imports + Let's add a function that maps a `Page` to some `Frame`: ```rust @@ -521,14 +535,14 @@ We can use `unwrap()` here since the next table definitely exists. ### Safety We used an `unsafe` block in `map_to` to convert the raw `P4` pointer to a `&mut` reference. That's bad. It's now possible that the `&mut` reference is not exclusive, which breaks Rust's guarantees. It's only a matter time before we run into a data race. For example, imagine that one thread maps an entry to `frame_A` and another thread (on the same core) tries to map the same entry to `frame_B`. -The problem is that there's no clear _owner_ for the page tables. So let's define the ownership! +The problem is that there's no clear _owner_ for the page tables. So let's define page table ownership! ### Page Table Ownership We define the following: > A page table owns all of its subtables. -We already obey this rule: To get a reference to a table, we need to lend it from its parent table through the `next_table` method. But who owns the P4 table? +We already obey this rule: To get a reference to a table, we need to borrow it from its parent table through the `next_table` method. But who owns the P4 table? > The recursively mapped P4 table is owned by a `RecursivePageTable` struct. @@ -544,6 +558,17 @@ We can't store the `Table` directly because it needs to be at a special [VGA text buffer]: http://os.phil-opp.com/printing-to-screen.html#the-text-buffer [Unique]: https://doc.rust-lang.org/nightly/core/ptr/struct.Unique.html +Because the `RecursivePageTable` owns the unique recursive mapped P4 table, there must be only one `RecursivePageTable` instance. Thus we make the constructor function unsafe: + +```rust +pub unsafe fn new() -> RecursivePageTable { + use self::table::P4; + RecursivePageTable { + p4: Unique::new(P4), + } +} +``` + We add some functions to get a P4 reference: ```rust From 6ed376112d1bf23ebd1397a01b29c677f8ce9d2a Mon Sep 17 00:00:00 2001 From: Philipp Oppermann Date: Mon, 7 Dec 2015 10:55:20 +0100 Subject: [PATCH 41/76] PAGE_SIZE is already defined in memory/mod.rs --- posts/DRAFT-paging.md | 5 +++-- src/memory/paging/mod.rs | 3 +-- src/memory/paging/translate.rs | 4 ++-- 3 files changed, 6 insertions(+), 6 deletions(-) diff --git a/posts/DRAFT-paging.md b/posts/DRAFT-paging.md index 0190d3bf..a088a6c2 100644 --- a/posts/DRAFT-paging.md +++ b/posts/DRAFT-paging.md @@ -13,7 +13,8 @@ TODO Let's create a basic `memory/paging/mod.rs` module: ```rust -pub const PAGE_SIZE: usize = 4096; +use memory::PAGE_SIZE; + const ENTRY_COUNT: usize = 512; pub type PhysicalAddress = usize; @@ -23,7 +24,7 @@ pub struct Page { number: usize, } ``` -We define constants for the page size and the number of entries per table. To make future function signatures more expressive, we can use the type aliases `PhysicalAddress` and `VirtualAddress`. The `Page` struct is similar to the `Frame` struct in the [previous post], but represents a virtual page instead of a physical frame. +We import the `PAGE_SIZE` and define a constant for the number of entries per table. To make future function signatures more expressive, we can use the type aliases `PhysicalAddress` and `VirtualAddress`. The `Page` struct is similar to the `Frame` struct in the [previous post], but represents a virtual page instead of a physical frame. [previous post]: {{ page.previous.url }} diff --git a/src/memory/paging/mod.rs b/src/memory/paging/mod.rs index 9badb5a4..471bf876 100644 --- a/src/memory/paging/mod.rs +++ b/src/memory/paging/mod.rs @@ -1,5 +1,5 @@ use core::ptr::Unique; -use memory::{Frame, FrameAllocator}; +use memory::{PAGE_SIZE, Frame, FrameAllocator}; use self::table::{Table, Level4}; use self::entry::*; @@ -30,7 +30,6 @@ pub fn test(frame_allocator: &mut A) mapping::map(&Page::containing_address(0x0), PRESENT, frame_allocator); } -pub const PAGE_SIZE: usize = 4096; const ENTRY_COUNT: usize = 512; pub type PhysicalAddress = usize; diff --git a/src/memory/paging/translate.rs b/src/memory/paging/translate.rs index af5b69ff..73dda190 100644 --- a/src/memory/paging/translate.rs +++ b/src/memory/paging/translate.rs @@ -1,7 +1,7 @@ -use super::{VirtualAddress, PhysicalAddress, Page, PAGE_SIZE, ENTRY_COUNT}; +use super::{VirtualAddress, PhysicalAddress, Page, ENTRY_COUNT}; use super::table::P4; use super::entry::HUGE_PAGE; -use memory::Frame; +use memory::{PAGE_SIZE, Frame}; pub fn translate(virtual_address: VirtualAddress) -> Option { let offset = virtual_address % PAGE_SIZE; From b314d4827fb97e1fa906496420a2ae04e01bb5ae Mon Sep 17 00:00:00 2001 From: Philipp Oppermann Date: Mon, 7 Dec 2015 10:56:26 +0100 Subject: [PATCH 42/76] Move Frame::start_address to memory/mod.rs and use it in identity_map() --- posts/DRAFT-paging.md | 14 ++++++-------- src/memory/mod.rs | 5 +++++ src/memory/paging/entry.rs | 6 ------ src/memory/paging/mod.rs | 2 +- 4 files changed, 12 insertions(+), 15 deletions(-) diff --git a/posts/DRAFT-paging.md b/posts/DRAFT-paging.md index a088a6c2..04461493 100644 --- a/posts/DRAFT-paging.md +++ b/posts/DRAFT-paging.md @@ -130,18 +130,16 @@ pub fn set(&mut self, frame: Frame, flags: EntryFlags) { ``` The start address of a frame should be page aligned and smaller than 2^52 (since x86 uses 52bit physical addresses). Since an invalid address could mess up the entry, we add an assertion. To actually set the entry, we just need to `or` the start address and the flag bits. -The missing `start_address` function is pretty simple: +The missing `Frame::start_address` method is pretty simple: ```rust -use memory::paging::PhysicalAddress; +use self::paging::PhysicalAddress; -impl Frame { - fn start_address(&self) -> PhysicalAddress { - self.number << 12 - } +fn start_address(&self) -> PhysicalAddress { + self.number * PAGE_SIZE } ``` -Since we only need it in the entry submodule, we put it in a new `impl Frame` block in `entry.rs`. +We add it to the `impl Frame` block in `memory/mod.rs`. ### Page Tables To model page tables, we create a basic `Table` struct in a new `table` submodule: @@ -640,7 +638,7 @@ pub fn identity_map(&mut self, allocator: &mut A) where A: FrameAllocator { - let page = Page { number: frame.number }; + let page = Page::containing_address(frame.start_address()); self.map_to(&page, frame, flags, allocator) } ``` diff --git a/src/memory/mod.rs b/src/memory/mod.rs index 57335343..7bf5033d 100644 --- a/src/memory/mod.rs +++ b/src/memory/mod.rs @@ -1,4 +1,5 @@ pub use self::area_frame_allocator::AreaFrameAllocator; +use self::paging::PhysicalAddress; pub mod paging; mod area_frame_allocator; @@ -14,6 +15,10 @@ 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 { diff --git a/src/memory/paging/entry.rs b/src/memory/paging/entry.rs index 1048f469..ce6f2c82 100644 --- a/src/memory/paging/entry.rs +++ b/src/memory/paging/entry.rs @@ -44,9 +44,3 @@ bitflags! { const NO_EXECUTE = 1 << 63, } } - -impl Frame { - fn start_address(&self) -> PhysicalAddress { - self.number << 12 - } -} diff --git a/src/memory/paging/mod.rs b/src/memory/paging/mod.rs index 471bf876..10548a50 100644 --- a/src/memory/paging/mod.rs +++ b/src/memory/paging/mod.rs @@ -150,7 +150,7 @@ impl RecursivePageTable { pub fn identity_map(&mut self, frame: Frame, flags: EntryFlags, allocator: &mut A) where A: FrameAllocator { - let page = Page { number: frame.number }; + let page = Page::containing_address(frame.start_address()); self.map_to(&page, frame, flags, allocator) } From 15427b363ac5e21b25b03af2457096b5e4d07e5f Mon Sep 17 00:00:00 2001 From: Philipp Oppermann Date: Mon, 7 Dec 2015 11:01:22 +0100 Subject: [PATCH 43/76] Take page by value in mapping functions (there is no reason to borrow it at the moment) --- posts/DRAFT-paging.md | 10 +++++----- src/memory/paging/mod.rs | 8 ++++---- 2 files changed, 9 insertions(+), 9 deletions(-) diff --git a/posts/DRAFT-paging.md b/posts/DRAFT-paging.md index 04461493..345abda6 100644 --- a/posts/DRAFT-paging.md +++ b/posts/DRAFT-paging.md @@ -498,7 +498,7 @@ TODO imports Let's add a function that maps a `Page` to some `Frame`: ```rust -pub fn map_to(page: &Page, frame: Frame, flags: EntryFlags, allocator: &mut A) +pub fn map_to(page: Page, frame: Frame, flags: EntryFlags, allocator: &mut A) where A: FrameAllocator { let p4 = unsafe { &mut *P4 }; @@ -603,7 +603,7 @@ impl RecursivePageTable { } pub fn map_to(&mut self, - page: &Page, + page: Page, frame: Frame, flags: EntryFlags, allocator: &mut A) @@ -621,7 +621,7 @@ Now the `p4()` and `p4_mut()` methods should be the only methods containing an ` For convenience, we add a `map` method that just picks a free frame for us: ```rust -pub fn map(&mut self, page: &Page, flags: EntryFlags, allocator: &mut A) +pub fn map(&mut self, page: Page, flags: EntryFlags, allocator: &mut A) where A: FrameAllocator { let frame = allocator.allocate_frame().expect("out of memory"); @@ -639,7 +639,7 @@ pub fn identity_map(&mut self, where A: FrameAllocator { let page = Page::containing_address(frame.start_address()); - self.map_to(&page, frame, flags, allocator) + self.map_to(page, frame, flags, allocator) } ``` @@ -648,7 +648,7 @@ pub fn identity_map(&mut self, TODO ```rust -fn unmap(&mut self, page: &Page, allocator: &mut A) +fn unmap(&mut self, page: Page, allocator: &mut A) where A: FrameAllocator { assert!(self.translate(page.start_address()).is_some()); diff --git a/src/memory/paging/mod.rs b/src/memory/paging/mod.rs index 10548a50..dfe817d3 100644 --- a/src/memory/paging/mod.rs +++ b/src/memory/paging/mod.rs @@ -129,14 +129,14 @@ impl RecursivePageTable { .or_else(huge_page) } - pub fn map(&mut self, page: &Page, flags: EntryFlags, allocator: &mut A) + pub fn map(&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 map_to(&mut self, page: &Page, frame: Frame, flags: EntryFlags, allocator: &mut A) + pub fn map_to(&mut self, page: Page, frame: Frame, flags: EntryFlags, allocator: &mut A) where A: FrameAllocator { let mut p3 = self.p4_mut().next_table_create(page.p4_index(), allocator); @@ -151,11 +151,11 @@ impl RecursivePageTable { where A: FrameAllocator { let page = Page::containing_address(frame.start_address()); - self.map_to(&page, frame, flags, allocator) + self.map_to(page, frame, flags, allocator) } - fn unmap(&mut self, page: &Page, allocator: &mut A) + fn unmap(&mut self, page: Page, allocator: &mut A) where A: FrameAllocator { use x86::tlb; From e331a6610b050b46705512d53aa2de3e2e9536c7 Mon Sep 17 00:00:00 2001 From: Philipp Oppermann Date: Mon, 7 Dec 2015 11:07:38 +0100 Subject: [PATCH 44/76] Describe `p*_index` functions --- posts/DRAFT-paging.md | 17 ++++++++++++++++- 1 file changed, 16 insertions(+), 1 deletion(-) diff --git a/posts/DRAFT-paging.md b/posts/DRAFT-paging.md index 345abda6..1258cd97 100644 --- a/posts/DRAFT-paging.md +++ b/posts/DRAFT-paging.md @@ -449,10 +449,25 @@ pub fn translate_page(page: Page) -> Option { .or_else(huge_page) } ``` -We use an unsafe block to convert the raw `P4` pointer to a reference. Then we use the [Option::and_then] function to go through the four table levels. If some entry along the way is `None`, we check if the page is a huge page through the (unimplemented) `huge_page` closure. +We use an unsafe block to convert the raw `P4` pointer to a reference. Then we use the [Option::and_then] function to go through the four table levels. If some entry along the way is `None`, we check if the page is a huge page through the (unimplemented) `huge_page` closure. The `Page::p*_index` functions look like this: [Option::and_then]: https://doc.rust-lang.org/nightly/core/option/enum.Option.html#method.and_then +```rust +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 +} +``` + ### Safety We use an `unsafe` block to convert the raw `P4` pointer into a shared reference. It's safe because we don't create any `&mut` references to the table right now and don't switch the P4 table either. But as soon as we do something like that, we have to revisit this method. From 67b30314c469ecce53e032bf85160497590cc56f Mon Sep 17 00:00:00 2001 From: Philipp Oppermann Date: Mon, 7 Dec 2015 14:07:00 +0100 Subject: [PATCH 45/76] Use relative links for images --- posts/DRAFT-paging.md | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/posts/DRAFT-paging.md b/posts/DRAFT-paging.md index 1258cd97..d8792326 100644 --- a/posts/DRAFT-paging.md +++ b/posts/DRAFT-paging.md @@ -201,17 +201,17 @@ We will use another solution, which uses a trick called _recursive mapping_. ### Recursive Mapping The trick is to map the P4 table recursively: The last entry doesn't point to a P3 table, but to the P4 table itself. Through this entry, all page tables are mapped to an unique virtual address. -![access P4 table through recursive paging]({{ site.url }}/images/recursive_mapping_access_p4.svg) +![access P4 table through recursive paging](/images/recursive_mapping_access_p4.svg) To access for example the P4 table itself, we use the address that chooses the 511th P4 entry, the 511th P3 entry, the 511th P2 entry and the 511th P1 entry. Thus we choose the same P4 frame over and over again and finally end up on it, too. Through the offset (12 bits) we choose the desired entry. -![access P3 table through recursive paging]({{ site.url }}/images/recursive_mapping_access_p3.svg) +![access P3 table through recursive paging](/images/recursive_mapping_access_p3.svg) To access a P3 table, we do the same but choose the real P4 index instead of the fourth loop. So if we like to access the 42th P3 table, we use the address that chooses the 511th entry in the P4, P3, and P2 table, but the 42th P1 entry. When accessing a P2 table, we only loop two times and then choose entries that correspond to the P4 and P3 table of the desired P2 table. And accessing a P1 table just loops once and then uses the corresponding P4, P3, and P2 entries: -![access P1 table through recursive paging]({{ site.url }}/images/recursive_mapping_access_p1.svg) +![access P1 table through recursive paging](/images/recursive_mapping_access_p1.svg) So we can access and modify page tables of all levels by just setting one P4 entry once. It may seem a bit strange at first, but is a very clean and simple solution once you wrapped your head around it. From bd00921dac03000e506232fc1bb26a48358bd918 Mon Sep 17 00:00:00 2001 From: Philipp Oppermann Date: Mon, 7 Dec 2015 15:23:18 +0100 Subject: [PATCH 46/76] Use expect instead of unwrap --- posts/DRAFT-paging.md | 2 +- src/memory/paging/mod.rs | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/posts/DRAFT-paging.md b/posts/DRAFT-paging.md index d8792326..3c11ed1c 100644 --- a/posts/DRAFT-paging.md +++ b/posts/DRAFT-paging.md @@ -672,7 +672,7 @@ fn unmap(&mut self, page: Page, allocator: &mut A) .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())) - .unwrap(); + .expect("mapping code does not support huge pages"); let frame = p1[page.p1_index()].pointed_frame().unwrap(); p1[page.p1_index()].set_unused(); unsafe { tlb::flush(page.start_address()) }; diff --git a/src/memory/paging/mod.rs b/src/memory/paging/mod.rs index dfe817d3..9f6c12fb 100644 --- a/src/memory/paging/mod.rs +++ b/src/memory/paging/mod.rs @@ -166,7 +166,7 @@ impl RecursivePageTable { .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())) - .unwrap(); + .expect("mapping code does not support huge pages"); let frame = p1[page.p1_index()].pointed_frame().unwrap(); p1[page.p1_index()].set_unused(); unsafe { tlb::flush(page.start_address()) }; From 57fbd2efc5fb4256e8c00a737f22b568dfe06fdd Mon Sep 17 00:00:00 2001 From: Philipp Oppermann Date: Mon, 7 Dec 2015 15:24:06 +0100 Subject: [PATCH 47/76] Specify some imports --- posts/DRAFT-paging.md | 5 +++++ 1 file changed, 5 insertions(+) diff --git a/posts/DRAFT-paging.md b/posts/DRAFT-paging.md index 3c11ed1c..f2b8b58e 100644 --- a/posts/DRAFT-paging.md +++ b/posts/DRAFT-paging.md @@ -437,6 +437,8 @@ The other function, `translate_page`, looks like this: ```rust pub fn translate_page(page: Page) -> Option { + use self::table::P4; + let p3 = unsafe { &*P4 }.next_table(page.p4_index()); let huge_page = || { @@ -563,6 +565,9 @@ We already obey this rule: To get a reference to a table, we need to borrow it f We just defined some random owner for the P4 table. But it will solve our problems. So let's create it: ```rust +use core::ptr::Unique; +use self::table::{Table, Level4}; + pub struct RecursivePageTable { p4: Unique>, } From fac58a003a2e558ebde0dfa6674376a87d40d1e6 Mon Sep 17 00:00:00 2001 From: Philipp Oppermann Date: Mon, 7 Dec 2015 15:24:23 +0100 Subject: [PATCH 48/76] Describe unwrap function --- posts/DRAFT-paging.md | 11 ++++++----- 1 file changed, 6 insertions(+), 5 deletions(-) diff --git a/posts/DRAFT-paging.md b/posts/DRAFT-paging.md index f2b8b58e..2ded8b37 100644 --- a/posts/DRAFT-paging.md +++ b/posts/DRAFT-paging.md @@ -664,8 +664,7 @@ pub fn identity_map(&mut self, ``` ### Unmapping Pages - -TODO +To unmap a page, we set the corresponding P1 entry to unused: ```rust fn unmap(&mut self, page: Page, allocator: &mut A) @@ -680,13 +679,15 @@ fn unmap(&mut self, page: Page, allocator: &mut A) .expect("mapping code does not support huge pages"); let frame = p1[page.p1_index()].pointed_frame().unwrap(); p1[page.p1_index()].set_unused(); - unsafe { tlb::flush(page.start_address()) }; // TODO free p(1,2,3) table if empty allocator.deallocate_frame(frame); } ``` -TODO -Huge pages… +The assertion ensures that the page is mapped. Thus the corresponding P1 table and frame must exist for a standard 4KiB page. We set the entry to unused and free the associated frame in the supplied frame allocator. + +We can also free the P1, P2, or even P3 table when the last entry is freed. But checking the whole table on every `unmap` would be very expensive. For now, we leave the `TODO` in place until we find a good solution. I'm open for suggestions :). + +_Spoiler_: There is an ugly bug in this function, which we will find in the next section. ## Testing it From bf6bcdcc822d28a728ad26fb0b5191c610bbf13d Mon Sep 17 00:00:00 2001 From: Philipp Oppermann Date: Mon, 7 Dec 2015 17:43:51 +0100 Subject: [PATCH 49/76] Add paging introduction --- posts/DRAFT-paging.md | 22 ++++++++++++++++++++-- 1 file changed, 20 insertions(+), 2 deletions(-) diff --git a/posts/DRAFT-paging.md b/posts/DRAFT-paging.md index 2ded8b37..259c6fe8 100644 --- a/posts/DRAFT-paging.md +++ b/posts/DRAFT-paging.md @@ -6,11 +6,29 @@ title: 'A Paging Module' TODO ## Paging +_Paging_ is a memory management scheme that separates virtual and physical memory. The address space is split into equal sized _pages_ and _page tables_ specify which virtual page points to which physical page. For an extensive paging introduction take a look at the paging chapter ([PDF][paging chapter]) of the [Three Easy Pieces] OS book. -TODO +[paging chapter]: http://pages.cs.wisc.edu/~remzi/OSTEP/vm-paging.pdf +[Three Easy Pieces]: http://pages.cs.wisc.edu/~remzi/OSTEP/ + +The x86 architecture uses a 4-level page table in 64-bit mode. A virtual address has the following structure: + +![structure of a virtual address on x86](/images/x86_address_structure.svg) + +The bits 48–63 are so-called _sign extension_ bits and must be copies of bit 47. The following 36 bits define the page table indexes (9 bits per table) and the last 12 bits specify the offset in the 4KiB page. + +Each table has 2^9 = 512 entries and each entry is 8 byte. Thus a page table fits exactly in one page (4 KiB). + +To translate an address, the CPU reads the P4 address from the CR3 register. Then it uses the indexes to walk the tables: + +![translation of virtual to physical addresses in 64 bit mode](/images/X86_Paging_64bit.svg) + +The P4 entry points to a P3 table, where the next 9 bits of the address are used to select an entry. The P3 entry then points to a P2 table and the P2 entry points to a P1 table. The P1 entry, which is specified through bits 12–20, finally points to the physical page. + +So let's create a Rust module for it! ## A Basic Paging Module -Let's create a basic `memory/paging/mod.rs` module: +We start by creating a basic `memory/paging/mod.rs` module: ```rust use memory::PAGE_SIZE; From 2b8838459cb227ee56c7f17821b733742bbeec2c Mon Sep 17 00:00:00 2001 From: Philipp Oppermann Date: Mon, 7 Dec 2015 17:53:01 +0100 Subject: [PATCH 50/76] Add image sources --- posts/DRAFT-paging.md | 4 ++++ 1 file changed, 4 insertions(+) diff --git a/posts/DRAFT-paging.md b/posts/DRAFT-paging.md index 259c6fe8..386d2141 100644 --- a/posts/DRAFT-paging.md +++ b/posts/DRAFT-paging.md @@ -714,3 +714,7 @@ _Spoiler_: There is an ugly bug in this function, which we will find in the next In the next post we will extend this module and add a function to modify inactive page tables. Through that function, we will create a new page table hierarchy that maps the kernel correctly using 4KiB pages. Then we will switch to the new table to get a safer kernel environment. Afterwards, we will use this paging module to build a heap allocator. This will allow us to use allocation and collection types such as `Box` and `Vec`. + +Image sources: [^virtual_physical_translation_source] + +[^virtual_physical_translation_source]: Image sources: Modified versions of an image from [Wikipedia](https://commons.wikimedia.org/wiki/File:X86_Paging_64bit.svg). The modified files are licensed under the Creative Commons Attribution-Share Alike 3.0 Unported license. From fd42d634ad00b5a2517acfa8a238b9f4b03f8bdc Mon Sep 17 00:00:00 2001 From: Philipp Oppermann Date: Mon, 7 Dec 2015 18:23:13 +0100 Subject: [PATCH 51/76] Change title and add some incomplete introduction variants --- posts/DRAFT-paging.md | 7 ++++++- 1 file changed, 6 insertions(+), 1 deletion(-) diff --git a/posts/DRAFT-paging.md b/posts/DRAFT-paging.md index 386d2141..61ea5e7c 100644 --- a/posts/DRAFT-paging.md +++ b/posts/DRAFT-paging.md @@ -1,10 +1,14 @@ --- layout: post -title: 'A Paging Module' +title: 'Accessing and Modifying Page Tables' --- TODO +In this post we will create a paging module, which uses recursive mapping to access and modify page tables. + +In this post we will create a paging module, which allows us to access and modify the 4-level page table. We will create functions to translate a virtual to a physical address. + ## Paging _Paging_ is a memory management scheme that separates virtual and physical memory. The address space is split into equal sized _pages_ and _page tables_ specify which virtual page points to which physical page. For an extensive paging introduction take a look at the paging chapter ([PDF][paging chapter]) of the [Three Easy Pieces] OS book. @@ -708,6 +712,7 @@ We can also free the P1, P2, or even P3 table when the last entry is freed. But _Spoiler_: There is an ugly bug in this function, which we will find in the next section. ## Testing it +TODO ## What's next? From 9f509aec80cb8766f8a572a9b5d8a300a41bf0fb Mon Sep 17 00:00:00 2001 From: Philipp Oppermann Date: Wed, 9 Dec 2015 01:32:24 +0100 Subject: [PATCH 52/76] Improve post and rewrite recursive mapping section --- posts/DRAFT-paging.md | 96 ++++++++++++++++++++++++++----------------- 1 file changed, 59 insertions(+), 37 deletions(-) diff --git a/posts/DRAFT-paging.md b/posts/DRAFT-paging.md index 61ea5e7c..f32132d4 100644 --- a/posts/DRAFT-paging.md +++ b/posts/DRAFT-paging.md @@ -3,11 +3,9 @@ layout: post title: 'Accessing and Modifying Page Tables' --- -TODO +In this post we will create a paging module, which allows us to access and modify the 4-level page table. We will create functions to translate a virtual to a physical address. TODO more introduction -In this post we will create a paging module, which uses recursive mapping to access and modify page tables. - -In this post we will create a paging module, which allows us to access and modify the 4-level page table. We will create functions to translate a virtual to a physical address. +TODO current Rust nightly version, link to github repo, etc. ## Paging _Paging_ is a memory management scheme that separates virtual and physical memory. The address space is split into equal sized _pages_ and _page tables_ specify which virtual page points to which physical page. For an extensive paging introduction take a look at the paging chapter ([PDF][paging chapter]) of the [Three Easy Pieces] OS book. @@ -29,13 +27,13 @@ To translate an address, the CPU reads the P4 address from the CR3 register. The The P4 entry points to a P3 table, where the next 9 bits of the address are used to select an entry. The P3 entry then points to a P2 table and the P2 entry points to a P1 table. The P1 entry, which is specified through bits 12–20, finally points to the physical page. -So let's create a Rust module for it! +So let's create a Rust module for it! (TODO better transition) ## A Basic Paging Module We start by creating a basic `memory/paging/mod.rs` module: ```rust -use memory::PAGE_SIZE; +use memory::PAGE_SIZE; // needed later const ENTRY_COUNT: usize = 512; @@ -48,7 +46,7 @@ pub struct Page { ``` We import the `PAGE_SIZE` and define a constant for the number of entries per table. To make future function signatures more expressive, we can use the type aliases `PhysicalAddress` and `VirtualAddress`. The `Page` struct is similar to the `Frame` struct in the [previous post], but represents a virtual page instead of a physical frame. -[previous post]: {{ page.previous.url }} +[previous post]: {{ page.previous.url }}#a-memory-module ### Page Table Entries To model page table entries, we create a new `entry` submodule: @@ -88,7 +86,7 @@ Bit(s) | Name | Meaning 52-62 | available | can be used freely by the OS 63 | no execute | forbid executing code on this page (the NXE bit in the EFER register must be set) -To model the various flags, we will use the [bitflags] crate. Unfortunately the official version depends on the standard library as `no_std` is still unstable. But since it does not actually require any `std` functions, it's pretty easy to create a `no_std` version. You can find it here [here][bitflags fork]. To add it as a dependency, add the following to your `Cargo.toml`: +To model the various flags, we will use the [bitflags] crate. Unfortunately the official version depends on the standard library because `no_std` is still unstable in stable Rust. But since it does not actually require any `std` functions, it's pretty easy to create a `no_std` version. You can find it here [here][bitflags fork]. To add it as a dependency, add the following to your `Cargo.toml`: [bitflags]: https://github.com/rust-lang-nursery/bitflags [bitflags fork]: https://github.com/phil-opp/bitflags/tree/no_std @@ -214,30 +212,34 @@ We could read the `CR3` register to get the physical address of the P4 table and ## Mapping Page Tables So how do we map the page tables itself? We don't have that problem for the current P4, P3, and P2 table since they are part of the identity-mapped area, but we need a way to access future tables, too. -One solution is to identity map all page table. That way we would not need to differentiate virtual and physical address and could easily access the tables. But it clutters the virtual address space and increases fragmentation. And it makes creating page tables much more complicated since we need a physical frame whose corresponding page isn't already used for something else. +One solution is to identity map all page table. That way we would not need to differentiate virtual and physical addresses and could easily access the tables. But it clutters the virtual address space and increases fragmentation. And it makes creating page tables much more complicated since we need a physical frame whose corresponding page isn't already used for something else. -An alternative solution is to map the page tables only temporary. So to read/write a page table, we would map it to some free virtual address. We could use a small pool of such virtual addresses and reuse them for various tables. This method occupies only few virtual addresses and is thus a good solution for 32-bit systems, which have small address spaces. But it makes things much more complicated since the temporary mapping requires updating other page tables, which need to be mapped, too. +An alternative solution is to map the page tables only temporary. So to read/write a page table, we would map it to some free virtual address. We could use a small pool of such virtual addresses and reuse them for various tables. This method occupies only few virtual addresses and is thus a good solution for 32-bit systems, which have small address spaces. But it makes things much more complicated since we need to temporary map up to 4 tables to access a single page. And the temporary mapping requires modification of other page tables, which need to be mapped, too. -We will use another solution, which uses a trick called _recursive mapping_. +We will solve the problem in another way using a trick called _recursive mapping_. ### Recursive Mapping -The trick is to map the P4 table recursively: The last entry doesn't point to a P3 table, but to the P4 table itself. Through this entry, all page tables are mapped to an unique virtual address. - -![access P4 table through recursive paging](/images/recursive_mapping_access_p4.svg) - -To access for example the P4 table itself, we use the address that chooses the 511th P4 entry, the 511th P3 entry, the 511th P2 entry and the 511th P1 entry. Thus we choose the same P4 frame over and over again and finally end up on it, too. Through the offset (12 bits) we choose the desired entry. - -![access P3 table through recursive paging](/images/recursive_mapping_access_p3.svg) - -To access a P3 table, we do the same but choose the real P4 index instead of the fourth loop. So if we like to access the 42th P3 table, we use the address that chooses the 511th entry in the P4, P3, and P2 table, but the 42th P1 entry. - -When accessing a P2 table, we only loop two times and then choose entries that correspond to the P4 and P3 table of the desired P2 table. And accessing a P1 table just loops once and then uses the corresponding P4, P3, and P2 entries: +The trick is to map the P4 table recursively: The last entry doesn't point to a P3 table, but to the P4 table itself. We can use this entry to remove a translation level so that we land on a page table instead. For example, we can “loop” once to access a P1 table: ![access P1 table through recursive paging](/images/recursive_mapping_access_p1.svg) -So we can access and modify page tables of all levels by just setting one P4 entry once. It may seem a bit strange at first, but is a very clean and simple solution once you wrapped your head around it. +By selecting the 511th P4 entry, which points points to the P4 table itself, the P4 table is used as the P3 table. Similarly, the P3 table is used as a P2 table and the P2 table is treated like a P1 table. Thus the P1 table becomes the target page and can be accessed through the offset. -The math checks out, too. If all page tables are used, there is 1 P4 table, 511 P3 tables (the last entry is used for the recursive mapping), `511*512` P2 tables, and `511*512*512` P1 tables. So there are `134217728` page tables altogether. Each page table occupies 4KiB, so we need `134217728 * 4KiB = 512GiB` to store them. That's exactly the amount of memory that can be accessed through one P4 entry since `4KiB per page * 512 P1 entries * 512 P2 entries * 512 P3 entries = 512GiB`. +It's also possible to access P2 tables by looping twice. And if we select the 511th entry three times, we can access and modify P3 tables: + +![access P3 table through recursive paging](/images/recursive_mapping_access_p3.svg) + +So we just need to specify the desired P3 table in the address through the P1 index. By choosing the 511th entry multiple times, we stay on the P4 table until the address's P1 index becomes the actual P4 index. + +To access the P4 table itself, we loop once more and thus never leave the frame: + +![access P4 table through recursive paging](/images/recursive_mapping_access_p4.svg) + +So we can access and modify page tables of all levels by just setting one P4 entry once. Most work is done by the CPU, we just the recursive entry to remove one or more translation levels. It may seem a bit strange at first, but it's a clean and simple solution once you wrapped your head around it. + +By using recursive mapping, each page table is accessible through an unique virtual address. The math checks out, too: If all page tables are used, there is 1 P4 table, 511 P3 tables (the last entry is used for the recursive mapping), `511*512` P2 tables, and `511*512*512` P1 tables. So there are `134217728` page tables altogether. Each page table occupies 4KiB, so we need `134217728 * 4KiB = 512GiB` to store them. That's exactly the amount of memory that can be accessed through one P4 entry since `4KiB per page * 512 P1 entries * 512 P2 entries * 512 P3 entries = 512GiB`. + +Of course recursive mapping has some disadvantages, too. It occupies a whole P4 and thus 512GiB of the virtual address space. But since we're in long mode and have a 48-bit address space, there are still 225.5TiB left. The bigger problem is that only the active table can be modified by default. To access another table, the recursive entry needs to be replaced temporary. We will tackle this problem in the next post when we switch to a new page table. ### Implementation To map the P4 table recursively, we just need to point the 511th entry to the table itself. Of course we could do it in Rust, but it would require some unsafe pointer fiddling. It's easier to just add some lines to our boot assembly: @@ -356,9 +358,9 @@ pub struct Table { level: PhantomData, } ``` -We need to use [PhantomData] here because unused type parameters are not allowed in Rust. +We need to add a [PhantomData] field because unused type parameters are not allowed in Rust. (You need to add a `core::marker::PhantomData` import.) -[PhantomData]: https://doc.rust-lang.org/std/marker/struct.PhantomData.html#unused-type-parameters +[PhantomData]: https://doc.rust-lang.org/core/marker/struct.PhantomData.html#unused-type-parameters Since we changed the `Table` type, we need to update every use of it: @@ -437,7 +439,7 @@ pub fn translate(virtual_address: VirtualAddress) -> Option { .map(|frame| frame.number * PAGE_SIZE + offset) } ``` -It uses two functions we haven't defined yet: `Page::containing_address` and `translate_page`. Let's start with the former: +It uses two functions we haven't defined yet: `translate_page` and `Page::containing_address`. Let's start with the latter: ```rust fn containing_address(address: VirtualAddress) -> Page { @@ -455,13 +457,23 @@ valid address: 0xffff_8000_0000_0000 ``` So the address space is split into two halves: the _higher half_ containing addresses with sign extension and the _lower half_ containing addresses without. Everything in between is invalid. -The other function, `translate_page`, looks like this: +Since we added `containing_address`, we add the inverse method as well (maybe we need it later): ```rust -pub fn translate_page(page: Page) -> Option { - use self::table::P4; +fn start_address(&self) -> usize { + self.number * PAGE_SIZE +} +``` - let p3 = unsafe { &*P4 }.next_table(page.p4_index()); +The other missing function, `translate_page`, looks like this: + +```rust +use memory::Frame; + +fn translate_page(page: Page) -> Option { + use self::entry::HUGE_PAGE; + + let p3 = unsafe { &*table::P4 }.next_table(page.p4_index()); let huge_page = || { // TODO @@ -537,6 +549,9 @@ TODO imports Let's add a function that maps a `Page` to some `Frame`: ```rust +pub use self::entry::*; +use memory::FrameAllocator; + pub fn map_to(page: Page, frame: Frame, flags: EntryFlags, allocator: &mut A) where A: FrameAllocator { @@ -549,9 +564,13 @@ pub fn map_to(page: Page, frame: Frame, flags: EntryFlags, allocator: &mut A) p1[page.p1_index()].set(frame, flags | PRESENT); } ``` +We add an reexport for all `entry` types since they are required to call the function. We assert that the page is unmapped and always set the present flag (since it wouldn't make sense to map a page without setting it). + The `Table::next_table_create` method doesn't exist yet. It should return the next table if it exists, or create a new one. Therefor we need the `FrameAllocator` from the [previous post] and the `Table::zero` method: ```rust +use memory::FrameAllocator; + pub fn next_table_create(&mut self, index: usize, allocator: &mut A) @@ -587,8 +606,8 @@ We already obey this rule: To get a reference to a table, we need to borrow it f We just defined some random owner for the P4 table. But it will solve our problems. So let's create it: ```rust -use core::ptr::Unique; use self::table::{Table, Level4}; +use core::ptr::Unique; pub struct RecursivePageTable { p4: Unique>, @@ -602,10 +621,11 @@ We can't store the `Table` directly because it needs to be at a special Because the `RecursivePageTable` owns the unique recursive mapped P4 table, there must be only one `RecursivePageTable` instance. Thus we make the constructor function unsafe: ```rust -pub unsafe fn new() -> RecursivePageTable { - use self::table::P4; - RecursivePageTable { - p4: Unique::new(P4), +impl RecursivePageTable { + pub unsafe fn new() -> RecursivePageTable { + RecursivePageTable { + p4: Unique::new(table::P4), + } } } ``` @@ -628,6 +648,8 @@ Now we can make the `map_to` and `translate` functions safe by making them metho ```rust impl RecursivePageTable { + pub unsafe fn new() -> RecursivePageTable {...} + fn p4(&self) -> &Table {...} fn p4_mut(&mut self) -> &mut Table {...} @@ -656,7 +678,7 @@ impl RecursivePageTable { } } ``` -Now the `p4()` and `p4_mut()` methods should be the only methods containing an `unsafe` block. +Now the `p4()` and `p4_mut()` methods should be the only methods containing an `unsafe` block in the `paging/mod.rs` file. ### More Mapping Functions From 9019309d40422f81fc3a297dfbf4958727b0c244 Mon Sep 17 00:00:00 2001 From: Philipp Oppermann Date: Wed, 9 Dec 2015 02:07:48 +0100 Subject: [PATCH 53/76] Many wording improvements --- posts/DRAFT-paging.md | 28 ++++++++++++++++------------ 1 file changed, 16 insertions(+), 12 deletions(-) diff --git a/posts/DRAFT-paging.md b/posts/DRAFT-paging.md index f32132d4..9d75fc7f 100644 --- a/posts/DRAFT-paging.md +++ b/posts/DRAFT-paging.md @@ -207,14 +207,14 @@ pub fn zero(&mut self) { Now we can read page tables and retrieve the mapping information. We can also update them through the `IndexMut` trait and the `Entry::set` method. But how do we get references to the various page tables? -We could read the `CR3` register to get the physical address of the P4 table and read its entries to get the P3 addresses. The P3 entries then point to the P2 tables and so on. But this method only works for identity-mapped pages. But in the future we will create new page tables, which aren't in the identity-mapped area anymore. Since we can't access them through their physical address, we need a way to map them to virtual addresses. +We could read the `CR3` register to get the physical address of the P4 table and read its entries to get the P3 addresses. The P3 entries then point to the P2 tables and so on. But this method only works for identity-mapped pages. In the future we will create new page tables, which aren't in the identity-mapped area anymore. Since we can't access them through their physical address, we need a way to map them to virtual addresses. ## Mapping Page Tables So how do we map the page tables itself? We don't have that problem for the current P4, P3, and P2 table since they are part of the identity-mapped area, but we need a way to access future tables, too. -One solution is to identity map all page table. That way we would not need to differentiate virtual and physical addresses and could easily access the tables. But it clutters the virtual address space and increases fragmentation. And it makes creating page tables much more complicated since we need a physical frame whose corresponding page isn't already used for something else. +One solution is to identity map all page tables. That way we would not need to differentiate virtual and physical addresses and could easily access the tables. But it clutters the virtual address space and increases fragmentation. And it makes creating page tables much more complicated since we need a physical frame whose corresponding page isn't already used for something else. -An alternative solution is to map the page tables only temporary. So to read/write a page table, we would map it to some free virtual address. We could use a small pool of such virtual addresses and reuse them for various tables. This method occupies only few virtual addresses and is thus a good solution for 32-bit systems, which have small address spaces. But it makes things much more complicated since we need to temporary map up to 4 tables to access a single page. And the temporary mapping requires modification of other page tables, which need to be mapped, too. +An alternative solution is to map the page tables only temporary. To read/write a page table, we would map it to some free virtual address until we're done. We could use a small pool of such virtual addresses and reuse them for various tables. This method occupies only few virtual addresses and thus is a good solution for 32-bit systems, which have small address spaces. But it makes things much more complicated since we need to temporary map up to 4 tables to access a single page. And the temporary mapping requires modification of other page tables, which need to be mapped, too. We will solve the problem in another way using a trick called _recursive mapping_. @@ -239,10 +239,10 @@ So we can access and modify page tables of all levels by just setting one P4 ent By using recursive mapping, each page table is accessible through an unique virtual address. The math checks out, too: If all page tables are used, there is 1 P4 table, 511 P3 tables (the last entry is used for the recursive mapping), `511*512` P2 tables, and `511*512*512` P1 tables. So there are `134217728` page tables altogether. Each page table occupies 4KiB, so we need `134217728 * 4KiB = 512GiB` to store them. That's exactly the amount of memory that can be accessed through one P4 entry since `4KiB per page * 512 P1 entries * 512 P2 entries * 512 P3 entries = 512GiB`. -Of course recursive mapping has some disadvantages, too. It occupies a whole P4 and thus 512GiB of the virtual address space. But since we're in long mode and have a 48-bit address space, there are still 225.5TiB left. The bigger problem is that only the active table can be modified by default. To access another table, the recursive entry needs to be replaced temporary. We will tackle this problem in the next post when we switch to a new page table. +Of course recursive mapping has some disadvantages, too. It occupies a P4 entry and thus 512GiB of the virtual address space. But since we're in long mode and have a 48-bit address space, there are still 225.5TiB left. The bigger problem is that only the active table can be modified by default. To access another table, the recursive entry needs to be replaced temporary. We will tackle this problem in the next post when we switch to a new page table. ### Implementation -To map the P4 table recursively, we just need to point the 511th entry to the table itself. Of course we could do it in Rust, but it would require some unsafe pointer fiddling. It's easier to just add some lines to our boot assembly: +To map the P4 table recursively, we just need to point the 511th entry to the table itself. Of course we could do it in Rust, but it would require some fiddling with unsafe pointers. It's easier to just add some lines to our boot assembly: ```nasm mov eax, p4_table @@ -321,7 +321,7 @@ Well, they would calculate the address of the next table (which does not exist) [^fn-invalid-address]: If the `XXX` part of the address is smaller than `0o400`, it's binary representation doesn't start with `1`. But the sign extension bits, which should be a copy of that bit, are `1` instead of `0`. Thus the address is not valid. ## Some Clever Solution -We can use Rust's type system to statically guarantee that the methods can only be called on P4, P3, and P2 tables. The idea is to add a `Level` parameter to the `Table` type and implement the `next_table` methods only for level 4, 3, and 2. +We can use Rust's type system to statically guarantee that the `next_table` methods can only be called on P4, P3, and P2 tables, but not on a P1 table. The idea is to add a `Level` parameter to the `Table` type and implement the `next_table` methods only for level 4, 3, and 2. To model the levels we use a trait and empty enums: @@ -353,12 +353,14 @@ impl HierachicalLevel for Level2 {} Now we add the level parameter to the `Table` type: ```rust +use core::marker::PhantomData; + pub struct Table { entries: [Entry; ENTRY_COUNT], level: PhantomData, } ``` -We need to add a [PhantomData] field because unused type parameters are not allowed in Rust. (You need to add a `core::marker::PhantomData` import.) +We need to add a [PhantomData] field because unused type parameters are not allowed in Rust. [PhantomData]: https://doc.rust-lang.org/core/marker/struct.PhantomData.html#unused-type-parameters @@ -485,7 +487,9 @@ fn translate_page(page: Page) -> Option { .or_else(huge_page) } ``` -We use an unsafe block to convert the raw `P4` pointer to a reference. Then we use the [Option::and_then] function to go through the four table levels. If some entry along the way is `None`, we check if the page is a huge page through the (unimplemented) `huge_page` closure. The `Page::p*_index` functions look like this: +We use an unsafe block to convert the raw `P4` pointer to a reference. Then we use the [Option::and_then] function to go through the four table levels. If some entry along the way is `None`, we check if the page is a huge page through the (unimplemented) `huge_page` closure. + +The `Page::p*_index` functions return the different table indexes. They look like this: [Option::and_then]: https://doc.rust-lang.org/nightly/core/option/enum.Option.html#method.and_then @@ -544,7 +548,7 @@ p3.and_then(|p3| { This function is much longer and more complex than the `translate_page` function itself. To avoid this complexity in the future, we will only work with standard 4KiB pages from now on. ## Mapping Pages -TODO imports +TODO introduction/motivation Let's add a function that maps a `Page` to some `Frame`: @@ -630,7 +634,7 @@ impl RecursivePageTable { } ``` -We add some functions to get a P4 reference: +We add some methods to get P4 references: ```rust fn p4(&self) -> &Table { @@ -642,7 +646,7 @@ fn p4_mut(&mut self) -> &mut Table { } ``` -Since we will only create valid P4 pointers, the `unsafe` blocks are safe. However, we don't make these functions public since they could be used to make page tables invalid. Only the higher level functions (such as `translate` or `map_to`) should be usable from other modules. +Since we will only create valid P4 pointers, the `unsafe` blocks are safe. However, we don't make these functions public since they can be used to make page tables invalid. Only the higher level functions (such as `translate` or `map_to`) should be usable from other modules. Now we can make the `map_to` and `translate` functions safe by making them methods of `RecursivePageTable`: @@ -729,7 +733,7 @@ fn unmap(&mut self, page: Page, allocator: &mut A) ``` The assertion ensures that the page is mapped. Thus the corresponding P1 table and frame must exist for a standard 4KiB page. We set the entry to unused and free the associated frame in the supplied frame allocator. -We can also free the P1, P2, or even P3 table when the last entry is freed. But checking the whole table on every `unmap` would be very expensive. For now, we leave the `TODO` in place until we find a good solution. I'm open for suggestions :). +We can also free the P1, P2, or even P3 table when the last entry is freed. But checking the whole table on every `unmap` would be very expensive. So we leave the `TODO` in place until we find a good solution. I'm open for suggestions :). _Spoiler_: There is an ugly bug in this function, which we will find in the next section. From dabef43db952c1d7e2a1958d50d226641e692dcc Mon Sep 17 00:00:00 2001 From: Philipp Oppermann Date: Mon, 7 Dec 2015 18:33:53 +0100 Subject: [PATCH 54/76] Reset src to master to be able to follow step-by-stp --- Cargo.toml | 7 - src/arch/x86_64/boot.asm | 7 +- src/lib.rs | 30 ++--- src/memory/mod.rs | 8 +- src/memory/paging/entry.rs | 46 ------- src/memory/paging/mapping.rs | 24 ---- src/memory/paging/mod.rs | 225 --------------------------------- src/memory/paging/table.rs | 105 --------------- src/memory/paging/translate.rs | 50 -------- 9 files changed, 10 insertions(+), 492 deletions(-) delete mode 100644 src/memory/paging/entry.rs delete mode 100644 src/memory/paging/mapping.rs delete mode 100644 src/memory/paging/mod.rs delete mode 100644 src/memory/paging/table.rs delete mode 100644 src/memory/paging/translate.rs diff --git a/Cargo.toml b/Cargo.toml index 1470612e..7eb63f79 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -12,10 +12,3 @@ spin = "0.3.4" [dependencies.multiboot2] git = "https://github.com/phil-opp/multiboot2-elf64" - -[dependencies.x86] -git = "https://github.com/gz/rust-x86" - -[dependencies.bitflags] -git = "https://github.com/phil-opp/bitflags.git" -branch = "no_std" diff --git a/src/arch/x86_64/boot.asm b/src/arch/x86_64/boot.asm index 7035fc17..2f5295d7 100644 --- a/src/arch/x86_64/boot.asm +++ b/src/arch/x86_64/boot.asm @@ -42,11 +42,6 @@ start: jmp gdt64.code:long_mode_start setup_page_tables: - ; recursive map P4 - 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 @@ -156,7 +151,7 @@ p3_table: p2_table: resb 4096 stack_bottom: - resb 4096 * 2 + resb 4096 stack_top: section .rodata diff --git a/src/lib.rs b/src/lib.rs index 49ffa893..e0ae38fc 100644 --- a/src/lib.rs +++ b/src/lib.rs @@ -13,16 +13,12 @@ // limitations under the License. #![feature(no_std, lang_items)] -#![feature(const_fn, unique, core_str_ext, iter_cmp, optin_builtin_traits)] -#![feature(core_slice_ext)] +#![feature(const_fn, unique, core_str_ext, iter_cmp)] #![no_std] extern crate rlibc; extern crate spin; extern crate multiboot2; -extern crate x86; -#[macro_use] -extern crate bitflags; #[macro_use] mod vga_buffer; @@ -61,23 +57,13 @@ pub extern fn rust_main(multiboot_information_address: 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()); - - // println!("outer {}", {println!("inner"); "NO DEADLOCK"}); - /*println!("{:?}", memory::paging::translate::translate(0));*/ - - println!("{:?}", memory::paging::translate::translate(0)); - println!("{:?}", memory::paging::translate::translate(0x40000000)); - println!("{:?}", memory::paging::translate::translate(0x40000000 - 1)); - println!("{:?}", memory::paging::translate::translate(0xdeadbeaa000)); - println!("{:?}", memory::paging::translate::translate(0xcafebeaf000)); - memory::paging::test(&mut frame_allocator); - println!("{:x}", memory::paging::translate::translate(0xdeadbeaa000).unwrap()); - println!("{:x}", memory::paging::translate::translate(0xdeadbeab000).unwrap()); - println!("{:x}", memory::paging::translate::translate(0xdeadbeac000).unwrap()); - println!("{:x}", memory::paging::translate::translate(0xdeadbead000).unwrap()); - println!("{:x}", memory::paging::translate::translate(0xcafebeaf000).unwrap()); - - + for i in 0.. { + use memory::FrameAllocator; + if let None = frame_allocator.allocate_frame() { + println!("allocated {} frames", i); + break; + } + } loop{} } diff --git a/src/memory/mod.rs b/src/memory/mod.rs index 7bf5033d..7bfc85d8 100644 --- a/src/memory/mod.rs +++ b/src/memory/mod.rs @@ -1,7 +1,5 @@ pub use self::area_frame_allocator::AreaFrameAllocator; -use self::paging::PhysicalAddress; -pub mod paging; mod area_frame_allocator; pub const PAGE_SIZE: usize = 4096; @@ -13,11 +11,7 @@ pub struct Frame { impl Frame { fn containing_address(address: usize) -> Frame { - Frame { number: address / PAGE_SIZE } - } - - fn start_address(&self) -> PhysicalAddress { - self.number * PAGE_SIZE + Frame{ number: address / PAGE_SIZE } } } diff --git a/src/memory/paging/entry.rs b/src/memory/paging/entry.rs deleted file mode 100644 index ce6f2c82..00000000 --- a/src/memory/paging/entry.rs +++ /dev/null @@ -1,46 +0,0 @@ -use memory::Frame; -use memory::paging::PhysicalAddress; - -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 { - 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! { - 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, - } -} diff --git a/src/memory/paging/mapping.rs b/src/memory/paging/mapping.rs deleted file mode 100644 index faa7c602..00000000 --- a/src/memory/paging/mapping.rs +++ /dev/null @@ -1,24 +0,0 @@ -use memory::Frame; -use super::Page; -use super::entry::{EntryFlags, PRESENT}; -use memory::FrameAllocator; -use super::table::P4; - -pub fn map(page: &Page, flags: EntryFlags, allocator: &mut A) - where A: FrameAllocator -{ - let frame = allocator.allocate_frame().expect("out of memory"); - map_to(page, frame, flags, allocator) -} - -pub fn map_to(page: &Page, frame: Frame, flags: EntryFlags, allocator: &mut A) - where A: FrameAllocator -{ - let p4 = unsafe { &mut *P4 }; - 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); -} diff --git a/src/memory/paging/mod.rs b/src/memory/paging/mod.rs deleted file mode 100644 index 9f6c12fb..00000000 --- a/src/memory/paging/mod.rs +++ /dev/null @@ -1,225 +0,0 @@ -use core::ptr::Unique; -use memory::{PAGE_SIZE, Frame, FrameAllocator}; -use self::table::{Table, Level4}; -use self::entry::*; - -mod entry; -mod table; -pub mod translate; -pub mod mapping; - -pub fn test(frame_allocator: &mut A) - where A: super::FrameAllocator -{ - use self::entry::PRESENT; - mapping::map(&Page::containing_address(0xdeadbeaa000), - PRESENT, - frame_allocator); - mapping::map(&Page::containing_address(0xdeadbeab000), - PRESENT, - frame_allocator); - mapping::map(&Page::containing_address(0xdeadbeac000), - PRESENT, - frame_allocator); - mapping::map(&Page::containing_address(0xdeadbead000), - PRESENT, - frame_allocator); - mapping::map(&Page::containing_address(0xcafebeaf000), - PRESENT, - frame_allocator); - mapping::map(&Page::containing_address(0x0), PRESENT, frame_allocator); -} - -const ENTRY_COUNT: usize = 512; - -pub type PhysicalAddress = usize; -pub type VirtualAddress = usize; - -pub struct Page { - number: usize, -} - -impl Page { - 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) -> VirtualAddress { - 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 RecursivePageTable { - p4: Unique>, -} - -impl RecursivePageTable { - pub unsafe fn new() -> RecursivePageTable { - use self::table::P4; - RecursivePageTable { - p4: Unique::new(P4), - } - } - - fn p4(&self) -> &Table { - unsafe { self.p4.get() } - } - - fn p4_mut(&mut self) -> &mut Table { - unsafe { self.p4.get_mut() } - } - - pub fn translate(&self, virtual_address: VirtualAddress) -> Option { - 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 { - 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(&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 map_to(&mut self, page: Page, frame: Frame, flags: EntryFlags, allocator: &mut A) - where A: FrameAllocator - { - let mut p3 = self.p4_mut().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()].flags().contains(PRESENT)); - p1[page.p1_index()].set(frame, flags | PRESENT); - } - - pub fn identity_map(&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(&mut self, page: Page, allocator: &mut A) - where A: FrameAllocator - { - use x86::tlb; - - 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(); - unsafe { tlb::flush(page.start_address()) }; - // TODO free p(1,2,3) table if empty - allocator.deallocate_frame(frame); - } -} - -pub struct InactivePageTable { - p4_frame: Frame, // recursive mapped -} - -impl InactivePageTable { - pub fn create_new_on_identity_mapped_frame(&self, - identity_mapped_frame: Frame) - -> InactivePageTable { - let page_address = Page { number: identity_mapped_frame.number }.start_address(); - // frame must be identity mapped - assert!(self.read(|lock| lock.translate(page_address)) == Some(page_address)); - - let table = unsafe { &mut *(page_address as *mut Table) }; - table[511].set(Frame { number: identity_mapped_frame.number }, WRITABLE); - InactivePageTable { p4_frame: identity_mapped_frame } - } - - pub fn read(&self, f: F) -> R - where F: FnOnce(&RecursivePageTable) -> R - { - self.activate_temporary(|pt| f(pt)) - } - - pub fn modify(&mut self, f: F) - where F: FnOnce(&mut RecursivePageTable) - { - self.activate_temporary(f) - } - - fn activate_temporary(&self, f: F) -> R - where F: FnOnce(&mut RecursivePageTable) -> R - { - use memory::paging::table::P4; - - let mut page_table = RecursivePageTable { p4: unsafe { Unique::new(P4) } }; - - let backup = page_table.p4()[511].pointed_frame().unwrap(); - if backup == self.p4_frame { - f(&mut page_table) - } else { - page_table.p4_mut()[511] - .set(Frame { number: self.p4_frame.number }, PRESENT | WRITABLE); - let ret = f(&mut page_table); - page_table.p4_mut()[511].set(backup, PRESENT | WRITABLE); - ret - } - } -} diff --git a/src/memory/paging/table.rs b/src/memory/paging/table.rs deleted file mode 100644 index e81f671d..00000000 --- a/src/memory/paging/table.rs +++ /dev/null @@ -1,105 +0,0 @@ -use memory::FrameAllocator; -use memory::paging::ENTRY_COUNT; -use memory::paging::entry::*; -use core::ops::{Index, IndexMut}; -use core::marker::PhantomData; - -pub const P4: *mut Table = 0xffffffff_fffff000 as *mut _; - -pub struct Table { - entries: [Entry; ENTRY_COUNT], - level: PhantomData, -} - -impl Table where L: TableLevel -{ - pub fn zero(&mut self) { - for entry in self.entries.iter_mut() { - entry.set_unused(); - } - } -} - -impl Table where L: HierachicalLevel -{ - pub fn next_table(&self, index: usize) -> Option<&Table> { - self.next_table_address(index).map(|t| unsafe { &*(t as *const _) }) - } - - pub fn next_table_mut(&mut self, index: usize) -> Option<&mut Table> { - self.next_table_address(index).map(|t| unsafe { &mut *(t as *mut _) }) - } - - pub fn next_table_create(&mut self, - index: usize, - allocator: &mut A) - -> &mut Table - 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() - } - - fn next_table_address(&self, index: usize) -> Option { - 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 - } - } -} - -impl Index for Table where L: TableLevel -{ - type Output = Entry; - - fn index(&self, index: usize) -> &Entry { - &self.entries[index] - } -} - -impl IndexMut for Table where L: TableLevel -{ - fn index_mut(&mut self, index: usize) -> &mut Entry { - &mut self.entries[index] - } -} - -pub trait TableLevel {} - -pub enum Level4 {} -#[allow(dead_code)] -enum Level3 {} -#[allow(dead_code)] -enum Level2 {} -#[allow(dead_code)] -enum Level1 {} - -impl TableLevel for Level4 {} -impl TableLevel for Level3 {} -impl TableLevel for Level2 {} -impl TableLevel for Level1 {} - -trait HierachicalLevel: TableLevel { - type NextLevel: TableLevel; -} - -impl HierachicalLevel for Level4 { - type NextLevel = Level3; -} - -impl HierachicalLevel for Level3 { - type NextLevel = Level2; -} - -impl HierachicalLevel for Level2 { - type NextLevel = Level1; -} diff --git a/src/memory/paging/translate.rs b/src/memory/paging/translate.rs deleted file mode 100644 index 73dda190..00000000 --- a/src/memory/paging/translate.rs +++ /dev/null @@ -1,50 +0,0 @@ -use super::{VirtualAddress, PhysicalAddress, Page, ENTRY_COUNT}; -use super::table::P4; -use super::entry::HUGE_PAGE; -use memory::{PAGE_SIZE, Frame}; - -pub fn translate(virtual_address: VirtualAddress) -> Option { - let offset = virtual_address % PAGE_SIZE; - translate_page(Page::containing_address(virtual_address)) - .map(|frame| frame.number * PAGE_SIZE + offset) -} - -fn translate_page(page: Page) -> Option { - let p4 = unsafe { &*P4 }; - - let huge_page = || { - p4.next_table(page.p4_index()) - .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 - }) - }; - - p4.next_table(page.p4_index()) - .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) -} From d827f51bb68c8c179a29a7d8f8e03860ee04ef08 Mon Sep 17 00:00:00 2001 From: Philipp Oppermann Date: Tue, 8 Dec 2015 22:14:42 +0100 Subject: [PATCH 55/76] Create basic paging module --- src/memory/mod.rs | 3 ++- src/memory/paging/mod.rs | 8 ++++++++ 2 files changed, 10 insertions(+), 1 deletion(-) create mode 100644 src/memory/paging/mod.rs diff --git a/src/memory/mod.rs b/src/memory/mod.rs index 7bfc85d8..7287b464 100644 --- a/src/memory/mod.rs +++ b/src/memory/mod.rs @@ -1,6 +1,7 @@ pub use self::area_frame_allocator::AreaFrameAllocator; mod area_frame_allocator; +mod paging; pub const PAGE_SIZE: usize = 4096; @@ -11,7 +12,7 @@ pub struct Frame { impl Frame { fn containing_address(address: usize) -> Frame { - Frame{ number: address / PAGE_SIZE } + Frame { number: address / PAGE_SIZE } } } diff --git a/src/memory/paging/mod.rs b/src/memory/paging/mod.rs new file mode 100644 index 00000000..ae5b85fd --- /dev/null +++ b/src/memory/paging/mod.rs @@ -0,0 +1,8 @@ +const ENTRY_COUNT: usize = 512; + +pub type PhysicalAddress = usize; +pub type VirtualAddress = usize; + +pub struct Page { + number: usize, +} From 14384fb27f4d50f7e985bfc9511e94c75aec8bfb Mon Sep 17 00:00:00 2001 From: Philipp Oppermann Date: Tue, 8 Dec 2015 22:15:32 +0100 Subject: [PATCH 56/76] Model page table entries --- Cargo.toml | 4 ++++ src/lib.rs | 2 ++ src/memory/mod.rs | 5 +++++ src/memory/paging/entry.rs | 45 ++++++++++++++++++++++++++++++++++++++ src/memory/paging/mod.rs | 2 ++ 5 files changed, 58 insertions(+) create mode 100644 src/memory/paging/entry.rs diff --git a/Cargo.toml b/Cargo.toml index 7eb63f79..2738b730 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -12,3 +12,7 @@ spin = "0.3.4" [dependencies.multiboot2] git = "https://github.com/phil-opp/multiboot2-elf64" + +[dependencies.bitflags] +git = "https://github.com/phil-opp/bitflags.git" +branch = "no_std" diff --git a/src/lib.rs b/src/lib.rs index e0ae38fc..2350abfd 100644 --- a/src/lib.rs +++ b/src/lib.rs @@ -19,6 +19,8 @@ extern crate rlibc; extern crate spin; extern crate multiboot2; +#[macro_use] +extern crate bitflags; #[macro_use] mod vga_buffer; diff --git a/src/memory/mod.rs b/src/memory/mod.rs index 7287b464..064be1c1 100644 --- a/src/memory/mod.rs +++ b/src/memory/mod.rs @@ -1,4 +1,5 @@ pub use self::area_frame_allocator::AreaFrameAllocator; +use self::paging::PhysicalAddress; mod area_frame_allocator; mod paging; @@ -14,6 +15,10 @@ 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 { diff --git a/src/memory/paging/entry.rs b/src/memory/paging/entry.rs new file mode 100644 index 00000000..071ee277 --- /dev/null +++ b/src/memory/paging/entry.rs @@ -0,0 +1,45 @@ +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 { + 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! { + 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, + } +} diff --git a/src/memory/paging/mod.rs b/src/memory/paging/mod.rs index ae5b85fd..d0e7145f 100644 --- a/src/memory/paging/mod.rs +++ b/src/memory/paging/mod.rs @@ -1,3 +1,5 @@ +mod entry; + const ENTRY_COUNT: usize = 512; pub type PhysicalAddress = usize; From 96b0dc0c66bf7dfc860e78775ee8962a4ee32391 Mon Sep 17 00:00:00 2001 From: Philipp Oppermann Date: Tue, 8 Dec 2015 22:47:39 +0100 Subject: [PATCH 57/76] Model page tables --- src/memory/paging/mod.rs | 1 + src/memory/paging/table.rs | 29 +++++++++++++++++++++++++++++ 2 files changed, 30 insertions(+) create mode 100644 src/memory/paging/table.rs diff --git a/src/memory/paging/mod.rs b/src/memory/paging/mod.rs index d0e7145f..53ad489b 100644 --- a/src/memory/paging/mod.rs +++ b/src/memory/paging/mod.rs @@ -1,4 +1,5 @@ mod entry; +mod table; const ENTRY_COUNT: usize = 512; diff --git a/src/memory/paging/table.rs b/src/memory/paging/table.rs new file mode 100644 index 00000000..0a1935e2 --- /dev/null +++ b/src/memory/paging/table.rs @@ -0,0 +1,29 @@ +use memory::paging::entry::*; +use memory::paging::ENTRY_COUNT; +use core::ops::{Index, IndexMut}; + +pub struct Table { + entries: [Entry; ENTRY_COUNT], +} + +impl Table { + pub fn zero(&mut self) { + for entry in self.entries.iter_mut() { + entry.set_unused(); + } + } +} + +impl Index for Table { + type Output = Entry; + + fn index(&self, index: usize) -> &Entry { + &self.entries[index] + } +} + +impl IndexMut for Table { + fn index_mut(&mut self, index: usize) -> &mut Entry { + &mut self.entries[index] + } +} From e071c2468067c77e8672755082cc4006e33d11ee Mon Sep 17 00:00:00 2001 From: Philipp Oppermann Date: Wed, 9 Dec 2015 00:10:50 +0100 Subject: [PATCH 58/76] Recursive map the P4 table --- src/arch/x86_64/boot.asm | 5 +++++ src/memory/paging/table.rs | 2 ++ 2 files changed, 7 insertions(+) diff --git a/src/arch/x86_64/boot.asm b/src/arch/x86_64/boot.asm index 2f5295d7..4abb2899 100644 --- a/src/arch/x86_64/boot.asm +++ b/src/arch/x86_64/boot.asm @@ -42,6 +42,11 @@ start: jmp gdt64.code:long_mode_start setup_page_tables: + ; recursive map P4 + 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 diff --git a/src/memory/paging/table.rs b/src/memory/paging/table.rs index 0a1935e2..41d92742 100644 --- a/src/memory/paging/table.rs +++ b/src/memory/paging/table.rs @@ -2,6 +2,8 @@ use memory::paging::entry::*; use memory::paging::ENTRY_COUNT; use core::ops::{Index, IndexMut}; +pub const P4: *mut Table = 0xffffffff_fffff000 as *mut _; + pub struct Table { entries: [Entry; ENTRY_COUNT], } From e5a4114262731a720d17179a20469f05372ce2a5 Mon Sep 17 00:00:00 2001 From: Philipp Oppermann Date: Wed, 9 Dec 2015 00:12:55 +0100 Subject: [PATCH 59/76] Add unsafe next_table methods --- src/memory/paging/table.rs | 20 ++++++++++++++++++++ 1 file changed, 20 insertions(+) diff --git a/src/memory/paging/table.rs b/src/memory/paging/table.rs index 41d92742..4ede5f9e 100644 --- a/src/memory/paging/table.rs +++ b/src/memory/paging/table.rs @@ -14,6 +14,26 @@ impl Table { entry.set_unused(); } } + + fn next_table_address(&self, index: usize) -> Option { + 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 unsafe fn next_table(&self, index: usize) -> Option<&Table> { + self.next_table_address(index) + .map(|t| unsafe { &*(t as *const _) }) + } + + pub unsafe fn next_table_mut(&mut self, index: usize) -> Option<&mut Table> { + self.next_table_address(index) + .map(|t| unsafe { &mut *(t as *mut _) }) + } } impl Index for Table { From d267ac1c98e996da154811c4e83a10b2730c0503 Mon Sep 17 00:00:00 2001 From: Philipp Oppermann Date: Wed, 9 Dec 2015 00:21:10 +0100 Subject: [PATCH 60/76] Define the next_table methods only for P4, P3, and P2 tables --- src/memory/paging/table.rs | 50 ++++++++++++++++++++++++++++++++------ 1 file changed, 43 insertions(+), 7 deletions(-) diff --git a/src/memory/paging/table.rs b/src/memory/paging/table.rs index 4ede5f9e..baad8523 100644 --- a/src/memory/paging/table.rs +++ b/src/memory/paging/table.rs @@ -1,20 +1,26 @@ use memory::paging::entry::*; use memory::paging::ENTRY_COUNT; use core::ops::{Index, IndexMut}; +use core::marker::PhantomData; -pub const P4: *mut Table = 0xffffffff_fffff000 as *mut _; +pub const P4: *mut Table = 0xffffffff_fffff000 as *mut _; -pub struct Table { +pub struct Table { entries: [Entry; ENTRY_COUNT], + level: PhantomData, } -impl Table { +impl Table where L: TableLevel +{ pub fn zero(&mut self) { for entry in self.entries.iter_mut() { entry.set_unused(); } } +} +impl Table where L: HierachicalLevel +{ fn next_table_address(&self, index: usize) -> Option { let entry_flags = self[index].flags(); if entry_flags.contains(PRESENT) && !entry_flags.contains(HUGE_PAGE) { @@ -25,18 +31,19 @@ impl Table { } } - pub unsafe fn next_table(&self, index: usize) -> Option<&Table> { + pub fn next_table(&self, index: usize) -> Option<&Table> { self.next_table_address(index) .map(|t| unsafe { &*(t as *const _) }) } - pub unsafe fn next_table_mut(&mut self, index: usize) -> Option<&mut Table> { + pub fn next_table_mut(&mut self, index: usize) -> Option<&mut Table> { self.next_table_address(index) .map(|t| unsafe { &mut *(t as *mut _) }) } } -impl Index for Table { +impl Index for Table where L: TableLevel +{ type Output = Entry; fn index(&self, index: usize) -> &Entry { @@ -44,8 +51,37 @@ impl Index for Table { } } -impl IndexMut for Table { +impl IndexMut for Table where L: TableLevel +{ fn index_mut(&mut self, index: usize) -> &mut Entry { &mut self.entries[index] } } + +pub trait TableLevel {} + +pub enum Level4 {} +enum Level3 {} +enum Level2 {} +enum Level1 {} + +impl TableLevel for Level4 {} +impl TableLevel for Level3 {} +impl TableLevel for Level2 {} +impl TableLevel for Level1 {} + +trait HierachicalLevel: TableLevel { + type NextLevel: TableLevel; +} + +impl HierachicalLevel for Level4 { + type NextLevel = Level3; +} + +impl HierachicalLevel for Level3 { + type NextLevel = Level2; +} + +impl HierachicalLevel for Level2 { + type NextLevel = Level1; +} From e84344f59ae36cb11782f5d8157d2346ad2dfd94 Mon Sep 17 00:00:00 2001 From: Philipp Oppermann Date: Wed, 9 Dec 2015 00:34:19 +0100 Subject: [PATCH 61/76] Add function to translate virtual to physical address --- src/memory/paging/mod.rs | 74 ++++++++++++++++++++++++++++++++++++++++ 1 file changed, 74 insertions(+) diff --git a/src/memory/paging/mod.rs b/src/memory/paging/mod.rs index 53ad489b..9c2cc37b 100644 --- a/src/memory/paging/mod.rs +++ b/src/memory/paging/mod.rs @@ -1,3 +1,5 @@ +use memory::{PAGE_SIZE, Frame}; + mod entry; mod table; @@ -9,3 +11,75 @@ pub type VirtualAddress = usize; pub struct Page { number: usize, } + +impl Page { + 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 fn translate(virtual_address: VirtualAddress) -> Option { + let offset = virtual_address % PAGE_SIZE; + translate_page(Page::containing_address(virtual_address)) + .map(|frame| frame.number * PAGE_SIZE + offset) +} + +fn translate_page(page: Page) -> Option { + use self::entry::HUGE_PAGE; + + let p3 = unsafe { &*table::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) +} From 46b93e0650b462c3eaf3369dd2ef83d3005d6ee7 Mon Sep 17 00:00:00 2001 From: Philipp Oppermann Date: Wed, 9 Dec 2015 00:51:34 +0100 Subject: [PATCH 62/76] Add unsafe map_to function and make translate unsafe, too These functions are unsafe because it's possible to get aliased &mut references. --- src/memory/paging/mod.rs | 20 ++++++++++++++++---- src/memory/paging/table.rs | 17 +++++++++++++++++ 2 files changed, 33 insertions(+), 4 deletions(-) diff --git a/src/memory/paging/mod.rs b/src/memory/paging/mod.rs index 9c2cc37b..f541910e 100644 --- a/src/memory/paging/mod.rs +++ b/src/memory/paging/mod.rs @@ -1,4 +1,5 @@ -use memory::{PAGE_SIZE, Frame}; +pub use self::entry::*; +use memory::{PAGE_SIZE, Frame, FrameAllocator}; mod entry; mod table; @@ -38,14 +39,13 @@ impl Page { } } -pub fn translate(virtual_address: VirtualAddress) -> Option { +pub unsafe fn translate(virtual_address: VirtualAddress) -> Option { let offset = virtual_address % PAGE_SIZE; translate_page(Page::containing_address(virtual_address)) .map(|frame| frame.number * PAGE_SIZE + offset) } -fn translate_page(page: Page) -> Option { - use self::entry::HUGE_PAGE; +unsafe fn translate_page(page: Page) -> Option { let p3 = unsafe { &*table::P4 }.next_table(page.p4_index()); @@ -83,3 +83,15 @@ fn translate_page(page: Page) -> Option { .and_then(|p1| p1[page.p1_index()].pointed_frame()) .or_else(huge_page) } + +pub unsafe fn map_to(page: Page, frame: Frame, flags: EntryFlags, allocator: &mut A) + where A: FrameAllocator +{ + let p4 = unsafe { &mut *table::P4 }; + 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); +} diff --git a/src/memory/paging/table.rs b/src/memory/paging/table.rs index baad8523..b1a45d06 100644 --- a/src/memory/paging/table.rs +++ b/src/memory/paging/table.rs @@ -1,5 +1,6 @@ use memory::paging::entry::*; use memory::paging::ENTRY_COUNT; +use memory::FrameAllocator; use core::ops::{Index, IndexMut}; use core::marker::PhantomData; @@ -40,6 +41,22 @@ impl Table where L: HierachicalLevel self.next_table_address(index) .map(|t| unsafe { &mut *(t as *mut _) }) } + + pub fn next_table_create(&mut self, + index: usize, + allocator: &mut A) + -> &mut Table + 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 Index for Table where L: TableLevel From fb7d2d22b63063be06c82b2800ff96cb506e1348 Mon Sep 17 00:00:00 2001 From: Philipp Oppermann Date: Wed, 9 Dec 2015 00:55:53 +0100 Subject: [PATCH 63/76] Add RecursivePageTable as an owner for the P4 table --- src/memory/paging/mod.rs | 20 ++++++++++++++++++++ 1 file changed, 20 insertions(+) diff --git a/src/memory/paging/mod.rs b/src/memory/paging/mod.rs index f541910e..f977b513 100644 --- a/src/memory/paging/mod.rs +++ b/src/memory/paging/mod.rs @@ -1,5 +1,7 @@ pub use self::entry::*; use memory::{PAGE_SIZE, Frame, FrameAllocator}; +use self::table::{Table, Level4}; +use core::ptr::Unique; mod entry; mod table; @@ -39,6 +41,24 @@ impl Page { } } +pub struct RecursivePageTable { + p4: Unique>, +} + +impl RecursivePageTable { + pub unsafe fn new() -> RecursivePageTable { + RecursivePageTable { p4: Unique::new(table::P4) } + } + + fn p4(&self) -> &Table { + unsafe { self.p4.get() } + } + + fn p4_mut(&mut self) -> &mut Table { + unsafe { self.p4.get_mut() } + } +} + pub unsafe fn translate(virtual_address: VirtualAddress) -> Option { let offset = virtual_address % PAGE_SIZE; translate_page(Page::containing_address(virtual_address)) From 86d8e99271b46edd6183e09b920fad8754556e75 Mon Sep 17 00:00:00 2001 From: Philipp Oppermann Date: Wed, 9 Dec 2015 01:01:32 +0100 Subject: [PATCH 64/76] Make translate and map_to safe by making them RecursivePageTable methods --- src/memory/paging/mod.rs | 100 +++++++++++++++++++-------------------- 1 file changed, 49 insertions(+), 51 deletions(-) diff --git a/src/memory/paging/mod.rs b/src/memory/paging/mod.rs index f977b513..c991fba5 100644 --- a/src/memory/paging/mod.rs +++ b/src/memory/paging/mod.rs @@ -57,61 +57,59 @@ impl RecursivePageTable { fn p4_mut(&mut self) -> &mut Table { unsafe { self.p4.get_mut() } } -} -pub unsafe fn translate(virtual_address: VirtualAddress) -> Option { - let offset = virtual_address % PAGE_SIZE; - translate_page(Page::containing_address(virtual_address)) - .map(|frame| frame.number * PAGE_SIZE + offset) -} + pub fn translate(&self, virtual_address: VirtualAddress) -> Option { + let offset = virtual_address % PAGE_SIZE; + self.translate_page(Page::containing_address(virtual_address)) + .map(|frame| frame.number * PAGE_SIZE + offset) + } -unsafe fn translate_page(page: Page) -> Option { + fn translate_page(&self, page: Page) -> Option { + let p3 = self.p4().next_table(page.p4_index()); - let p3 = unsafe { &*table::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() }); + 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(), + }); } } - } - None - }) - }; + 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 unsafe fn map_to(page: Page, frame: Frame, flags: EntryFlags, allocator: &mut A) - where A: FrameAllocator -{ - let p4 = unsafe { &mut *table::P4 }; - 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); + 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(&mut self, page: Page, frame: Frame, flags: EntryFlags, allocator: &mut A) + where A: FrameAllocator + { + let mut p3 = self.p4_mut().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); + } } From d52c79b106b7d1b1522c862562bdc1c8db0158e4 Mon Sep 17 00:00:00 2001 From: Philipp Oppermann Date: Wed, 9 Dec 2015 01:06:41 +0100 Subject: [PATCH 65/76] Add `map` and `identity_map` functions --- src/memory/paging/mod.rs | 14 ++++++++++++++ 1 file changed, 14 insertions(+) diff --git a/src/memory/paging/mod.rs b/src/memory/paging/mod.rs index c991fba5..0fb3cce6 100644 --- a/src/memory/paging/mod.rs +++ b/src/memory/paging/mod.rs @@ -112,4 +112,18 @@ impl RecursivePageTable { assert!(p1[page.p1_index()].is_unused()); p1[page.p1_index()].set(frame, flags | PRESENT); } + + pub fn map(&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(&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) + } } From 4fd8891aff2f5f2320a0d6ccf2655f8820f2fc75 Mon Sep 17 00:00:00 2001 From: Philipp Oppermann Date: Wed, 9 Dec 2015 01:30:25 +0100 Subject: [PATCH 66/76] Add (buggy) unmap function --- src/memory/paging/mod.rs | 16 ++++++++++++++++ 1 file changed, 16 insertions(+) diff --git a/src/memory/paging/mod.rs b/src/memory/paging/mod.rs index 0fb3cce6..0b9c22ed 100644 --- a/src/memory/paging/mod.rs +++ b/src/memory/paging/mod.rs @@ -126,4 +126,20 @@ impl RecursivePageTable { let page = Page::containing_address(frame.start_address()); self.map_to(page, frame, flags, allocator) } + + fn unmap(&mut self, page: Page, allocator: &mut A) + where A: FrameAllocator + { + 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(); + // TODO free p(1,2,3) table if empty + allocator.deallocate_frame(frame); + } } From d8c6b6f5b7433ba8f92467c8e1504f7c14807a16 Mon Sep 17 00:00:00 2001 From: Philipp Oppermann Date: Wed, 9 Dec 2015 12:08:41 +0100 Subject: [PATCH 67/76] Add and call `test_paging` function --- posts/DRAFT-paging.md | 23 ++++++++++++++++++++++- src/lib.rs | 8 +------- src/memory/mod.rs | 1 + src/memory/paging/mod.rs | 6 ++++++ 4 files changed, 30 insertions(+), 8 deletions(-) diff --git a/posts/DRAFT-paging.md b/posts/DRAFT-paging.md index 9d75fc7f..3558c0da 100644 --- a/posts/DRAFT-paging.md +++ b/posts/DRAFT-paging.md @@ -738,8 +738,29 @@ We can also free the P1, P2, or even P3 table when the last entry is freed. But _Spoiler_: There is an ugly bug in this function, which we will find in the next section. ## Testing it -TODO +To test it, we add a `test_paging` function in `memory/paging/mod.rs`: +```rust +pub fn test_paging(allocator: &mut A) + where A: FrameAllocator +{ + let page_table = unsafe { RecursivePageTable::new() }; + + // test it +} +``` +We borrow the frame allocator since we will need it for the mapping functions. To be able to call that function from main, we need to reexport it in `memory/mod.rs`: + +```rust +// in memory/mod.rs +pub use self::paging::test_paging; + +// lib.rs +let mut frame_allocator = ...; +memory::test_paging(&mut frame_allocator); +``` + +TODO ## What's next? In the next post we will extend this module and add a function to modify inactive page tables. Through that function, we will create a new page table hierarchy that maps the kernel correctly using 4KiB pages. Then we will switch to the new table to get a safer kernel environment. diff --git a/src/lib.rs b/src/lib.rs index 2350abfd..35e160e4 100644 --- a/src/lib.rs +++ b/src/lib.rs @@ -59,13 +59,7 @@ pub extern fn rust_main(multiboot_information_address: 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()); - for i in 0.. { - use memory::FrameAllocator; - if let None = frame_allocator.allocate_frame() { - println!("allocated {} frames", i); - break; - } - } + memory::test_paging(&mut frame_allocator); loop{} } diff --git a/src/memory/mod.rs b/src/memory/mod.rs index 064be1c1..ab420af7 100644 --- a/src/memory/mod.rs +++ b/src/memory/mod.rs @@ -1,4 +1,5 @@ pub use self::area_frame_allocator::AreaFrameAllocator; +pub use self::paging::test_paging; use self::paging::PhysicalAddress; mod area_frame_allocator; diff --git a/src/memory/paging/mod.rs b/src/memory/paging/mod.rs index 0b9c22ed..38cda373 100644 --- a/src/memory/paging/mod.rs +++ b/src/memory/paging/mod.rs @@ -143,3 +143,9 @@ impl RecursivePageTable { allocator.deallocate_frame(frame); } } + +pub fn test_paging(allocator: &mut A) + where A: FrameAllocator +{ + let page_table = unsafe { RecursivePageTable::new() }; +} From 786e1d5cab2f3f1e1c32c543b8ac7e0cfd0ce53b Mon Sep 17 00:00:00 2001 From: Philipp Oppermann Date: Wed, 9 Dec 2015 12:20:50 +0100 Subject: [PATCH 68/76] Test translate function --- posts/DRAFT-paging.md | 31 ++++++++++++++++++++++++++++++- src/memory/paging/mod.rs | 7 +++++++ 2 files changed, 37 insertions(+), 1 deletion(-) diff --git a/posts/DRAFT-paging.md b/posts/DRAFT-paging.md index 3558c0da..63f0c103 100644 --- a/posts/DRAFT-paging.md +++ b/posts/DRAFT-paging.md @@ -760,7 +760,36 @@ let mut frame_allocator = ...; memory::test_paging(&mut frame_allocator); ``` -TODO +### translate +First, we translate some addresses: + +```rust +// address 0 is mapped +println!("Some = {:?}", page_table.translate(0)); + // second P1 entry +println!("Some = {:?}", page_table.translate(4096)); +// second P2 entry +println!("Some = {:?}", page_table.translate(512 * 4096)); +// 300th P2 entry +println!("Some = {:?}", page_table.translate(300 * 512 * 4096)); +// second P3 entry +println!("None = {:?}", page_table.translate(512 * 512 * 4096)); +// last mapped byte +println!("Some = {:?}", page_table.translate(512 * 512 * 4096 - 1)); +``` +Currently, the first GiB of the address space is identity-mapped. Thus all addresses in this area should translate to `Some(x)`, where `x` is the virtual address. Only the second last address, `512 * 512 * 4096`, is not in that area and should resolve to `None`. + +But the output shows two `None` lines: + +``` +Some = Some(0) +Some = Some(4096) +Some = Some(2097152) +Some = Some(629145600) +None = None +Some = None +``` +The last line is wrong. But why? ## What's next? In the next post we will extend this module and add a function to modify inactive page tables. Through that function, we will create a new page table hierarchy that maps the kernel correctly using 4KiB pages. Then we will switch to the new table to get a safer kernel environment. diff --git a/src/memory/paging/mod.rs b/src/memory/paging/mod.rs index 38cda373..230d7b05 100644 --- a/src/memory/paging/mod.rs +++ b/src/memory/paging/mod.rs @@ -148,4 +148,11 @@ pub fn test_paging(allocator: &mut A) where A: FrameAllocator { let page_table = unsafe { RecursivePageTable::new() }; + + println!("Some = {:?}", page_table.translate(0)); + println!("Some = {:?}", page_table.translate(4096)); // second P1 entry + println!("Some = {:?}", page_table.translate(512 * 4096)); // second P2 entry + println!("Some = {:?}", page_table.translate(300 * 512 * 4096)); // 300th P2 entry + println!("None = {:?}", page_table.translate(512 * 512 * 4096)); // second P3 entry + println!("Some = {:?}", page_table.translate(512 * 512 * 4096 - 1)); // last mapped byte } From 5b4e457439c8708a5b51ff0ed882a082db8d2eb2 Mon Sep 17 00:00:00 2001 From: Philipp Oppermann Date: Wed, 9 Dec 2015 13:15:22 +0100 Subject: [PATCH 69/76] Double stack size to avoid stack overflow --- posts/DRAFT-paging.md | 26 ++++++++++++++++++++++++++ src/arch/x86_64/boot.asm | 2 +- 2 files changed, 27 insertions(+), 1 deletion(-) diff --git a/posts/DRAFT-paging.md b/posts/DRAFT-paging.md index 63f0c103..8ed169dd 100644 --- a/posts/DRAFT-paging.md +++ b/posts/DRAFT-paging.md @@ -791,6 +791,32 @@ Some = None ``` The last line is wrong. But why? +In fact, all addresses above `344 * 512 * 4096` seem to get translated to `None`. But even worse, there are some wrong translations, too. For example, on my machine `357 * 512 * 4096` translates to roughly `255TiB`: + +``` +Some(280735973961728) +``` +Something is terribly wrong here. But it's not our code. + +The reason for this bug is a silent stack overflow. Remember, our `.bss` section in the `boot.asm` file looks like this: + +```nasm +section .bss +align 4096 +p4_table: + resb 4096 +p3_table: + resb 4096 +p2_table: + resb 4096 +stack_bottom: + resb 4096 +stack_top: +``` +So a stack overflow overwrites the P2 table, starting at the last entry. But the CPU still uses the memory as page table entries. And if the stack bytes contain the present byte, it seems to point to a frame and `translate` returns a (wrong) `Some`. + +To fix it, we double the stack size to `4096 * 2`. Now the last byte gets translated to `Some(1073741823)` correctly. To avoid this kind of bug in the future, we need to add a guard page to the stack, which causes an exception on stack overflow. We will do that in the next post when we remap the kernel. + ## What's next? In the next post we will extend this module and add a function to modify inactive page tables. Through that function, we will create a new page table hierarchy that maps the kernel correctly using 4KiB pages. Then we will switch to the new table to get a safer kernel environment. diff --git a/src/arch/x86_64/boot.asm b/src/arch/x86_64/boot.asm index 4abb2899..7035fc17 100644 --- a/src/arch/x86_64/boot.asm +++ b/src/arch/x86_64/boot.asm @@ -156,7 +156,7 @@ p3_table: p2_table: resb 4096 stack_bottom: - resb 4096 + resb 4096 * 2 stack_top: section .rodata From cf5ea7664e83b3736e262643d8b833c353cf11ec Mon Sep 17 00:00:00 2001 From: Philipp Oppermann Date: Wed, 9 Dec 2015 13:45:39 +0100 Subject: [PATCH 70/76] Test map_to function --- posts/DRAFT-paging.md | 25 +++++++++++++++++++++++++ src/memory/paging/mod.rs | 16 ++++++++++++++-- 2 files changed, 39 insertions(+), 2 deletions(-) diff --git a/posts/DRAFT-paging.md b/posts/DRAFT-paging.md index 8ed169dd..2b25e17d 100644 --- a/posts/DRAFT-paging.md +++ b/posts/DRAFT-paging.md @@ -817,6 +817,31 @@ So a stack overflow overwrites the P2 table, starting at the last entry. But the To fix it, we double the stack size to `4096 * 2`. Now the last byte gets translated to `Some(1073741823)` correctly. To avoid this kind of bug in the future, we need to add a guard page to the stack, which causes an exception on stack overflow. We will do that in the next post when we remap the kernel. +### map_to +Let's test the `map_to` function: + +```rust +let addr = 42 * 512 * 512 * 4096; // 42th P3 entry +let page = Page::containing_address(addr); +let frame = allocator.allocate_frame().expect("no more frames"); +println!("None = {:?}, map to {:?}", + page_table.translate(addr), + frame); +page_table.map_to(page, frame, EntryFlags::empty(), allocator); +println!("Some = {:?}", page_table.translate(addr)); +println!("next free frame: {:?}", allocator.allocate_frame()); +``` +We just map some random page to a free frame. To be able to borrow the page table as `&mut`, we need to make it mutable. + +You should see output similar to this: + +``` +None = None, map to Frame { number: 0 } +Some = Some(0) +next free frame: Some(Frame { number: 3 }) +``` +It's frame 0 because it's the first frame returned by the frame allocator. Since we map the 42th P3 entry, the mapping code needs to create a P2 and a P1 table. So the next free frame returned by the allocator is frame 3. + ## What's next? In the next post we will extend this module and add a function to modify inactive page tables. Through that function, we will create a new page table hierarchy that maps the kernel correctly using 4KiB pages. Then we will switch to the new table to get a safer kernel environment. diff --git a/src/memory/paging/mod.rs b/src/memory/paging/mod.rs index 230d7b05..1c96b332 100644 --- a/src/memory/paging/mod.rs +++ b/src/memory/paging/mod.rs @@ -16,7 +16,7 @@ pub struct Page { } impl Page { - fn containing_address(address: VirtualAddress) -> Page { + pub fn containing_address(address: VirtualAddress) -> Page { assert!(address < 0x0000_8000_0000_0000 || address >= 0xffff_8000_0000_0000, "invalid address: 0x{:x}", address); @@ -147,12 +147,24 @@ impl RecursivePageTable { pub fn test_paging(allocator: &mut A) where A: FrameAllocator { - let page_table = unsafe { RecursivePageTable::new() }; + let mut page_table = unsafe { RecursivePageTable::new() }; + // test translate println!("Some = {:?}", page_table.translate(0)); println!("Some = {:?}", page_table.translate(4096)); // second P1 entry println!("Some = {:?}", page_table.translate(512 * 4096)); // second P2 entry println!("Some = {:?}", page_table.translate(300 * 512 * 4096)); // 300th P2 entry println!("None = {:?}", page_table.translate(512 * 512 * 4096)); // second P3 entry println!("Some = {:?}", page_table.translate(512 * 512 * 4096 - 1)); // last mapped byte + + // test map_to + let addr = 42 * 512 * 512 * 4096; // 42th P3 entry + let page = Page::containing_address(addr); + let frame = allocator.allocate_frame().expect("no more frames"); + println!("None = {:?}, map to {:?}", + page_table.translate(addr), + frame); + page_table.map_to(page, frame, EntryFlags::empty(), allocator); + println!("Some = {:?}", page_table.translate(addr)); + println!("next free frame: {:?}", allocator.allocate_frame()); } From 8bb09f47f8302d4cac55390ad37586575d9fdd89 Mon Sep 17 00:00:00 2001 From: Philipp Oppermann Date: Wed, 9 Dec 2015 14:35:46 +0100 Subject: [PATCH 71/76] Test unmap function --- posts/DRAFT-paging.md | 20 ++++++++++++++++++++ src/memory/paging/mod.rs | 12 +++++++++++- 2 files changed, 31 insertions(+), 1 deletion(-) diff --git a/posts/DRAFT-paging.md b/posts/DRAFT-paging.md index 2b25e17d..64d6d6ad 100644 --- a/posts/DRAFT-paging.md +++ b/posts/DRAFT-paging.md @@ -842,6 +842,26 @@ next free frame: Some(Frame { number: 3 }) ``` It's frame 0 because it's the first frame returned by the frame allocator. Since we map the 42th P3 entry, the mapping code needs to create a P2 and a P1 table. So the next free frame returned by the allocator is frame 3. +### unmap +To test the `unmap` function, we unmap the test page so that it translates to `None` again: + +```rust +page_table.unmap(Page::containing_address(addr), allocator); +println!("None = {:?}", page_table.translate(addr)); +``` +It causes a panic since we call the unimplemented `deallocate_frame` method in `unwrap`. If we comment this call out, it works without problems. But there is some bug in this function nevertheless. + +Let's read something from the mapped page (of course before we unmap it again): + +```rust +println!("{:#x}", unsafe{ + *(Page::containing_address(addr).start_address() as *const u64) +}); +``` +Since we don't zero the mapped pages, the output is random. For me, it's `0xf000ff53f000ff53`. + +If `unmap` worked correctly, reading it again after unmapping should cause a page fault. But it doesn't. Instead, it just prints the same number again. + ## What's next? In the next post we will extend this module and add a function to modify inactive page tables. Through that function, we will create a new page table hierarchy that maps the kernel correctly using 4KiB pages. Then we will switch to the new table to get a safer kernel environment. diff --git a/src/memory/paging/mod.rs b/src/memory/paging/mod.rs index 1c96b332..4eef2c22 100644 --- a/src/memory/paging/mod.rs +++ b/src/memory/paging/mod.rs @@ -140,7 +140,7 @@ impl RecursivePageTable { let frame = p1[page.p1_index()].pointed_frame().unwrap(); p1[page.p1_index()].set_unused(); // TODO free p(1,2,3) table if empty - allocator.deallocate_frame(frame); + //allocator.deallocate_frame(frame); } } @@ -167,4 +167,14 @@ pub fn test_paging(allocator: &mut A) page_table.map_to(page, frame, EntryFlags::empty(), allocator); println!("Some = {:?}", page_table.translate(addr)); println!("next free frame: {:?}", allocator.allocate_frame()); + + // test unmap + println!("{:#x}", unsafe { + *(Page::containing_address(addr).start_address() as *const u64) + }); + page_table.unmap(Page::containing_address(addr), allocator); + println!("None = {:?}", page_table.translate(addr)); + println!("{:#x}", unsafe { + *(Page::containing_address(addr).start_address() as *const u64) + }); } From 75264e7cac39d921c20960ae3306a97777459f39 Mon Sep 17 00:00:00 2001 From: Philipp Oppermann Date: Wed, 9 Dec 2015 15:03:29 +0100 Subject: [PATCH 72/76] Fix unmap function by flushing the TLB --- Cargo.toml | 1 + posts/DRAFT-paging.md | 18 +++++++++++++++++- src/lib.rs | 1 + src/memory/paging/mod.rs | 4 +--- 4 files changed, 20 insertions(+), 4 deletions(-) diff --git a/Cargo.toml b/Cargo.toml index 2738b730..0524dc4f 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -9,6 +9,7 @@ crate-type = ["staticlib"] [dependencies] rlibc = "0.1.4" spin = "0.3.4" +x86 = "0.5.0" [dependencies.multiboot2] git = "https://github.com/phil-opp/multiboot2-elf64" diff --git a/posts/DRAFT-paging.md b/posts/DRAFT-paging.md index 64d6d6ad..b24c67b6 100644 --- a/posts/DRAFT-paging.md +++ b/posts/DRAFT-paging.md @@ -860,7 +860,23 @@ println!("{:#x}", unsafe{ ``` Since we don't zero the mapped pages, the output is random. For me, it's `0xf000ff53f000ff53`. -If `unmap` worked correctly, reading it again after unmapping should cause a page fault. But it doesn't. Instead, it just prints the same number again. +If `unmap` worked correctly, reading it again after unmapping should cause a page fault. But it doesn't. Instead, it just prints the same number again. When we remove the first read, we get the desired page fault (i.e. QEMU reboots again and again). So this seems to be some cache issue. + +An x86 processor has many different caches because always accessing the main memory would be very slow. Most of these caches are completely _transparent_. That means everything works exactly the same as without them, it's just much faster. But there is one cache, that needs to be updated manually: the _translation lookaside buffer_. + +The translation lookaside buffer, or TLB, caches the translation of virtual to physical addresses. It's filled automatically when a page is accessed. But it's not updated transparently when the mapping of a page changes. This is the reason that we still can access the page even through we unmapped it in the page table. + +So to fix our `unmap` function, we need to remove the cached translation from the TLB. We can use Gerd Zellweger's x86 crate to do this easily. To add it, append `x86 = "0.5.0"` to the dependency section in the `Cargo.toml`. Then we can use it to fix `unmap`: + +```rust +... + p1[page.p1_index()].set_unused(); + unsafe { ::x86::tlb::flush(page.start_address() )}; + // TODO free p(1,2,3) table if empty + //allocator.deallocate_frame(frame); +} +``` +Now the desired page fault occurs even when we access the page before. ## What's next? In the next post we will extend this module and add a function to modify inactive page tables. Through that function, we will create a new page table hierarchy that maps the kernel correctly using 4KiB pages. Then we will switch to the new table to get a safer kernel environment. diff --git a/src/lib.rs b/src/lib.rs index 35e160e4..b2e54a14 100644 --- a/src/lib.rs +++ b/src/lib.rs @@ -21,6 +21,7 @@ extern crate spin; extern crate multiboot2; #[macro_use] extern crate bitflags; +extern crate x86; #[macro_use] mod vga_buffer; diff --git a/src/memory/paging/mod.rs b/src/memory/paging/mod.rs index 4eef2c22..3477ad88 100644 --- a/src/memory/paging/mod.rs +++ b/src/memory/paging/mod.rs @@ -139,6 +139,7 @@ impl RecursivePageTable { .expect("mapping code does not support huge pages"); let frame = p1[page.p1_index()].pointed_frame().unwrap(); p1[page.p1_index()].set_unused(); + unsafe { ::x86::tlb::flush(page.start_address() )}; // TODO free p(1,2,3) table if empty //allocator.deallocate_frame(frame); } @@ -174,7 +175,4 @@ pub fn test_paging(allocator: &mut A) }); page_table.unmap(Page::containing_address(addr), allocator); println!("None = {:?}", page_table.translate(addr)); - println!("{:#x}", unsafe { - *(Page::containing_address(addr).start_address() as *const u64) - }); } From 329a64c9a1091fab67bad33ac882c44f76524c67 Mon Sep 17 00:00:00 2001 From: Philipp Oppermann Date: Wed, 9 Dec 2015 15:25:24 +0100 Subject: [PATCH 73/76] Some small improvements --- posts/DRAFT-paging.md | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) diff --git a/posts/DRAFT-paging.md b/posts/DRAFT-paging.md index b24c67b6..14082d52 100644 --- a/posts/DRAFT-paging.md +++ b/posts/DRAFT-paging.md @@ -8,7 +8,7 @@ In this post we will create a paging module, which allows us to access and modif TODO current Rust nightly version, link to github repo, etc. ## Paging -_Paging_ is a memory management scheme that separates virtual and physical memory. The address space is split into equal sized _pages_ and _page tables_ specify which virtual page points to which physical page. For an extensive paging introduction take a look at the paging chapter ([PDF][paging chapter]) of the [Three Easy Pieces] OS book. +_Paging_ is a memory management scheme that separates virtual and physical memory. The address space is split into equal sized _pages_ and _page tables_ specify which virtual page points to which physical frame. For an extensive paging introduction take a look at the paging chapter ([PDF][paging chapter]) of the [Three Easy Pieces] OS book. [paging chapter]: http://pages.cs.wisc.edu/~remzi/OSTEP/vm-paging.pdf [Three Easy Pieces]: http://pages.cs.wisc.edu/~remzi/OSTEP/ @@ -25,7 +25,7 @@ To translate an address, the CPU reads the P4 address from the CR3 register. The ![translation of virtual to physical addresses in 64 bit mode](/images/X86_Paging_64bit.svg) -The P4 entry points to a P3 table, where the next 9 bits of the address are used to select an entry. The P3 entry then points to a P2 table and the P2 entry points to a P1 table. The P1 entry, which is specified through bits 12–20, finally points to the physical page. +The P4 entry points to a P3 table, where the next 9 bits of the address are used to select an entry. The P3 entry then points to a P2 table and the P2 entry points to a P1 table. The P1 entry, which is specified through bits 12–20, finally points to the physical frame. So let's create a Rust module for it! (TODO better transition) @@ -737,7 +737,7 @@ We can also free the P1, P2, or even P3 table when the last entry is freed. But _Spoiler_: There is an ugly bug in this function, which we will find in the next section. -## Testing it +## Testing and Bugfixing To test it, we add a `test_paging` function in `memory/paging/mod.rs`: ```rust @@ -854,7 +854,7 @@ It causes a panic since we call the unimplemented `deallocate_frame` method in ` Let's read something from the mapped page (of course before we unmap it again): ```rust -println!("{:#x}", unsafe{ +println!("{:#x}", unsafe { *(Page::containing_address(addr).start_address() as *const u64) }); ``` From b3cb09ba8d687387cda6eec3498895be1443bc44 Mon Sep 17 00:00:00 2001 From: Philipp Oppermann Date: Wed, 9 Dec 2015 15:25:57 +0100 Subject: [PATCH 74/76] Add short conclusion --- posts/DRAFT-paging.md | 10 ++++++++++ 1 file changed, 10 insertions(+) diff --git a/posts/DRAFT-paging.md b/posts/DRAFT-paging.md index 14082d52..cc8aa70c 100644 --- a/posts/DRAFT-paging.md +++ b/posts/DRAFT-paging.md @@ -878,6 +878,16 @@ So to fix our `unmap` function, we need to remove the cached translation from th ``` Now the desired page fault occurs even when we access the page before. +## Conclusion +This post has become pretty long. So let's summarize what we've done: + +- we created a paging module and modeled page tables plus entries +- we mapped the P4 page recursively and created `next_table` methods +- we used empty enums and associated types to make the `next_table` functions safe +- we wrote a function to translate virtual to physical addresses +- we created safe functions to map and unmap pages +- and we fixed stack overflow and TLB related bugs + ## What's next? In the next post we will extend this module and add a function to modify inactive page tables. Through that function, we will create a new page table hierarchy that maps the kernel correctly using 4KiB pages. Then we will switch to the new table to get a safer kernel environment. From 9366db272c819c6aa47c2c856dcf3588ccc86cc4 Mon Sep 17 00:00:00 2001 From: Philipp Oppermann Date: Wed, 9 Dec 2015 16:14:25 +0100 Subject: [PATCH 75/76] Finish introduction and other TODOs --- posts/DRAFT-paging.md | 14 ++++++-------- 1 file changed, 6 insertions(+), 8 deletions(-) diff --git a/posts/DRAFT-paging.md b/posts/DRAFT-paging.md index cc8aa70c..eab6e2e8 100644 --- a/posts/DRAFT-paging.md +++ b/posts/DRAFT-paging.md @@ -3,9 +3,11 @@ layout: post title: 'Accessing and Modifying Page Tables' --- -In this post we will create a paging module, which allows us to access and modify the 4-level page table. We will create functions to translate a virtual to a physical address. TODO more introduction +In this post we will create a paging module, which allows us to access and modify the 4-level page table. We will explore recursive page table mapping and use some Rust features to make it safe. Finally we will create functions to translate virtual addresses and to map and unmap pages. -TODO current Rust nightly version, link to github repo, etc. +You can find the source code and this post itself on [Github][source repository]. Please file an issue there if you have any problems or improvement suggestions. There is also a comment section at the end of this page. Note that this post requires a current Rust nightly. + +[source repository]: https://github.com/phil-opp/blog_os/tree/paging_module ## Paging _Paging_ is a memory management scheme that separates virtual and physical memory. The address space is split into equal sized _pages_ and _page tables_ specify which virtual page points to which physical frame. For an extensive paging introduction take a look at the paging chapter ([PDF][paging chapter]) of the [Three Easy Pieces] OS book. @@ -27,10 +29,8 @@ To translate an address, the CPU reads the P4 address from the CR3 register. The The P4 entry points to a P3 table, where the next 9 bits of the address are used to select an entry. The P3 entry then points to a P2 table and the P2 entry points to a P1 table. The P1 entry, which is specified through bits 12–20, finally points to the physical frame. -So let's create a Rust module for it! (TODO better transition) - ## A Basic Paging Module -We start by creating a basic `memory/paging/mod.rs` module: +Let's create a basic paging module in `memory/paging/mod.rs`: ```rust use memory::PAGE_SIZE; // needed later @@ -548,9 +548,7 @@ p3.and_then(|p3| { This function is much longer and more complex than the `translate_page` function itself. To avoid this complexity in the future, we will only work with standard 4KiB pages from now on. ## Mapping Pages -TODO introduction/motivation - -Let's add a function that maps a `Page` to some `Frame`: +Let's add a function that modifies the page tables to map a `Page` to a `Frame`: ```rust pub use self::entry::*; From 3a82ca32cd8f18f176ec4f61f99c3a23ffcf0cc2 Mon Sep 17 00:00:00 2001 From: Philipp Oppermann Date: Wed, 9 Dec 2015 16:18:16 +0100 Subject: [PATCH 76/76] Publish `Accessing and Modifying Page Tables` post --- posts/{DRAFT-paging.md => 2015-12-09-modifying-page-tables.md} | 0 1 file changed, 0 insertions(+), 0 deletions(-) rename posts/{DRAFT-paging.md => 2015-12-09-modifying-page-tables.md} (100%) diff --git a/posts/DRAFT-paging.md b/posts/2015-12-09-modifying-page-tables.md similarity index 100% rename from posts/DRAFT-paging.md rename to posts/2015-12-09-modifying-page-tables.md