Skip to content

Commit 4e54423

Browse files
authored
Allow page allocation requests to specify a desired alignment (#1029)
* The new `AllocationRequest::AlignedAt` variant can accept an alignment value, specified in number of 4K pages (not bytes). * This is needed to support easier allocation of huge pages, which have a large alignment requirement, e.g., a 2MiB huge page requires a contiguous 512-page allocation aligned to that same boundary of 512 normal 4K pages (512 * 4KiB = 2MiB).
1 parent e979b80 commit 4e54423

File tree

8 files changed

+139
-16
lines changed

8 files changed

+139
-16
lines changed

Cargo.lock

Lines changed: 10 additions & 0 deletions
Some generated files are not rendered by default. Learn more about customizing how changed files appear on GitHub.

Cargo.toml

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -79,6 +79,7 @@ exclude = [
7979

8080
## Exclude application crates used for testing specific Theseus functionality.
8181
## TODO: move these to a specific "tests" folder so we can exclude that entire folder.
82+
"applications/test_aligned_page_allocation",
8283
"applications/test_backtrace",
8384
"applications/test_block_io",
8485
"applications/test_channel",
Lines changed: 15 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,15 @@
1+
[package]
2+
name = "test_aligned_page_allocation"
3+
version = "0.1.0"
4+
description = "Tests the `AllocationRequest::AlignedTo` variant, which is needed for huge pages"
5+
authors = ["Kevin Boos <[email protected]>"]
6+
edition = "2021"
7+
8+
[dependencies]
9+
log = "0.4.8"
10+
11+
[dependencies.memory]
12+
path = "../../kernel/memory"
13+
14+
[dependencies.app_io]
15+
path = "../../kernel/app_io"
Lines changed: 45 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,45 @@
1+
//! A set of basic tests for the [`AllocationRequest::AlignedTo`] variant.
2+
3+
#![no_std]
4+
5+
extern crate alloc;
6+
7+
use alloc::{
8+
vec::Vec,
9+
string::String,
10+
};
11+
use app_io::println;
12+
use memory::AllocationRequest;
13+
14+
static TEST_SET: [usize; 9] = [1, 2, 4, 8, 27, 48, 256, 512, 1024];
15+
16+
pub fn main(_args: Vec<String>) -> isize {
17+
match rmain() {
18+
Ok(_) => 0,
19+
Err(e) => {
20+
println!("Error: {}", e);
21+
-1
22+
}
23+
}
24+
}
25+
26+
fn rmain() -> Result<(), &'static str> {
27+
for num_pages in TEST_SET.into_iter() {
28+
for alignment in TEST_SET.into_iter() {
29+
println!("Attempting to allocate {num_pages} pages with alignment of {alignment} 4K pages...");
30+
match memory::allocate_pages_deferred(
31+
AllocationRequest::AlignedTo { alignment_4k_pages: alignment },
32+
num_pages,
33+
) {
34+
Ok((ap, _action)) => {
35+
assert_eq!(ap.start().number() % alignment, 0);
36+
assert_eq!(ap.size_in_pages(), num_pages);
37+
println!(" Success: {ap:?}");
38+
}
39+
Err(e) => println!(" !! FAILURE: {e:?}"),
40+
}
41+
}
42+
}
43+
44+
Ok(())
45+
}

kernel/memory/src/lib.rs

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -26,6 +26,8 @@ pub use memory_structs::*;
2626
pub use page_allocator::{
2727
AllocatedPages,
2828
AllocationRequest,
29+
allocate_pages_deferred,
30+
allocate_pages_by_bytes_deferred,
2931
allocate_pages,
3032
allocate_pages_at,
3133
allocate_pages_by_bytes,
@@ -37,6 +39,8 @@ pub use page_allocator::{
3739
pub use frame_allocator::{
3840
AllocatedFrames,
3941
UnmappedFrames,
42+
allocate_frames_deferred,
43+
allocate_frames_by_bytes_deferred,
4044
allocate_frames,
4145
allocate_frames_at,
4246
allocate_frames_by_bytes,

kernel/memory_structs/src/lib.rs

Lines changed: 10 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -7,6 +7,7 @@
77
88
#![no_std]
99
#![feature(step_trait)]
10+
#![feature(int_roundings)]
1011
#![allow(incomplete_features)]
1112
#![feature(adt_const_params)]
1213

@@ -287,6 +288,15 @@ macro_rules! implement_page_frame {
287288
number: addr.value() / PAGE_SIZE,
288289
}
289290
}
291+
292+
#[doc = "Returns a new `" $TypeName "` that is aligned up from this \
293+
`" $TypeName "` to the nearest multiple of `alignment_4k_pages`."]
294+
#[doc(alias = "next_multiple_of")]
295+
pub const fn align_up(&self, alignment_4k_pages: usize) -> $TypeName {
296+
$TypeName {
297+
number: self.number.next_multiple_of(alignment_4k_pages)
298+
}
299+
}
290300
}
291301
impl fmt::Debug for $TypeName {
292302
fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {

kernel/page_allocator/src/lib.rs

Lines changed: 52 additions & 16 deletions
Original file line numberDiff line numberDiff line change
@@ -38,7 +38,7 @@ use static_array_rb_tree::*;
3838

3939

4040
/// Certain regions are pre-designated for special usage, specifically the kernel's initial identity mapping.
41-
/// They will be allocated from if an address within them is specifically requested;
41+
/// They will be allocated from if an address within them is specifically
4242
/// otherwise, they will only be allocated from as a "last resort" if all other non-designated address ranges are exhausted.
4343
///
4444
/// Any virtual addresses **less than or equal** to this address are considered "designated".
@@ -536,10 +536,15 @@ fn find_specific_chunk(
536536
/// If no range is specified, this function first attempts to find a suitable chunk
537537
/// that is **not** within the designated regions,
538538
/// and only allocates from the designated regions as a backup option.
539+
///
540+
/// If an alignment is specified (in terms of number of 4KiB pages), then the starting page
541+
/// in the allocated range must be aligned to that number of pages.
542+
/// If no specific alignment is needed, the default aligment of 1 page should be used.
539543
fn find_any_chunk(
540544
list: &mut StaticArrayRBTree<Chunk>,
541545
num_pages: usize,
542546
within_range: Option<&PageRange>,
547+
alignment_4k_pages: usize,
543548
) -> Result<(AllocatedPages, DeferredAllocAction<'static>), AllocationError> {
544549
let designated_low_end = DESIGNATED_PAGES_LOW_END.get()
545550
.ok_or(AllocationError::NotInitialized)?;
@@ -555,7 +560,8 @@ fn find_any_chunk(
555560
if let Some(chunk) = elem {
556561
// Use max and min below to ensure that the range of pages we allocate from
557562
// is within *both* the current chunk's bounds and the range's bounds.
558-
let lowest_possible_start_page = *max(chunk.start(), range.start());
563+
let lowest_possible_start_page = max(chunk.start(), range.start())
564+
.align_up(alignment_4k_pages);
559565
let highest_possible_end_page = *min(chunk.end(), range.end());
560566
if lowest_possible_start_page + num_pages <= highest_possible_end_page {
561567
return adjust_chosen_chunk(
@@ -589,7 +595,8 @@ fn find_any_chunk(
589595
while let Some(chunk) = cursor.get().map(|w| w.deref()) {
590596
// Use max and min below to ensure that the range of pages we allocate from
591597
// is within *both* the current chunk's bounds and the range's bounds.
592-
let lowest_possible_start_page = *max(chunk.start(), range.start());
598+
let lowest_possible_start_page = max(chunk.start(), range.start())
599+
.align_up(alignment_4k_pages);
593600
let highest_possible_end_page = *min(chunk.end(), range.end());
594601
if lowest_possible_start_page + num_pages <= highest_possible_end_page {
595602
return adjust_chosen_chunk(
@@ -621,8 +628,14 @@ fn find_any_chunk(
621628
Inner::Array(ref mut arr) => {
622629
for elem in arr.iter_mut() {
623630
if let Some(chunk) = elem {
624-
if num_pages <= chunk.size_in_pages() {
625-
return adjust_chosen_chunk(*chunk.start(), num_pages, &chunk.clone(), ValueRefMut::Array(elem));
631+
let lowest_possible_start_page = chunk.start().align_up(alignment_4k_pages);
632+
if lowest_possible_start_page + num_pages <= *chunk.end() {
633+
return adjust_chosen_chunk(
634+
lowest_possible_start_page,
635+
num_pages,
636+
&chunk.clone(),
637+
ValueRefMut::Array(elem),
638+
);
626639
}
627640
}
628641
}
@@ -644,8 +657,14 @@ fn find_any_chunk(
644657
// The first iterates over the lower designated region, from higher addresses to lower, down to zero.
645658
let mut cursor = tree.upper_bound_mut(Bound::Included(designated_low_end));
646659
while let Some(chunk) = cursor.get().map(|w| w.deref()) {
647-
if num_pages < chunk.size_in_pages() {
648-
return adjust_chosen_chunk(*chunk.start(), num_pages, &chunk.clone(), ValueRefMut::RBTree(cursor));
660+
let lowest_possible_start_page = chunk.start().align_up(alignment_4k_pages);
661+
if lowest_possible_start_page + num_pages <= *chunk.end() {
662+
return adjust_chosen_chunk(
663+
lowest_possible_start_page,
664+
num_pages,
665+
&chunk.clone(),
666+
ValueRefMut::RBTree(cursor),
667+
);
649668
}
650669
cursor.move_prev();
651670
}
@@ -657,8 +676,14 @@ fn find_any_chunk(
657676
// we already iterated over non-designated pages in the first match statement above, so we're out of memory.
658677
break;
659678
}
660-
if num_pages < chunk.size_in_pages() {
661-
return adjust_chosen_chunk(*chunk.start(), num_pages, &chunk.clone(), ValueRefMut::RBTree(cursor));
679+
let lowest_possible_start_page = chunk.start().align_up(alignment_4k_pages);
680+
if lowest_possible_start_page + num_pages <= *chunk.end() {
681+
return adjust_chosen_chunk(
682+
lowest_possible_start_page,
683+
num_pages,
684+
&chunk.clone(),
685+
ValueRefMut::RBTree(cursor),
686+
);
662687
}
663688
cursor.move_prev();
664689
}
@@ -729,23 +754,31 @@ fn adjust_chosen_chunk(
729754
}
730755

731756

732-
/// Possible options when requested pages from the page allocator.
757+
/// Possible options when requesting pages from the page allocator.
733758
pub enum AllocationRequest<'r> {
734-
/// The allocated pages can be located at any virtual address.
735-
Any,
736759
/// The allocated pages must start exactly at the given `VirtualAddress`.
737760
AtVirtualAddress(VirtualAddress),
761+
/// The allocated pages may be located at any virtual address,
762+
/// but the starting page must be aligned to a multiple of `alignment_4k_pages`.
763+
/// An alignment of `1` page is equivalent to specifying no alignment requirement.
764+
///
765+
/// Note: alignment is specified in number of 4KiB pages, not number of bytes.
766+
AlignedTo { alignment_4k_pages: usize },
738767
/// The allocated pages can be located anywhere within the given range.
739768
WithinRange(&'r PageRange),
769+
/// The allocated pages can be located at any virtual address
770+
/// and have no special alignment requirements beyond a single page.
771+
Any,
740772
}
741773

774+
742775
/// The core page allocation routine that allocates the given number of virtual pages,
743776
/// optionally at the requested starting `VirtualAddress`.
744777
///
745778
/// This simply reserves a range of virtual addresses, it does not allocate
746779
/// actual physical memory frames nor do any memory mapping.
747780
/// Thus, the returned `AllocatedPages` aren't directly usable until they are mapped to physical frames.
748-
///
781+
///
749782
/// Allocation is based on a red-black tree and is thus `O(log(n))`.
750783
/// Fragmentation isn't cleaned up until we're out of address space, but that's not really a big deal.
751784
///
@@ -780,11 +813,14 @@ pub fn allocate_pages_deferred(
780813
AllocationRequest::AtVirtualAddress(vaddr) => {
781814
find_specific_chunk(&mut locked_list, Page::containing_address(vaddr), num_pages)
782815
}
783-
AllocationRequest::Any => {
784-
find_any_chunk(&mut locked_list, num_pages, None)
816+
AllocationRequest::AlignedTo { alignment_4k_pages } => {
817+
find_any_chunk(&mut locked_list, num_pages, None, alignment_4k_pages)
785818
}
786819
AllocationRequest::WithinRange(range) => {
787-
find_any_chunk(&mut locked_list, num_pages, Some(range))
820+
find_any_chunk(&mut locked_list, num_pages, Some(range), 1)
821+
}
822+
AllocationRequest::Any => {
823+
find_any_chunk(&mut locked_list, num_pages, None, 1)
788824
}
789825
};
790826
res.map_err(From::from) // convert from AllocationError to &str

theseus_features/Cargo.toml

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -47,6 +47,7 @@ hello = { path = "../applications/hello", optional = true }
4747
raw_mode = { path = "../applications/raw_mode", optional = true }
4848
print_fault_log = { path = "../applications/print_fault_log", optional = true }
4949
seconds_counter = { path = "../applications/seconds_counter", optional = true }
50+
test_aligned_page_allocation = { path = "../applications/test_aligned_page_allocation", optional = true }
5051
test_async = { path = "../applications/test_async", optional = true }
5152
test_backtrace = { path = "../applications/test_backtrace", optional = true }
5253
test_block_io = { path = "../applications/test_block_io", optional = true }
@@ -146,6 +147,7 @@ theseus_tests = [
146147
"hello",
147148
"raw_mode",
148149
"seconds_counter",
150+
"test_aligned_page_allocation",
149151
"test_async",
150152
"test_backtrace",
151153
"test_block_io",

0 commit comments

Comments
 (0)