Skip to content

Commit f9a8d5f

Browse files
Ramla-Ikevinaboos
authored andcommitted
Redesign frame allocation for easier formal verification (theseus-os#1004)
* A single type `Frames` is now used to represent all physical memory frames. * Each `Frames` object is globally unique, and ownership of one represents the exclusive capability to access those frames, e.g., map pages to them. * The `Frames` struct is parameterized with a const generic enum that determines which state it is in, one of four `memory_structs::MemoryState`s: 1. Free: the range of frames is free and is owned by the frame allocator. * `Frames<{MemoryState::Free}>` is type aliased to `FreeFrames`. 2. Allocated: the range of frames has been allocated, and is owned by another entity outside of the frame allocator. * `Frames<{MemoryState::Allocated}>` is type aliased to `AllocatedFrames`, which replaces the previous struct of the same name. 3. Mapped: the range of frames has been mapped by a range of virtual pages. * `Frames<{MemoryState::Mapped}>` is type aliased to `MappedFrames`, which is not yet used in the Theseus codebase. 4. Unmapped: the range of frames has just been unmapped by a range of virtual pages, but has yet to be returned to the frame allocator. * `Frames<{MemoryState::Unmapped}>` is type aliased to `UnmappedFrames`, which is used as an intermediary step before transitioning back into `AllocatedFrames`. * See the documentation of `Frames` for more info on state transitions: (Free) <---> (Allocated) --> (Mapped) --> (Unmapped) --> (Allocated) <---> (Free) * `FreeFrames` is used in favor of `Chunk`. Note that the term "chunk" still appears in the code in order to minimize the sheer volume of tiny changes. * Added a few new APIs to frame-related types, mostly for convenience: `split_at`, `split_range`, `contains_range`. * Combined the `PhysicalMemoryRegion` and `Region` into a single type used across the entire frame allocator. * The core logic of the frame allocator has been changed to accommodate the new `Frames` type, which is a verified "true linear" type that cannot be cloned or have its inner fields mutated. * The entire point of this redesigns is to make the frame allocator amenable to formal verification based on typestate analysis combined with Prusti-verifiable pre- and post-conditions for key functions. * Actual verification code and proofs of frame allocation correctness are coming soon in future PRs. Co-authored-by: Kevin Boos <[email protected]>
1 parent 425b96d commit f9a8d5f

File tree

8 files changed

+664
-390
lines changed

8 files changed

+664
-390
lines changed

kernel/frame_allocator/src/lib.rs

Lines changed: 552 additions & 323 deletions
Large diffs are not rendered by default.

kernel/frame_allocator/src/static_array_rb_tree.rs

Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -42,6 +42,11 @@ impl <T: Ord> Wrapper<T> {
4242
inner: value,
4343
})
4444
}
45+
46+
/// Returns the inner value, consuming this wrapper.
47+
pub(crate) fn into_inner(self) -> T {
48+
self.inner
49+
}
4550
}
4651

4752

kernel/frame_allocator/src/test.rs

Lines changed: 60 additions & 45 deletions
Original file line numberDiff line numberDiff line change
@@ -1,24 +1,19 @@
1-
//! Tests for the AllocatedFrames type, mainly the `split` method.
1+
//! Tests for the `Frames` type, mainly the `split` method.
22
33
extern crate std;
44

55
use self::std::dbg;
66

77
use super::*;
88

