[kernel] Begin replacing page_manager with vm_space

This is the first commit of several reworking the VM system. The main
focus is replacing page_manager's global functionality with objects
representing individual VM spaces. The main changes in this commit were:

- Adding the (as yet unused) vm_area object, which will be the main
  point of control for programs to allocate or share memory.
- Replace the old vm_space with a new one based on state in its page
  tables. They will also be containers for vm_areas.
- vm_space takes over from page_manager as the page fault handler
- Commented out the page walking in memory_bootstrap; I'll probably need
  to recreate this functionality, but it was broken as it was.
- Split out the page_table.h implementations from page_manager.cpp into
  the new page_table.cpp, updated it, and added page_table::iterator as
  well.
This commit is contained in:
2020-09-17 00:48:17 -07:00
parent ca7f78565d
commit 9aa08a70cf
16 changed files with 1004 additions and 401 deletions

View File

@@ -1,256 +1,125 @@
#include <algorithm>
#include "kutil/vector.h"
#include "log.h"
#include "objects/process.h"
#include "objects/vm_area.h"
#include "page_manager.h"
#include "vm_space.h"
using node_type = kutil::avl_node<vm_range>;
using node_vec = kutil::vector<node_type*>;
DEFINE_SLAB_ALLOCATOR(node_type, 1);
vm_space::vm_space(uintptr_t start, size_t size)
int
vm_space::area::compare(const vm_space::area &o) const
{
node_type *node = new node_type;
node->address = start;
node->size = size;
node->state = vm_state::none;
m_ranges.insert(node);
log::info(logs::vmem, "Creating address space from %016llx-%016llx",
start, start+size);
if (base > o.base) return 1;
else if (base < o.base) return -1;
else return 0;
}
vm_space::vm_space()
bool
vm_space::area::operator==(const vm_space::area &o) const
{
return o.base == base && o.area == area;
}
vm_space::vm_space(page_table *p, bool kernel) :
m_kernel(kernel),
m_pml4(p)
{
}
inline static bool
contains(node_type *node, uintptr_t start, size_t size)
vm_space::~vm_space()
{
return start >= node->address &&
size <= node->size;
for (auto &a : m_areas)
a.area->remove_from(this);
}
inline static bool
overlaps(node_type *node, uintptr_t start, size_t size)
vm_space &
vm_space::kernel_space()
{
return start < node->end() &&
(start + size) > node->address;
extern vm_space &g_kernel_space;
return g_kernel_space;
}
static node_type *
find_overlapping(node_type *from, uintptr_t start, size_t size)
bool
vm_space::add(uintptr_t base, vm_area *area)
{
while (from) {
if (overlaps(from, start, size))
return from;
from = start < from->address ?
from->left() :
from->right();
}
return nullptr;
//TODO: check for collisions
m_areas.sorted_insert({base, area});
return true;
}
node_type *
vm_space::split_out(node_type *node, uintptr_t start, size_t size, vm_state state)
bool
vm_space::remove(vm_area *area)
{
// No cross-boundary splits allowed for now
const bool contained = contains(node, start, size);
kassert(contained, "Tried to split an address range across existing boundaries");
if (!contained)
return nullptr;
vm_state old_state = node->state;
if (state == old_state)
return node;
node->state = state;
log::debug(logs::vmem, "Splitting out region %016llx-%016llx[%d] from %016llx-%016llx[%d]",
start, start+size, state, node->address, node->end(), old_state);
bool do_consolidate = false;
if (node->address < start) {
// Split off rest into new node
size_t leading = start - node->address;
node_type *next = new node_type;
next->address = start;
next->size = node->size - leading;
next->state = state;
node->size = leading;
node->state = old_state;
log::debug(logs::vmem,
" leading region %016llx-%016llx[%d]",
node->address, node->address + node->size, node->state);
m_ranges.insert(next);
node = next;
} else {
do_consolidate = true;
}
if (node->end() > start + size) {
// Split off remaining into new node
size_t trailing = node->size - size;
node->size -= trailing;
node_type *next = new node_type;
next->state = old_state;
next->address = node->end();
next->size = trailing;
log::debug(logs::vmem,
" tailing region %016llx-%016llx[%d]",
next->address, next->address + next->size, next->state);
m_ranges.insert(next);
} else {
do_consolidate = true;
}
if (do_consolidate)
node = consolidate(node);
return node;
}
node_type *
vm_space::find_empty(node_type *node, size_t size, vm_state state)
{
if (node->state == vm_state::none && node->size >= size)
return split_out(node, node->address, size, state);
if (node->left()) {
node_type *found = find_empty(node->left(), size, state);
if (found)
return found;
}
if (node->right()) {
node_type *found = find_empty(node->right(), size, state);
if (found)
return found;
}
return nullptr;
}
inline void gather(node_type *node, node_vec &vec)
{
if (node) {
gather(node->left(), vec);
vec.append(node);
gather(node->right(), vec);
}
}
node_type *
vm_space::consolidate(node_type *needle)
{
node_vec nodes(m_ranges.count());
gather(m_ranges.root(), nodes);
node_type *prev = nullptr;
for (auto *node : nodes) {
log::debug(logs::vmem,
"* Existing region %016llx-%016llx[%d]",
node->address, node->address + node->size, node->state);
if (prev && node->address == prev->end() && node->state == prev->state) {
log::debug(logs::vmem,
"Joining regions %016llx-%016llx[%d] %016llx-%016llx[%d]",
prev->address, prev->address + prev->size, prev->state,
node->address, node->address + node->size, node->state);
prev->size += node->size;
if (needle == node)
needle = prev;
m_ranges.remove(node);
} else {
prev = node;
for (auto &a : m_areas) {
if (a.area == area) {
m_areas.remove(a);
return true;
}
}
return needle;
return false;
}
uintptr_t
vm_space::reserve(uintptr_t start, size_t size)
vm_area *
vm_space::get(uintptr_t addr, uintptr_t *base)
{
if (start == 0) {
log::debug(logs::vmem, "Reserving any region of size %llx", size);
node_type *node = find_empty(m_ranges.root(), size, vm_state::reserved);
if (!node) {
log::debug(logs::vmem, " found no large enough region");
return 0;
for (auto &a : m_areas) {
uintptr_t end = a.base + a.area->size();
if (addr >= a.base && addr < end) {
if (base) *base = a.base;
return a.area;
}
return node->address;
}
log::debug(logs::vmem, "Reserving region %016llx-%016llx",
start, start+size);
node_type *node = find_overlapping(m_ranges.root(), start, size);
if (!node) {
log::debug(logs::vmem, " found no match");
return 0;
}
node = split_out(node, start, size, vm_state::reserved);
return node ? start : 0;
return nullptr;
}
void
vm_space::unreserve(uintptr_t start, size_t size)
vm_space::page_in(uintptr_t addr, size_t count, uintptr_t phys)
{
log::debug(logs::vmem, "Unreserving region %016llx-%016llx", start, start+size);
node_type *node = find_overlapping(m_ranges.root(), start, size);
if (!node || !contains(node, start, size)) {
log::debug(logs::vmem, " found no match");
return;
}
split_out(node, start, size, vm_state::none);
page_manager *pm = page_manager::get();
pm->page_in(m_pml4, phys, addr, count, is_kernel());
}
uintptr_t
vm_space::commit(uintptr_t start, size_t size)
void
vm_space::page_out(uintptr_t addr, size_t count)
{
if (start == 0) {
log::debug(logs::vmem, "Committing any region of size %llx", size);
node_type *node = find_empty(m_ranges.root(), size, vm_state::committed);
if (!node) {
log::debug(logs::vmem, " found no large enough region");
return 0;
}
return node->address;
}
log::debug(logs::vmem, "Committing region %016llx-%016llx",
start, start+size);
node_type *node = find_overlapping(m_ranges.root(), start, size);
if (!node) {
log::debug(logs::vmem, " found no match");
return 0;
}
node = split_out(node, start, size, vm_state::committed);
return node ? start : 0;
page_manager *pm = page_manager::get();
pm->page_out(m_pml4, addr, count, false);
}
vm_state
vm_space::get(uintptr_t addr)
void
vm_space::allow(uintptr_t start, size_t length, bool allow)
{
node_type *node = find_overlapping(m_ranges.root(), addr, 1);
return node ? node->state : vm_state::unknown;
using level = page_table::level;
kassert((start & 0xfff) == 0, "non-page-aligned address");
kassert((length & 0xfff) == 0, "non-page-aligned length");
const uintptr_t end = start + length;
page_table::iterator it {start, m_pml4};
while (it.vaddress() < end) {
level d = it.align();
while (it.end(d) > end) ++d;
it.allow(d-1, allow);
it.next(d);
}
}
bool
vm_space::handle_fault(uintptr_t addr, fault_type fault)
{
uintptr_t page = addr & ~0xfffull;
page_table::iterator it {addr, m_pml4};
if (!it.allowed())
return false;
// TODO: pull this out of PM
page_manager::get()->map_pages(page, 1, m_pml4);
/* TODO: Tell the VMA if there is one
uintptr_t base = 0;
vm_area *area = get(addr, &base);
*/
return true;
}