[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

@@ -18,12 +18,10 @@ public:
none,
event,
eventpair,
channel,
endpoint,
vms,
vmo,
vma,
job,
process,

View File

@@ -10,6 +10,7 @@ kutil::vector<process*> process::s_processes;
process::process(page_table *pml4) :
kobject(kobject::type::process),
m_pml4(pml4),
m_space(pml4),
m_next_handle(0),
m_state(state::running)
{

View File

@@ -6,6 +6,7 @@
#include "kutil/vector.h"
#include "objects/kobject.h"
#include "page_table.h"
#include "vm_space.h"
class process :
public kobject
@@ -37,6 +38,9 @@ public:
/// Get the process' page table root
page_table * pml4() { return m_pml4; }
/// Get the process' virtual memory space
vm_space & space() { return m_space; }
/// Create a new thread in this process
/// \args priority The new thread's scheduling priority
/// \args user If true, create a userspace stack for this thread
@@ -72,6 +76,8 @@ private:
uint32_t m_return_code;
page_table *m_pml4;
vm_space m_space;
kutil::vector<thread*> m_threads;
kutil::map<j6_handle_t, kobject*> m_handles;
j6_handle_t m_next_handle;

View File

@@ -0,0 +1,278 @@
#include "kernel_memory.h"
#include "objects/process.h"
#include "objects/vm_area.h"
using memory::frame_size;
vm_area::vm_area(size_t size, vm_flags flags) :
m_size(size),
m_flags(flags),
kobject(kobject::type::vma)
{
}
vm_area::~vm_area()
{
}
size_t
vm_area::resize(size_t size)
{
return m_size;
}
j6_status_t
vm_area::add_to(vm_space *space, uintptr_t *base)
{
if (!base || !space)
return j6_err_invalid_arg;
uintptr_t *prev = m_procs.find(space);
if (prev) {
*base = *prev;
return j6_status_exists;
}
if (!*base)
return j6_err_nyi;
m_procs.insert(space, *base);
for (auto &m : m_mappings)
if (m.state == state::mapped)
space->page_in(*base + m.offset, m.count, m.phys);
return j6_status_ok;
}
j6_status_t
vm_area::remove_from(vm_space *space)
{
uintptr_t *base = m_procs.find(space);
if (space && base) {
for (auto &m : m_mappings)
if (m.state == state::mapped)
space->page_out(*base + m.offset, m.count);
m_procs.erase(space);
}
return j6_status_ok;
}
size_t
vm_area::overlaps(uintptr_t offset, size_t pages, size_t *count)
{
size_t first = 0;
size_t n = 0;
uintptr_t end = offset + pages * frame_size;
for (size_t i = 0; i < m_mappings.count(); ++i) {
mapping &m = m_mappings[i];
uintptr_t map_end = m.offset + m.count * frame_size;
if (offset < map_end && end > m.offset) {
if (!first) first = i;
++n;
} else if (n) {
break;
}
}
if (count) *count = n;
return first;
}
bool
vm_area::commit(uintptr_t phys, uintptr_t offset, size_t count)
{
return add(offset, count, state::mapped, phys);
}
bool
vm_area::uncommit(uintptr_t offset, size_t count)
{
return remove(offset, count, state::reserved);
}
bool
vm_area::reserve(uintptr_t offset, size_t count)
{
return add(offset, count, state::reserved, 0);
}
bool
vm_area::unreserve(uintptr_t offset, size_t count)
{
return remove(offset, count, state::reserved);
}
vm_area::state
vm_area::get(uintptr_t offset, uintptr_t *phys)
{
size_t n = 0;
size_t o = overlaps(offset, 1, &n);
if (n) {
mapping &m = m_mappings[o];
if (phys) *phys = m.phys;
return m.state;
}
return state::none;
}
bool
vm_area::add(uintptr_t offset, size_t count, state desired, uintptr_t phys)
{
const bool do_map = desired == state::mapped;
size_t n = 0;
size_t o = overlaps(offset, count, &n);
if (!n) {
// In the clear, map it
size_t o = m_mappings.sorted_insert({
.offset = offset,
.count = count,
.phys = phys,
.state = desired});
n = 1;
if (do_map)
map(offset, count, phys);
} else if (desired == state::mapped) {
// Mapping overlaps not allowed
return false;
}
// Any overlaps with different states is not allowed
for (size_t i = o; i < o+n; ++i)
if (m_mappings[i].state != desired)
return false;
// Try to expand to abutting similar areas
if (o > 0 &&
m_mappings[o-1].state == desired &&
m_mappings[o-1].end() == offset &&
(!do_map || m_mappings[o-1].phys_end() == phys)) {
--o;
++n;
}
uintptr_t end = offset + count * frame_size;
uintptr_t pend = offset + count * frame_size;
if (o + n < m_mappings.count() &&
m_mappings[o+n].state == desired &&
end == m_mappings[o+n].offset &&
(!do_map || m_mappings[o-1].phys == pend)) {
++n;
}
// Use the first overlap block as our new block
mapping &first = m_mappings[o];
mapping &last = m_mappings[o + n -1];
if (offset < first.offset)
first.offset = offset;
size_t diff =
(end > last.end() ? end : last.end()) -
first.offset;
first.count = diff / frame_size;
if (n > 1)
m_mappings.remove_at(o+1, n-1);
return true;
}
bool
vm_area::remove(uintptr_t offset, size_t count, state expected)
{
size_t n = 0;
size_t o = overlaps(offset, count, &n);
if (!n) return true;
// Any overlaps with different states is not allowed
for (size_t i = o; i < o+n; ++i)
if (m_mappings[i].state != expected)
return false;
mapping *first = &m_mappings[o];
mapping *last = &m_mappings[o+n-1];
uintptr_t end = offset + count * frame_size;
size_t leading = offset - first->offset;
size_t trailing = last->end() - end;
// if were entirely contained in one, we need to split it
if (leading && trailing && n == 1) {
size_t i = m_mappings.sorted_insert({
.offset = end,
.count = trailing / frame_size,
.state = first->state,
});
last = &m_mappings[i];
trailing = 0;
first->count -= last->count;
if (first->state == state::mapped)
last->phys = first->phys + first->count * frame_size;
}
if (leading) {
size_t remove_pages = first->count;
first->count = leading / frame_size;
remove_pages -= first->count;
if (expected == state::mapped)
unmap(first->end(), remove_pages);
}
if (trailing) {
uintptr_t remove_off = last->offset;
size_t remove_pages = last->count;
last->offset = end;
last->count = trailing / frame_size;
remove_pages -= last->count;
if (expected == state::mapped) {
unmap(remove_off, remove_pages);
last->phys += remove_pages * frame_size;
}
}
size_t delete_start = 0;
size_t delete_count = 0;
for (size_t i = o; i < o+n; ++i) {
mapping &m = m_mappings[i];
if (offset <= m.offset && end >= m.end()) {
if (!delete_count) delete_start = i;
++delete_count;
if (expected == state::mapped)
unmap(m.offset, m.count);
}
}
if (delete_count)
m_mappings.remove_at(delete_start, delete_count);
return true;
}
void
vm_area::map(uintptr_t offset, size_t count, uintptr_t phys)
{
for (auto &it : m_procs) {
uintptr_t addr = it.val + offset;
vm_space *space = it.key;
space->page_in(addr, count, phys);
}
}
void
vm_area::unmap(uintptr_t offset, size_t count)
{
for (auto &it : m_procs) {
uintptr_t addr = it.val + offset;
vm_space *space = it.key;
space->page_out(addr, count);
}
}

View File

@@ -0,0 +1,127 @@
#pragma once
/// \file vm_area.h
/// Definition of VMA objects and related functions
#include "j6/signals.h"
#include "kutil/enum_bitfields.h"
#include "kutil/map.h"
#include "kernel_memory.h"
#include "objects/kobject.h"
class vm_space;
enum class vm_flags : uint32_t
{
none = 0x00000000,
zero = 0x00000001,
contiguous = 0x00000002,
large_pages = 0x00000100,
huge_pages = 0x00000200,
offset_linear = 0x80000000
};
IS_BITFIELD(vm_flags);
/// Virtual memory areas allow control over memory allocation
class vm_area :
public kobject
{
public:
/// Constructor.
/// \arg size Initial virtual size of the memory area
/// \arg flags Flags for this memory area
vm_area(size_t size, vm_flags flags = vm_flags::none);
virtual ~vm_area();
/// Get the current virtual size of the memory area
size_t size() const { return m_size; }
/// Change the virtual size of the memory area. This may cause
/// deallocation if the new size is smaller than the current size.
/// Note that if resizing is unsuccessful, the previous size will
/// be returned.
/// \arg size The desired new virtual size
/// \returns The new virtual size
size_t resize(size_t size);
/// Add this virtual area to a process' virtual address space. If
/// the given base address is zero, a base address will be chosen
/// automatically.
/// \arg s The target address space
/// \arg base [in] The desired base address [out] the actual base address
/// \returns j6_status_ok on success
j6_status_t add_to(vm_space *s, uintptr_t *base);
/// Remove this virtual area from a process' virtual address space.
/// \arg s The target address space
/// \returns j6_status_ok on success
j6_status_t remove_from(vm_space *s);
/// Commit contiguous physical pages to this area
/// \arg phys The physical address of the first page
/// \arg offset The offset from the start of this area these pages represent
/// \arg count The number of pages
/// \returns True if successful
bool commit(uintptr_t phys, uintptr_t offset, size_t count);
/// Uncommit physical pages from this area
/// \arg offset The offset from the start of this area these pages represent
/// \arg count The number of pages
/// \returns True if successful
bool uncommit(uintptr_t offset, size_t count);
/// Reserve a range of this area to never commit
/// \arg offset The offset from the start of this area
/// \arg count The number of pages
/// \returns True if successful
bool reserve(uintptr_t offset, size_t count);
/// Unreserve a range of this area to allow commits
/// \arg offset The offset from the start of this area
/// \arg count The number of pages
/// \returns True if successful
bool unreserve(uintptr_t offset, size_t count);
enum class state : uint8_t { none, reserved, mapped };
/// Get the physical page representing an offset in this area
/// \arg offset The offset into the area
/// \arg phys [out] The physical page address
/// \returns State of the given address
state get(uintptr_t offset, uintptr_t *phys);
/// Get the flags set for this area
vm_flags flags() const { return m_flags; }
private:
struct mapping {
uintptr_t offset;
size_t count;
uintptr_t phys;
state state;
int compare(const struct mapping &o) const {
return offset > o.offset ? 1 : offset < o.offset ? -1 : 0;
}
inline uintptr_t end() const { return offset + count * memory::frame_size; }
inline uintptr_t phys_end() const { return phys + count * memory::frame_size; }
};
size_t overlaps(uintptr_t offset, size_t pages, size_t *count);
bool add(uintptr_t offset, size_t count, state desired, uintptr_t phys);
bool remove(uintptr_t offset, size_t count, state expected);
void map(uintptr_t offset, size_t count, uintptr_t phys);
void unmap(uintptr_t offset, size_t count);
size_t m_size;
vm_flags m_flags;
kutil::map<vm_space*, uintptr_t> m_procs;
kutil::vector<mapping> m_mappings;
};