9-
impl PartialEq for AllocatedFrames {
10-
fn eq(&self, other: &Self) -> bool {
11-
self.frames == other.frames
12-
}
13-
}
14-
15-
fn from_addr(start_addr: usize, end_addr: usize) -> AllocatedFrames {
16-
AllocatedFrames {
17-
frames: FrameRange::new(
9+
fn from_addr(start_addr: usize, end_addr: usize) -> FreeFrames {
10+
FreeFrames::new(
11+
MemoryRegionType::Free,
12+
FrameRange::new(
1813
Frame::containing_address(PhysicalAddress::new_canonical(start_addr)),
1914
Frame::containing_address(PhysicalAddress::new_canonical(end_addr)),
2015
)
21-
}
16+
)
2217
}
2318

2419
fn frame_addr(addr: usize) -> Frame {
@@ -30,7 +25,7 @@ fn split_before_beginning() {
3025
let original = from_addr( 0x4275000, 0x4285000);
3126
let split_at = frame_addr(0x4274000);
3227

33-
let result = original.split(split_at);
28+
let result = original.split_at(split_at);
3429
dbg!(&result);
3530
assert!(result.is_err());
3631
}
@@ -42,11 +37,13 @@ fn split_at_beginning() {
4237
let first = AllocatedFrames::empty();
4338
let second = from_addr( 0x4275000, 0x4285000);
4439

45-
let result = original.split(split_at);
40+
let result = original.split_at(split_at);
4641
dbg!(&result);
4742
let (result1, result2) = result.unwrap();
48-
assert_eq!(result1, first);
49-
assert_eq!(result2, second);
43+
assert_eq!(result1.start(), first.start());
44+
assert_eq!(result1.end(), first.end());
45+
assert_eq!(result2.start(), second.start());
46+
assert_eq!(result2.end(), second.end());
5047
}
5148

5249

@@ -57,11 +54,13 @@ fn split_at_middle() {
5754
let first = from_addr( 0x4275000, 0x427C000);
5855
let second = from_addr( 0x427D000, 0x4285000);
5956

60-
let result = original.split(split_at);
57+
let result = original.split_at(split_at);
6158
dbg!(&result);
6259
let (result1, result2) = result.unwrap();
63-
assert_eq!(result1, first);
64-
assert_eq!(result2, second);
60+
assert_eq!(result1.start(), first.start());
61+
assert_eq!(result1.end(), first.end());
62+
assert_eq!(result2.start(), second.start());
63+
assert_eq!(result2.end(), second.end());
6564
}
6665

6766
#[test]
@@ -71,11 +70,13 @@ fn split_at_end() {
7170
let first = from_addr( 0x4275000, 0x4284000);
7271
let second = from_addr( 0x4285000, 0x4285000);
7372

74-
let result = original.split(split_at);
73+
let result = original.split_at(split_at);
7574
dbg!(&result);
7675
let (result1, result2) = result.unwrap();
77-
assert_eq!(result1, first);
78-
assert_eq!(result2, second);
76+
assert_eq!(result1.start(), first.start());
77+
assert_eq!(result1.end(), first.end());
78+
assert_eq!(result2.start(), second.start());
79+
assert_eq!(result2.end(), second.end());
7980
}
8081

8182

@@ -86,11 +87,13 @@ fn split_after_end() {
8687
let first = from_addr( 0x4275000, 0x4285000);
8788
let second = AllocatedFrames::empty();
8889

89-
let result = original.split(split_at);
90+
let result = original.split_at(split_at);
9091
dbg!(&result);
9192
let (result1, result2) = result.unwrap();
92-
assert_eq!(result1, first);
93-
assert_eq!(result2, second);
93+
assert_eq!(result1.start(), first.start());
94+
assert_eq!(result1.end(), first.end());
95+
assert_eq!(result2.start(), second.start());
96+
assert_eq!(result2.end(), second.end());
9497
}
9598

9699

@@ -99,7 +102,7 @@ fn split_empty_at_zero() {
99102
let original = AllocatedFrames::empty();
100103
let split_at = frame_addr(0x0000);
101104

102-
let result = original.split(split_at);
105+
let result = original.split_at(split_at);
103106
dbg!(&result);
104107
assert!(result.is_err());
105108
}
@@ -109,7 +112,7 @@ fn split_empty_at_one() {
109112
let original = AllocatedFrames::empty();
110113
let split_at = frame_addr(0x1000);
111114

112-
let result = original.split(split_at);
115+
let result = original.split_at(split_at);
113116
dbg!(&result);
114117
assert!(result.is_err());
115118
}
@@ -119,7 +122,7 @@ fn split_empty_at_two() {
119122
let original = AllocatedFrames::empty();
120123
let split_at = frame_addr(0x2000);
121124

122-
let result = original.split(split_at);
125+
let result = original.split_at(split_at);
123126
dbg!(&result);
124127
assert!(result.is_err());
125128
}
@@ -133,11 +136,13 @@ fn split_at_beginning_zero() {
133136
let first = AllocatedFrames::empty();
134137
let second = from_addr(0x0, 0x5000);
135138

136-
let result = original.split(split_at);
139+
let result = original.split_at(split_at);
137140
dbg!(&result);
138141
let (result1, result2) = result.unwrap();
139-
assert_eq!(result1, first);
140-
assert_eq!(result2, second);
142+
assert_eq!(result1.start(), first.start());
143+
assert_eq!(result1.end(), first.end());
144+
assert_eq!(result2.start(), second.start());
145+
assert_eq!(result2.end(), second.end());
141146
}
142147

143148
#[test]
@@ -147,11 +152,13 @@ fn split_at_beginning_one() {
147152
let first = from_addr( 0x0000, 0x0000);
148153
let second = from_addr( 0x1000, 0x5000);
149154

150-
let result = original.split(split_at);
155+
let result = original.split_at(split_at);
151156
dbg!(&result);
152157
let (result1, result2) = result.unwrap();
153-
assert_eq!(result1, first);
154-
assert_eq!(result2, second);
158+
assert_eq!(result1.start(), first.start());
159+
assert_eq!(result1.end(), first.end());
160+
assert_eq!(result2.start(), second.start());
161+
assert_eq!(result2.end(), second.end());
155162
}
156163

157164
#[test]
@@ -161,11 +168,13 @@ fn split_at_beginning_max_length_one() {
161168
let first = AllocatedFrames::empty();
162169
let second = from_addr(0xFFFF_FFFF_FFFF_F000, 0xFFFF_FFFF_FFFF_F000);
163170

164-
let result = original.split(split_at);
171+
let result = original.split_at(split_at);
165172
dbg!(&result);
166173
let (result1, result2) = result.unwrap();
167-
assert_eq!(result1, first);
168-
assert_eq!(result2, second);
174+
assert_eq!(result1.start(), first.start());
175+
assert_eq!(result1.end(), first.end());
176+
assert_eq!(result2.start(), second.start());
177+
assert_eq!(result2.end(), second.end());
169178
}
170179

171180
#[test]
@@ -175,11 +184,13 @@ fn split_at_end_max_length_two() {
175184
let first = from_addr( 0xFFFF_FFFF_FFFF_E000, 0xFFFF_FFFF_FFFF_E000);
176185
let second = from_addr( 0xFFFF_FFFF_FFFF_F000, 0xFFFF_FFFF_FFFF_F000);
177186

178-
let result = original.split(split_at);
187+
let result = original.split_at(split_at);
179188
dbg!(&result);
180189
let (result1, result2) = result.unwrap();
181-
assert_eq!(result1, first);
182-
assert_eq!(result2, second);
190+
assert_eq!(result1.start(), first.start());
191+
assert_eq!(result1.end(), first.end());
192+
assert_eq!(result2.start(), second.start());
193+
assert_eq!(result2.end(), second.end());
183194
}
184195

185196

@@ -190,11 +201,13 @@ fn split_after_end_max() {
190201
let first = from_addr( 0xFFFF_FFFF_FFFF_E000, 0xFFFF_FFFF_FFFF_E000);
191202
let second = AllocatedFrames::empty();
192203

193-
let result = original.split(split_at);
204+
let result = original.split_at(split_at);
194205
dbg!(&result);
195206
let (result1, result2) = result.unwrap();
196-
assert_eq!(result1, first);
197-
assert_eq!(result2, second);
207+
assert_eq!(result1.start(), first.start());
208+
assert_eq!(result1.end(), first.end());
209+
assert_eq!(result2.start(), second.start());
210+
assert_eq!(result2.end(), second.end());
198211
}
199212

200213
#[test]
@@ -204,9 +217,11 @@ fn split_at_beginning_max() {
204217
let first = AllocatedFrames::empty();
205218
let second = from_addr(0xFFFF_FFFF_FFFF_E000, 0xFFFF_FFFF_FFFF_E000);
206219

207-
let result = original.split(split_at);
220+
let result = original.split_at(split_at);
208221
dbg!(&result);
209222
let (result1, result2) = result.unwrap();
210-
assert_eq!(result1, first);
211-
assert_eq!(result2, second);
223+
assert_eq!(result1.start(), first.start());
224+
assert_eq!(result1.end(), first.end());
225+
assert_eq!(result2.start(), second.start());
226+
assert_eq!(result2.end(), second.end());
212227
}

kernel/memory/src/lib.rs

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -36,6 +36,7 @@ pub use page_allocator::{
3636
};
3737
pub use frame_allocator::{
3838
AllocatedFrames,
39+
UnmappedFrames,
3940
allocate_frames,
4041
allocate_frames_at,
4142
allocate_frames_by_bytes,

kernel/memory/src/paging/mapper.rs

Lines changed: 12 additions & 11 deletions
Original file line numberDiff line numberDiff line change
@@ -18,7 +18,7 @@ use core::{
1818
slice,
1919
};
2020
use log::{error, warn, debug, trace};
21-
use crate::{BROADCAST_TLB_SHOOTDOWN_FUNC, VirtualAddress, PhysicalAddress, Page, Frame, FrameRange, AllocatedPages, AllocatedFrames};
21+
use crate::{BROADCAST_TLB_SHOOTDOWN_FUNC, VirtualAddress, PhysicalAddress, Page, Frame, FrameRange, AllocatedPages, AllocatedFrames, UnmappedFrames};
2222
use crate::paging::{
2323
get_current_p4,
2424
table::{P4, UPCOMING_P4, Table, Level4},
@@ -34,23 +34,23 @@ use owned_borrowed_trait::{OwnedOrBorrowed, Owned, Borrowed};
3434
#[cfg(target_arch = "x86_64")]
3535
use kernel_config::memory::ENTRIES_PER_PAGE_TABLE;
3636

37-
/// This is a private callback used to convert `UnmappedFrames` into `AllocatedFrames`.
37+
/// This is a private callback used to convert `UnmappedFrameRange` into `UnmappedFrames`.
3838
///
3939
/// This exists to break the cyclic dependency cycle between `page_table_entry` and
4040
/// `frame_allocator`, which depend on each other as such:
41-
/// * `frame_allocator` needs to `impl Into<AllocatedPages> for UnmappedFrames`
41+
/// * `frame_allocator` needs to `impl Into<Frames> for UnmappedFrameRange`
4242
/// in order to allow unmapped exclusive frames to be safely deallocated
4343
/// * `page_table_entry` needs to use the `AllocatedFrames` type in order to allow
4444
/// page table entry values to be set safely to a real physical frame that is owned and exists.
4545
///
4646
/// To get around that, the `frame_allocator::init()` function returns a callback
47-
/// to its function that allows converting a range of unmapped frames back into `AllocatedFrames`,
47+
/// to its function that allows converting a range of unmapped frames back into `UnmappedFrames`,
4848
/// which then allows them to be dropped and thus deallocated.
4949
///
5050
/// This is safe because the frame allocator can only be initialized once, and also because
5151
/// only this crate has access to that function callback and can thus guarantee
52-
/// that it is only invoked for `UnmappedFrames`.
53-
pub(super) static INTO_ALLOCATED_FRAMES_FUNC: Once<fn(FrameRange) -> AllocatedFrames> = Once::new();
52+
/// that it is only invoked for `UnmappedFrameRange`.
53+
pub(super) static INTO_UNMAPPED_FRAMES_FUNC: Once<fn(FrameRange) -> UnmappedFrames> = Once::new();
5454

5555
/// A convenience function to translate the given virtual address into a
5656
/// physical address using the currently-active page table.
@@ -610,8 +610,8 @@ impl MappedPages {
610610
);
611611
}
612612

613-
let mut first_frame_range: Option<AllocatedFrames> = None; // this is what we'll return
614-
let mut current_frame_range: Option<AllocatedFrames> = None;
613+
let mut first_frame_range: Option<UnmappedFrames> = None; // this is what we'll return
614+
let mut current_frame_range: Option<UnmappedFrames> = None;
615615

616616
for page in self.pages.range().clone() {
617617
let p1 = active_table_mapper.p4_mut()
@@ -631,8 +631,8 @@ impl MappedPages {
631631
// freed from the newly-unmapped P1 PTE entry above.
632632
match unmapped_frames {
633633
UnmapResult::Exclusive(newly_unmapped_frames) => {
634-
let newly_unmapped_frames = INTO_ALLOCATED_FRAMES_FUNC.get()
635-
.ok_or("BUG: Mapper::unmap(): the `INTO_ALLOCATED_FRAMES_FUNC` callback was not initialized")
634+
let newly_unmapped_frames = INTO_UNMAPPED_FRAMES_FUNC.get()
635+
.ok_or("BUG: Mapper::unmap(): the `INTO_UNMAPPED_FRAMES_FUNC` callback was not initialized")
636636
.map(|into_func| into_func(newly_unmapped_frames.deref().clone()))?;
637637

638638
if let Some(mut curr_frames) = current_frame_range.take() {
@@ -681,7 +681,8 @@ impl MappedPages {
681681
}
682682

683683
// Ensure that we return at least some frame range, even if we broke out of the above loop early.
684-
Ok(first_frame_range.or(current_frame_range))
684+
Ok(first_frame_range.map(|f| f.into_allocated_frames())
685+
.or(current_frame_range.map(|f| f.into_allocated_frames())))
685686
}
686687

687688

kernel/memory/src/paging/mod.rs

Lines changed: 4 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -30,7 +30,7 @@ use core::{
3030
use log::debug;
3131
use super::{
3232
Frame, FrameRange, PageRange, VirtualAddress, PhysicalAddress,
33-
AllocatedPages, allocate_pages, AllocatedFrames, PteFlags,
33+
AllocatedPages, allocate_pages, AllocatedFrames, UnmappedFrames, PteFlags,
3434
InitialMemoryMappings, tlb_flush_all, tlb_flush_virt_addr,
3535
get_p4, find_section_memory_bounds,
3636
};
@@ -223,11 +223,11 @@ pub fn get_current_p4() -> Frame {
223223
pub fn init(
224224
boot_info: &impl BootInformation,
225225
stack_start_virt: VirtualAddress,
226-
into_alloc_frames_fn: fn(FrameRange) -> AllocatedFrames,
226+
into_unmapped_frames_fn: fn(FrameRange) -> UnmappedFrames,
227227
) -> Result<InitialMemoryMappings, &'static str> {
228228
// Store the callback from `frame_allocator::init()` that allows the `Mapper` to convert
229-
// `page_table_entry::UnmappedFrames` back into `AllocatedFrames`.
230-
mapper::INTO_ALLOCATED_FRAMES_FUNC.call_once(|| into_alloc_frames_fn);
229+
// `page_table_entry::UnmappedFrameRange` back into `UnmappedFrames`.
230+
mapper::INTO_UNMAPPED_FRAMES_FUNC.call_once(|| into_unmapped_frames_fn);
231231

232232
// bootstrap a PageTable from the currently-loaded page table
233233
let mut page_table = PageTable::from_current()

kernel/memory_structs/src/lib.rs

Lines changed: 24 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -7,19 +7,35 @@
77
88
#![no_std]
99
#![feature(step_trait)]
10+
#![allow(incomplete_features)]
11+
#![feature(adt_const_params)]
1012

1113
use core::{
1214
cmp::{min, max},
1315
fmt,
1416
iter::Step,
15-
ops::{Add, AddAssign, Deref, DerefMut, Sub, SubAssign}
17+
marker::ConstParamTy,
18+
ops::{Add, AddAssign, Deref, DerefMut, Sub, SubAssign},
1619
};
1720
use kernel_config::memory::{MAX_PAGE_NUMBER, PAGE_SIZE};
1821
use zerocopy::FromBytes;
1922
use paste::paste;
2023
use derive_more::*;
2124
use range_inclusive::{RangeInclusive, RangeInclusiveIterator};
2225

26+
/// The possible states that a range of exclusively-owned pages or frames can be in.
27+
#[derive(PartialEq, Eq, ConstParamTy)]
28+
pub enum MemoryState {
29+
/// Memory is free and owned by the allocator
30+
Free,
31+
/// Memory is allocated and can be used for a mapping
32+
Allocated,
33+
/// Memory is mapped (PTE has been set)
34+
Mapped,
35+
/// Memory has been unmapped (PTE has been cleared)
36+
Unmapped
37+
}
38+
2339
/// A macro for defining `VirtualAddress` and `PhysicalAddress` structs
2440
/// and implementing their common traits, which are generally identical.
2541
macro_rules! implement_address {
@@ -470,6 +486,13 @@ macro_rules! implement_page_frame_range {
470486
None
471487
}
472488
}
489+
490+
#[doc = "Returns `true` if the `other` `" $TypeName "` is fully contained within this `" $TypeName "`."]
491+
pub fn contains_range(&self, other: &$TypeName) -> bool {
492+
!other.is_empty()
493+
&& (other.start() >= self.start())
494+
&& (other.end() <= self.end())
495+
}
473496
}
474497
impl fmt::Debug for $TypeName {
475498
fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {

0 commit comments

Comments
 (0)