Add FrameAllocator trait, move frame allocator and mapper to library

This commit is contained in:
Jeremy Soller
2020-09-08 10:52:52 -06:00
parent 29945b84b1
commit fcb64422c4
9 changed files with 387 additions and 312 deletions
-3
View File
@@ -6,9 +6,6 @@ edition = "2018"
# See more keys and their definitions at https://doc.rust-lang.org/cargo/reference/manifest.html
[dependencies]
[features]
default = ["std"]
std = []
+229
View File
@@ -0,0 +1,229 @@
use core::{
marker::PhantomData,
mem,
};
use crate::{
Arch,
BumpAllocator,
FrameAllocator,
MemoryArea,
PhysicalAddress,
VirtualAddress,
};
#[derive(Clone, Copy, Debug)]
#[repr(packed)]
struct BuddyEntry {
base: PhysicalAddress,
size: usize,
map: PhysicalAddress,
}
impl BuddyEntry {
pub fn empty() -> Self {
Self {
base: PhysicalAddress::new(0),
size: 0,
map: PhysicalAddress::new(0),
}
}
}
#[derive(Clone, Copy, Debug)]
#[repr(packed)]
struct BuddyMapFooter {
next: PhysicalAddress,
//TODO: index of last known free bit
}
pub struct BuddyAllocator<A> {
table_phys: PhysicalAddress,
table_virt: VirtualAddress,
clear_frees: bool,
phantom: PhantomData<A>,
}
impl<A: Arch> BuddyAllocator<A> {
const BUDDY_ENTRIES: usize = A::PAGE_SIZE / mem::size_of::<BuddyEntry>();
const MAP_PAGE_BYTES: usize = (A::PAGE_SIZE - mem::size_of::<BuddyMapFooter>());
const MAP_PAGE_BITS: usize = Self::MAP_PAGE_BYTES * 8;
pub unsafe fn new(mut bump_allocator: BumpAllocator<A>, clear_frees: bool) -> Option<Self> {
// Allocate buddy table
let table_phys = bump_allocator.allocate(1)?;
let table_virt = A::phys_to_virt(table_phys);
for i in 0 .. (A::PAGE_SIZE / mem::size_of::<BuddyEntry>()) {
let virt = table_virt.add(i * mem::size_of::<BuddyEntry>());
A::write(virt, BuddyEntry::empty());
}
let mut allocator = Self {
table_phys,
table_virt,
clear_frees,
phantom: PhantomData,
};
// Add areas to buddy table, combining areas when possible
for area in bump_allocator.areas().iter() {
for i in 0 .. (A::PAGE_SIZE / mem::size_of::<BuddyEntry>()) {
let virt = table_virt.add(i * mem::size_of::<BuddyEntry>());
let mut entry = A::read::<BuddyEntry>(virt);
let inserted = if area.base.add(area.size) == entry.base {
// Combine entry at start
entry.base = area.base;
entry.size += area.size;
true
} else if area.base == entry.base.add(entry.size) {
// Combine entry at end
entry.size += area.size;
true
} else if entry.size == 0 {
// Create new entry
entry.base = area.base;
entry.size = area.size;
true
} else {
false
};
if inserted {
A::write(virt, entry);
break;
}
}
}
//TODO: sort areas?
// Allocate buddy maps
for i in 0 .. Self::BUDDY_ENTRIES {
let virt = table_virt.add(i * mem::size_of::<BuddyEntry>());
let mut entry = A::read::<BuddyEntry>(virt);
if entry.size > 0 {
println!("{}: {:X?}", i, entry);
let pages = entry.size / A::PAGE_SIZE;
println!(" pages: {}", pages);
let map_pages = (pages + (Self::MAP_PAGE_BITS - 1)) / Self::MAP_PAGE_BITS;
println!(" map pages: {}", map_pages);
for _ in 0 .. map_pages {
let map_phys = bump_allocator.allocate(1)?;
let map_virt = A::phys_to_virt(map_phys);
for i in 0..Self::MAP_PAGE_BYTES {
A::write(map_virt.add(i), 0);
}
A::write(map_virt.add(Self::MAP_PAGE_BYTES), BuddyMapFooter {
next: entry.map,
});
entry.map = map_phys;
}
A::write(virt, entry);
}
}
// Mark unused areas as free
let mut area_offset = bump_allocator.offset();
for area in bump_allocator.areas().iter() {
if area_offset < area.size {
let area_base = area.base.add(area_offset);
let area_size = area.size - area_offset;
allocator.free(area_base, area_size);
area_offset = 0;
} else {
area_offset -= area.size;
}
}
Some(allocator)
}
}
impl<A: Arch> FrameAllocator for BuddyAllocator<A> {
unsafe fn allocate(&mut self, size: usize) -> Option<PhysicalAddress> {
//TODO: support other sizes
if size != A::PAGE_SIZE {
return None;
}
for i in 0 .. Self::BUDDY_ENTRIES {
let virt = self.table_virt.add(i * mem::size_of::<BuddyEntry>());
let entry = A::read::<BuddyEntry>(virt);
//TODO: improve performance
let mut map_phys = entry.map;
let mut offset = 0;
while map_phys.data() != 0 {
let map_virt = A::phys_to_virt(map_phys);
for i in 0 .. Self::MAP_PAGE_BYTES {
let map_byte_virt = map_virt.add(i);
let mut value: u8 = A::read(map_byte_virt);
if (value & u8::MAX) != 0 {
for bit in 0..8 {
if (value & (1 << bit)) != 0 {
value &= !(1 << bit);
A::write(map_byte_virt, value);
let page_phys = entry.base.add(offset + bit * A::PAGE_SIZE);
return Some(page_phys);
}
}
}
offset += A::PAGE_SIZE * 8;
}
let footer = A::read::<BuddyMapFooter>(map_virt);
map_phys = footer.next;
}
}
None
}
unsafe fn free(&mut self, base: PhysicalAddress, size: usize) {
if self.clear_frees {
// Zero freed pages for security, also ensures all allocs are zerod
//TODO: improve performance
//TODO: assumes linear physical mapping
let mut zero_virt = A::phys_to_virt(base);
let zero_end = zero_virt.add(size);
while zero_virt < zero_end {
A::write(zero_virt, 0usize);
zero_virt = zero_virt.add(mem::size_of::<usize>());
}
}
for i in 0 .. Self::BUDDY_ENTRIES {
let virt = self.table_virt.add(i * mem::size_of::<BuddyEntry>());
let entry = A::read::<BuddyEntry>(virt);
if base >= entry.base && base.add(size) <= entry.base.add(entry.size) {
//TODO: Correct logic
let pages = size / A::PAGE_SIZE;
for page in 0 .. pages {
let page_base = base.add(page * A::PAGE_SIZE);
let index = (page_base.data() - entry.base.data()) / A::PAGE_SIZE;
let mut map_page = index / Self::MAP_PAGE_BITS;
let map_bit = index % Self::MAP_PAGE_BITS;
//TODO: improve performance
let mut map_phys = entry.map;
loop {
if map_phys.data() == 0 { unimplemented!() }
let map_virt = A::phys_to_virt(map_phys);
if map_page == 0 {
let map_byte_virt = map_virt.add(map_bit / 8);
let mut value: u8 = A::read(map_byte_virt);
value |= 1 << (map_bit % 8);
A::write(map_byte_virt, value);
break;
} else {
let footer = A::read::<BuddyMapFooter>(map_virt);
map_phys = footer.next;
map_page -= 1;
}
}
}
}
}
}
}
+55
View File
@@ -0,0 +1,55 @@
use core::marker::PhantomData;
use crate::{
Arch,
FrameAllocator,
MemoryArea,
PhysicalAddress,
};
pub struct BumpAllocator<A> {
areas: &'static [MemoryArea],
offset: usize,
phantom: PhantomData<A>,
}
impl<A: Arch> BumpAllocator<A> {
pub fn new(areas: &'static [MemoryArea], offset: usize) -> Self {
Self {
areas,
offset,
phantom: PhantomData,
}
}
pub fn areas(&self) -> &'static [MemoryArea] {
self.areas
}
pub fn offset(&self) -> usize {
self.offset
}
}
impl<A: Arch> FrameAllocator for BumpAllocator<A> {
unsafe fn allocate(&mut self, count: usize) -> Option<PhysicalAddress> {
//TODO: support allocation of multiple pages
if count != 1 {
return None;
}
let mut offset = self.offset;
for area in self.areas.iter() {
if offset < area.size {
self.offset += A::PAGE_SIZE;
return Some(area.base.add(offset));
}
offset -= area.size;
}
None
}
unsafe fn free(&mut self, address: PhysicalAddress, count: usize) {
unimplemented!();
}
}
+12
View File
@@ -0,0 +1,12 @@
use crate::PhysicalAddress;
pub use self::buddy::*;
pub use self::bump::*;
mod buddy;
mod bump;
pub trait FrameAllocator {
unsafe fn allocate(&mut self, count: usize) -> Option<PhysicalAddress>;
unsafe fn free(&mut self, address: PhysicalAddress, count: usize);
}
+3
View File
@@ -0,0 +1,3 @@
pub use self::frame::*;
mod frame;
+3 -4
View File
@@ -2,13 +2,12 @@
#![feature(asm)]
pub use crate::{
allocator::*,
arch::*,
page::{
PageEntry,
PageTable
},
page::*,
};
mod allocator;
mod arch;
mod page;
+24 -305
View File
@@ -4,9 +4,13 @@ use rmm::{
GIGABYTE,
TERABYTE,
Arch,
BuddyAllocator,
BumpAllocator,
EmulateArch,
FrameAllocator,
MemoryArea,
PageEntry,
PageMapper,
PageTable,
PhysicalAddress,
VirtualAddress,
@@ -49,34 +53,6 @@ unsafe fn dump_tables<A: Arch>(table: PageTable<A>) {
}
}
pub struct BumpAllocator<A> {
areas: &'static [MemoryArea],
offset: usize,
phantom: PhantomData<A>,
}
impl<A: Arch> BumpAllocator<A> {
pub fn new(areas: &'static [MemoryArea], offset: usize) -> Self {
Self {
areas,
offset,
phantom: PhantomData,
}
}
pub fn allocate(&mut self) -> Option<PhysicalAddress> {
let mut offset = self.offset;
for area in self.areas.iter() {
if offset < area.size {
self.offset += A::PAGE_SIZE;
return Some(area.base.add(offset));
}
offset -= area.size;
}
None
}
}
pub struct SlabNode<A> {
next: PhysicalAddress,
count: usize,
@@ -190,267 +166,6 @@ impl<A: Arch> SlabAllocator<A> {
}
}
#[derive(Clone, Copy, Debug)]
#[repr(packed)]
pub struct BuddyEntry {
pub base: PhysicalAddress,
pub size: usize,
pub map: PhysicalAddress,
}
impl BuddyEntry {
pub fn empty() -> Self {
Self {
base: PhysicalAddress::new(0),
size: 0,
map: PhysicalAddress::new(0),
}
}
}
#[derive(Clone, Copy, Debug)]
#[repr(packed)]
pub struct BuddyMapFooter {
pub next: PhysicalAddress,
//TODO: index of last known free bit
}
pub struct BuddyAllocator<A> {
table_phys: PhysicalAddress,
table_virt: VirtualAddress,
clear_frees: bool,
phantom: PhantomData<A>,
}
impl<A: Arch> BuddyAllocator<A> {
const BUDDY_ENTRIES: usize = A::PAGE_SIZE / mem::size_of::<BuddyEntry>();
const MAP_PAGE_BYTES: usize = (A::PAGE_SIZE - mem::size_of::<BuddyMapFooter>());
const MAP_PAGE_BITS: usize = Self::MAP_PAGE_BYTES * 8;
pub unsafe fn new(areas: &'static [MemoryArea], offset: usize, clear_frees: bool) -> Option<Self> {
// First, we need an allocator, so we can allocate the buddy tables
// Since the tables are static, we can use the bump allocator
let mut bump_allocator = BumpAllocator::<A>::new(areas, offset);
// Allocate buddy table
let table_phys = bump_allocator.allocate()?;
let table_virt = A::phys_to_virt(table_phys);
for i in 0 .. (A::PAGE_SIZE / mem::size_of::<BuddyEntry>()) {
let virt = table_virt.add(i * mem::size_of::<BuddyEntry>());
A::write(virt, BuddyEntry::empty());
}
let mut allocator = Self {
table_phys,
table_virt,
clear_frees,
phantom: PhantomData,
};
// Add areas to buddy table, combining areas when possible
for area in areas.iter() {
for i in 0 .. (A::PAGE_SIZE / mem::size_of::<BuddyEntry>()) {
let virt = table_virt.add(i * mem::size_of::<BuddyEntry>());
let mut entry = A::read::<BuddyEntry>(virt);
let inserted = if area.base.add(area.size) == entry.base {
// Combine entry at start
entry.base = area.base;
entry.size += area.size;
true
} else if area.base == entry.base.add(entry.size) {
// Combine entry at end
entry.size += area.size;
true
} else if entry.size == 0 {
// Create new entry
entry.base = area.base;
entry.size = area.size;
true
} else {
false
};
if inserted {
A::write(virt, entry);
break;
}
}
}
//TODO: sort areas?
// Allocate buddy maps
for i in 0 .. Self::BUDDY_ENTRIES {
let virt = table_virt.add(i * mem::size_of::<BuddyEntry>());
let mut entry = A::read::<BuddyEntry>(virt);
if entry.size > 0 {
println!("{}: {:X?}", i, entry);
let pages = entry.size / A::PAGE_SIZE;
println!(" pages: {}", pages);
let map_pages = (pages + (Self::MAP_PAGE_BITS - 1)) / Self::MAP_PAGE_BITS;
println!(" map pages: {}", map_pages);
for _ in 0 .. map_pages {
let map_phys = bump_allocator.allocate()?;
let map_virt = A::phys_to_virt(map_phys);
for i in 0..Self::MAP_PAGE_BYTES {
A::write(map_virt.add(i), 0);
}
A::write(map_virt.add(Self::MAP_PAGE_BYTES), BuddyMapFooter {
next: entry.map,
});
entry.map = map_phys;
}
A::write(virt, entry);
}
}
// Mark unused areas as free
let mut area_offset = bump_allocator.offset;
for area in areas.iter() {
if area_offset < area.size {
let area_base = area.base.add(area_offset);
let area_size = area.size - area_offset;
allocator.free(area_base, area_size);
area_offset = 0;
} else {
area_offset -= area.size;
}
}
Some(allocator)
}
pub unsafe fn allocate(&mut self, size: usize) -> Option<PhysicalAddress> {
//TODO: support other sizes
if size != A::PAGE_SIZE {
return None;
}
for i in 0 .. Self::BUDDY_ENTRIES {
let virt = self.table_virt.add(i * mem::size_of::<BuddyEntry>());
let entry = A::read::<BuddyEntry>(virt);
//TODO: improve performance
let mut map_phys = entry.map;
let mut offset = 0;
while map_phys.data() != 0 {
let map_virt = A::phys_to_virt(map_phys);
for i in 0 .. Self::MAP_PAGE_BYTES {
let map_byte_virt = map_virt.add(i);
let mut value: u8 = A::read(map_byte_virt);
if (value & u8::MAX) != 0 {
for bit in 0..8 {
if (value & (1 << bit)) != 0 {
value &= !(1 << bit);
A::write(map_byte_virt, value);
let page_phys = entry.base.add(offset + bit * A::PAGE_SIZE);
return Some(page_phys);
}
}
}
offset += A::PAGE_SIZE * 8;
}
let footer = A::read::<BuddyMapFooter>(map_virt);
map_phys = footer.next;
}
}
None
}
pub unsafe fn free(&mut self, base: PhysicalAddress, size: usize) {
if self.clear_frees {
// Zero freed pages for security, also ensures all allocs are zerod
//TODO: improve performance
//TODO: assumes linear physical mapping
let mut zero_virt = A::phys_to_virt(base);
let zero_end = zero_virt.add(size);
while zero_virt < zero_end {
A::write(zero_virt, 0usize);
zero_virt = zero_virt.add(mem::size_of::<usize>());
}
}
for i in 0 .. Self::BUDDY_ENTRIES {
let virt = self.table_virt.add(i * mem::size_of::<BuddyEntry>());
let entry = A::read::<BuddyEntry>(virt);
if base >= entry.base && base.add(size) <= entry.base.add(entry.size) {
//TODO: Correct logic
let pages = size / A::PAGE_SIZE;
for page in 0 .. pages {
let page_base = base.add(page * A::PAGE_SIZE);
let index = (page_base.data() - entry.base.data()) / A::PAGE_SIZE;
let mut map_page = index / Self::MAP_PAGE_BITS;
let map_bit = index % Self::MAP_PAGE_BITS;
//TODO: improve performance
let mut map_phys = entry.map;
loop {
if map_phys.data() == 0 { unimplemented!() }
let map_virt = A::phys_to_virt(map_phys);
if map_page == 0 {
let map_byte_virt = map_virt.add(map_bit / 8);
let mut value: u8 = A::read(map_byte_virt);
value |= 1 << (map_bit % 8);
A::write(map_byte_virt, value);
break;
} else {
let footer = A::read::<BuddyMapFooter>(map_virt);
map_phys = footer.next;
map_page -= 1;
}
}
}
}
}
}
}
pub struct Mapper<A> {
table_addr: PhysicalAddress,
allocator: BumpAllocator<A>,
}
impl<A: Arch> Mapper<A> {
pub unsafe fn new(mut allocator: BumpAllocator<A>) -> Option<Self> {
let table_addr = allocator.allocate()?;
Some(Self {
table_addr,
allocator,
})
}
pub unsafe fn map(&mut self, virt: VirtualAddress, entry: PageEntry<A>) -> Option<()> {
let mut table = PageTable::new(
VirtualAddress::new(0),
self.table_addr,
A::PAGE_LEVELS - 1
);
loop {
let i = table.index_of(virt)?;
if table.level() == 0 {
//TODO: check for overwriting entry
table.set_entry(i, entry);
return Some(());
} else {
let next_opt = table.next(i);
let next = match next_opt {
Some(some) => some,
None => {
let phys = self.allocator.allocate()?;
//TODO: correct flags?
table.set_entry(i, PageEntry::new(phys.data() | A::ENTRY_FLAG_WRITABLE | A::ENTRY_FLAG_PRESENT));
table.next(i)?
}
};
table = next;
}
}
}
}
unsafe fn new_tables<A: Arch>(areas: &'static [MemoryArea]) {
// First, calculate how much memory we have
let mut size = 0;
@@ -461,29 +176,33 @@ unsafe fn new_tables<A: Arch>(areas: &'static [MemoryArea]) {
println!("Memory: {}", format_size(size));
// Create a basic allocator for the first pages
let allocator = BumpAllocator::<A>::new(areas, 0);
let mut bump_allocator = BumpAllocator::<A>::new(areas, 0);
// Map all physical areas at PHYS_OFFSET
let mut mapper = Mapper::new(allocator).expect("failed to create Mapper");
for area in areas.iter() {
for i in 0..area.size / A::PAGE_SIZE {
let phys = area.base.add(i * A::PAGE_SIZE);
let virt = A::phys_to_virt(phys);
mapper.map(
virt,
PageEntry::new(phys.data() | A::ENTRY_FLAG_WRITABLE | A::ENTRY_FLAG_PRESENT)
).expect("failed to map frame");
{
// Map all physical areas at PHYS_OFFSET
let mut mapper = PageMapper::<A, _>::new(
&mut bump_allocator
).expect("failed to create Mapper");
for area in areas.iter() {
for i in 0..area.size / A::PAGE_SIZE {
let phys = area.base.add(i * A::PAGE_SIZE);
let virt = A::phys_to_virt(phys);
mapper.map(
virt,
PageEntry::new(phys.data() | A::ENTRY_FLAG_WRITABLE | A::ENTRY_FLAG_PRESENT)
).expect("failed to map frame");
}
}
// Use the new table
mapper.activate();
}
// Use the new table
A::set_table(mapper.table_addr);
// Create the physical memory map
let offset = mapper.allocator.offset;
let offset = bump_allocator.offset();
println!("Permanently used: {}", format_size(offset));
let mut allocator = BuddyAllocator::<A>::new(areas, offset, true).unwrap();
let mut allocator = BuddyAllocator::<A>::new(bump_allocator, true).unwrap();
for i in 0..16 {
let phys_opt = allocator.allocate(4 * KILOBYTE);
println!("4 KB page {}: {:X?}", i, phys_opt);
+59
View File
@@ -0,0 +1,59 @@
use core::marker::PhantomData;
use crate::{
Arch,
FrameAllocator,
PageEntry,
PageTable,
PhysicalAddress,
VirtualAddress,
};
pub struct PageMapper<'f, A, F> {
table_addr: PhysicalAddress,
allocator: &'f mut F,
phantom: PhantomData<A>,
}
impl<'f, A: Arch, F: FrameAllocator> PageMapper<'f, A, F> {
pub unsafe fn new(allocator: &'f mut F) -> Option<Self> {
let table_addr = allocator.allocate(1)?;
Some(Self {
table_addr,
allocator,
phantom: PhantomData,
})
}
pub unsafe fn activate(&mut self) {
A::set_table(self.table_addr);
}
pub unsafe fn map(&mut self, virt: VirtualAddress, entry: PageEntry<A>) -> Option<()> {
let mut table = PageTable::new(
VirtualAddress::new(0),
self.table_addr,
A::PAGE_LEVELS - 1
);
loop {
let i = table.index_of(virt)?;
if table.level() == 0 {
//TODO: check for overwriting entry
table.set_entry(i, entry);
return Some(());
} else {
let next_opt = table.next(i);
let next = match next_opt {
Some(some) => some,
None => {
let phys = self.allocator.allocate(1)?;
//TODO: correct flags?
table.set_entry(i, PageEntry::new(phys.data() | A::ENTRY_FLAG_WRITABLE | A::ENTRY_FLAG_PRESENT));
table.next(i)?
}
};
table = next;
}
}
}
}
+2
View File
@@ -1,7 +1,9 @@
pub use self::{
entry::PageEntry,
mapper::PageMapper,
table::PageTable,
};
mod entry;
mod mapper;
mod table;