[kernel] Spit out vm_area types

The vm_space allow() functionality was a bit janky; using VMAs for all
regions would be a lot cleaner. To that end, this change:

- Adds a "static array" ctor to kutil::vector for setting the kernel
  address space's VMA list. This way a kernel heap VMA can be created
  without the heap already existing.
- Splits vm_area into different subclasses depending on desired behavior
- Splits out the concept of vm_mapper which maps vm_areas to vm_spaces,
  so that some kinds of VMA can be inherently single-space
- Implements VMA resizing so that userspace can grow allocations.
- Obsolete page_table_indices is removed

Also, the following bugs were fixed:

- kutil::map iterators on empty maps no longer break
- memory::page_count was doing page-align, not page-count

See: Github bug #242
See: [frobozz blog post](https://jsix.dev/posts/frobozz/)

Tags:
This commit is contained in:
2020-09-26 21:47:15 -07:00
parent 0e0975e5f6
commit 13aee1755e
19 changed files with 533 additions and 190 deletions

View File

@@ -60,6 +60,7 @@ modules:
- src/kernel/syscalls/thread.cpp
- src/kernel/syscalls/vm_area.cpp
- src/kernel/task.s
- src/kernel/vm_mapper.cpp
- src/kernel/vm_space.cpp
- src/kernel/crtn.s

View File

@@ -63,13 +63,23 @@ main(int argc, const char **argv)
uintptr_t base = 0xcc0000000;
j6_handle_t vma = j6_handle_invalid;
j6_status_t result = _syscall_vma_create_map(&vma, 0x100000, base);
j6_status_t result = _syscall_vma_create_map(&vma, 0x100000, base, 1);
if (result != j6_status_ok)
return result;
size_t size = 0x800000;
result = _syscall_vma_resize(vma, &size);
if (result != j6_status_ok)
return result;
if (size == 0x800000)
_syscall_system_log("main thread resized memory area");
uint64_t *vma_ptr = reinterpret_cast<uint64_t*>(base);
for (int i = 0; i < 4096; ++i)
vma_ptr[i] = uint64_t(i);
for (int i = 0; i < 300; ++i)
vma_ptr[i * 512] = uint64_t(i);
_syscall_system_log("main thread wrote to memory area");
result = _syscall_endpoint_create(&endp);
if (result != j6_status_ok)
@@ -84,7 +94,7 @@ main(int argc, const char **argv)
_syscall_system_log("main thread created sub thread");
char message[] = "MAIN THREAD SUCCESSFULLY CALLED SENDRECV IF THIS IS LOWERCASE";
size_t size = sizeof(message);
size = sizeof(message);
result = _syscall_endpoint_sendrecv(endp, &size, (void*)message);
if (result != j6_status_ok)
return result;

View File

@@ -17,7 +17,7 @@ namespace memory {
constexpr uintptr_t page_offset = 0xffffc00000000000ull;
/// Max number of pages for a kernel stack
constexpr unsigned kernel_stack_pages = 1;
constexpr unsigned kernel_stack_pages = 4;
/// Max number of pages for a kernel buffer
constexpr unsigned kernel_buffer_pages = 16;
@@ -62,6 +62,6 @@ namespace memory {
/// \arg bytes The number of bytes desired
/// \returns The number of pages needed to contain the desired bytes
inline size_t page_count(size_t bytes) {
return ((bytes - 1) & (frame_size - 1)) + 1;
return ((bytes - 1) >> 12) + 1;
}
} // namespace memory

View File

@@ -24,8 +24,9 @@ SYSCALL(0x2a, endpoint_send, j6_handle_t, size_t, void *)
SYSCALL(0x2b, endpoint_receive, j6_handle_t, size_t *, void *)
SYSCALL(0x2c, endpoint_sendrecv, j6_handle_t, size_t *, void *)
SYSCALL(0x30, vma_create, j6_handle_t *, size_t)
SYSCALL(0x31, vma_create_map, j6_handle_t *, size_t, uintptr_t)
SYSCALL(0x30, vma_create, j6_handle_t *, size_t, uint32_t)
SYSCALL(0x31, vma_create_map, j6_handle_t *, size_t, uintptr_t, uint32_t)
SYSCALL(0x32, vma_close, j6_handle_t)
SYSCALL(0x33, vma_map, j6_handle_t, uintptr_t)
SYSCALL(0x34, vma_unmap, j6_handle_t)
SYSCALL(0x34, vma_resize, j6_handle_t, size_t *)

View File

@@ -35,6 +35,9 @@ kutil::heap_allocator &g_kernel_heap = __g_kernel_heap_storage.value;
static kutil::no_construct<frame_allocator> __g_frame_allocator_storage;
frame_allocator &g_frame_allocator = __g_frame_allocator_storage.value;
static kutil::no_construct<vm_area_open> __g_kernel_heap_area_storage;
vm_area_open &g_kernel_heap_area = __g_kernel_heap_area_storage.value;
void * operator new(size_t size) { return g_kernel_heap.allocate(size); }
void * operator new [] (size_t size) { return g_kernel_heap.allocate(size); }
void operator delete (void *p) noexcept { return g_kernel_heap.free(p); }
@@ -104,7 +107,11 @@ memory_initialize_pre_ctors(args::header *kargs)
process *kp = process::create_kernel_process(kpml4);
vm_space &vm = kp->space();
vm.allow(memory::heap_start, memory::kernel_max_heap, true);
vm_area *heap = new (&g_kernel_heap_area)
vm_area_open(memory::kernel_max_heap, vm, vm_flags::write);
vm.add(memory::heap_start, heap);
}
void

View File

@@ -22,6 +22,7 @@ process::process() :
s_processes.append(this);
}
// The "kernel process"-only constructor
process::process(page_table *kpml4) :
kobject {kobject::type::process},
m_space {kpml4},

View File

@@ -1,4 +1,3 @@
#include "frame_allocator.h"
#include "kernel_memory.h"
#include "objects/vm_area.h"
#include "vm_space.h"
@@ -6,64 +5,55 @@
using memory::frame_size;
vm_area::vm_area(size_t size, vm_flags flags) :
m_size(size),
m_flags(flags),
kobject(kobject::type::vma)
m_size {size},
m_flags {flags},
kobject {kobject::type::vma}
{
}
vm_area::~vm_area()
{
for (auto &it : m_spaces)
remove_from(it.key);
frame_allocator &fa = frame_allocator::get();
for (auto &m : m_mappings)
fa.free(m.phys, m.count);
}
vm_area::~vm_area() {}
size_t
vm_area::resize(size_t size)
{
if (mapper().can_resize(size))
m_size = size;
return m_size;
}
void
vm_area::add_to(vm_space *space, uintptr_t base)
vm_area::commit(uintptr_t phys, uintptr_t offset, size_t count)
{
kassert(space, "null vm_space passed to vm_area::add_to");
// Multiple copies in the same space not yet supported
uintptr_t *prev = m_spaces.find(space);
if (prev) return;
if (m_spaces.count()) {
// Just get the first address space in the map
auto it = m_spaces.begin();
vm_space *source = it->key;
uintptr_t from = it->val;
size_t pages = memory::page_count(m_size);
space->copy_from(*source, from, base, pages);
}
m_spaces.insert(space, base);
space->add(base, this);
mapper().map(offset, count, phys);
}
void
vm_area::remove_from(vm_space *space)
vm_area::uncommit(uintptr_t offset, size_t count)
{
mapper().unmap(offset, count);
}
void
vm_area::on_no_handles()
{
kobject::on_no_handles();
delete this;
}
vm_area_shared::vm_area_shared(size_t size, vm_flags flags) :
m_mapper {*this},
vm_area {size, flags}
{
}
vm_area_shared::~vm_area_shared()
{
size_t pages = memory::page_count(m_size);
uintptr_t *base = m_spaces.find(space);
if (space && base) {
space->clear(*base, pages);
m_spaces.erase(space);
}
space->remove(this);
}
size_t
vm_area::overlaps(uintptr_t offset, size_t pages, size_t *count)
vm_area_shared::overlaps(uintptr_t offset, size_t pages, size_t *count)
{
size_t first = 0;
size_t n = 0;
@@ -84,14 +74,16 @@ vm_area::overlaps(uintptr_t offset, size_t pages, size_t *count)
return first;
}
bool
vm_area::commit(uintptr_t phys, uintptr_t offset, size_t count)
void
vm_area_shared::commit(uintptr_t phys, uintptr_t offset, size_t count)
{
size_t n = 0;
size_t o = overlaps(offset, count, &n);
// Mapping overlaps not allowed
if (n) return false;
if (n) return;
m_mapper.map(offset, count, phys);
o = m_mappings.sorted_insert({
.offset = offset,
@@ -99,7 +91,6 @@ vm_area::commit(uintptr_t phys, uintptr_t offset, size_t count)
.phys = phys});
n = 1;
map(offset, count, phys);
// Try to expand to abutting similar areas
if (o > 0 &&
@@ -134,15 +125,17 @@ vm_area::commit(uintptr_t phys, uintptr_t offset, size_t count)
if (n > 1)
m_mappings.remove_at(o+1, n-1);
return true;
return;
}
bool
vm_area::uncommit(uintptr_t offset, size_t count)
void
vm_area_shared::uncommit(uintptr_t offset, size_t count)
{
size_t n = 0;
size_t o = overlaps(offset, count, &n);
if (!n) return true;
if (!n) return;
m_mapper.unmap(offset, count);
mapping *first = &m_mappings[o];
mapping *last = &m_mappings[o+n-1];
@@ -169,7 +162,7 @@ vm_area::uncommit(uintptr_t offset, size_t count)
size_t remove_pages = first->count;
first->count = leading / frame_size;
remove_pages -= first->count;
unmap(first->end(), remove_pages);
m_mapper.unmap(first->end(), remove_pages);
}
if (trailing) {
@@ -178,7 +171,7 @@ vm_area::uncommit(uintptr_t offset, size_t count)
last->offset = end;
last->count = trailing / frame_size;
remove_pages -= last->count;
unmap(remove_off, remove_pages);
m_mapper.unmap(remove_off, remove_pages);
last->phys += remove_pages * frame_size;
}
@@ -189,39 +182,32 @@ vm_area::uncommit(uintptr_t offset, size_t count)
if (offset <= m.offset && end >= m.end()) {
if (!delete_count) delete_start = i;
++delete_count;
unmap(m.offset, m.count);
m_mapper.unmap(m.offset, m.count);
}
}
if (delete_count)
m_mappings.remove_at(delete_start, delete_count);
return true;
return;
}
vm_area_open::vm_area_open(size_t size, vm_space &space, vm_flags flags) :
m_mapper(*this, space),
vm_area(size, flags)
{
}
void
vm_area::map(uintptr_t offset, size_t count, uintptr_t phys)
vm_area_open::commit(uintptr_t phys, uintptr_t offset, size_t count)
{
for (auto &it : m_spaces) {
uintptr_t addr = it.val + offset;
vm_space *space = it.key;
space->page_in(addr, phys, count);
}
m_mapper.map(offset, count, phys);
}
void
vm_area::unmap(uintptr_t offset, size_t count)
vm_area_open::uncommit(uintptr_t offset, size_t count)
{
for (auto &it : m_spaces) {
uintptr_t addr = it.val + offset;
vm_space *space = it.key;
space->clear(addr, count);
}
m_mapper.unmap(offset, count);
}
void
vm_area::on_no_handles()
{
kobject::on_no_handles();
delete this;
}

View File

@@ -2,12 +2,16 @@
/// \file vm_area.h
/// Definition of VMA objects and related functions
#include <stddef.h>
#include <stdint.h>
#include "j6/signals.h"
#include "kutil/enum_bitfields.h"
#include "kutil/map.h"
#include "kutil/vector.h"
#include "kernel_memory.h"
#include "objects/kobject.h"
#include "vm_mapper.h"
class vm_space;
@@ -15,14 +19,17 @@ enum class vm_flags : uint32_t
{
none = 0x00000000,
zero = 0x00000001,
contiguous = 0x00000002,
write = 0x00000001,
exec = 0x00000002,
zero = 0x00000010,
contiguous = 0x00000020,
large_pages = 0x00000100,
huge_pages = 0x00000200
};
huge_pages = 0x00000200,
IS_BITFIELD(vm_flags);
user_mask = 0x0000ffff ///< flags allowed via syscall
};
/// Virtual memory areas allow control over memory allocation
@@ -30,16 +37,20 @@ class vm_area :
public kobject
{
public:
static constexpr kobject::type type = kobject::type::vma;
/// 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();
static constexpr kobject::type type = kobject::type::vma;
/// Get the current virtual size of the memory area
size_t size() const { return m_size; }
inline size_t size() const { return m_size; }
/// Get the flags set for this area
inline vm_flags flags() const { return m_flags; }
/// Change the virtual size of the memory area. This may cause
/// deallocation if the new size is smaller than the current size.
@@ -49,37 +60,52 @@ public:
/// \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 The base address this area will be mapped to
void add_to(vm_space *s, uintptr_t base);
/// Get the mapper object that maps this area to address spaces
virtual vm_mapper & mapper() = 0;
virtual const vm_mapper & mapper() const = 0;
/// Remove this virtual area from a process' virtual address space.
/// \arg s The target address space
void remove_from(vm_space *s);
/// Check whether allocation at the given offset is allowed
virtual bool allowed(uintptr_t offset) const { return true; }
/// 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);
virtual void 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);
/// Get the flags set for this area
vm_flags flags() const { return m_flags; }
virtual void uncommit(uintptr_t offset, size_t count);
protected:
virtual void on_no_handles() override;
size_t m_size;
vm_flags m_flags;
};
/// The standard, sharable, user-controllable VMA type
class vm_area_shared :
public vm_area
{
public:
/// Constructor.
/// \arg size Initial virtual size of the memory area
/// \arg flags Flags for this memory area
vm_area_shared(size_t size, vm_flags flags = vm_flags::none);
virtual ~vm_area_shared();
virtual vm_mapper & mapper() override { return m_mapper; }
virtual const vm_mapper & mapper() const override { return m_mapper; }
virtual void commit(uintptr_t phys, uintptr_t offset, size_t count) override;
virtual void uncommit(uintptr_t offset, size_t count) override;
private:
vm_mapper_multi m_mapper;
struct mapping {
uintptr_t offset;
size_t count;
@@ -95,11 +121,72 @@ private:
size_t overlaps(uintptr_t offset, size_t pages, size_t *count);
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_spaces;
kutil::vector<mapping> m_mappings;
};
/// Area split into standard-sized segments
class vm_area_buffers :
public vm_area
{
public:
/// Constructor.
/// \arg size Initial virtual size of the memory area
/// \arg space The address space this area belongs to
/// \arg flags Flags for this memory area
vm_area_buffers(size_t size, vm_space &space, vm_flags flags);
virtual vm_mapper & mapper() override { return m_mapper; }
virtual const vm_mapper & mapper() const override { return m_mapper; }
virtual bool allowed(uintptr_t offset) const override;
virtual void commit(uintptr_t phys, uintptr_t offset, size_t count) override;
virtual void uncommit(uintptr_t offset, size_t count) override;
private:
vm_mapper_single m_mapper;
};
/// Area backed by an external source (like a loaded program)
class vm_area_backed :
public vm_area
{
public:
/// Constructor.
/// \arg size Initial virtual size of the memory area
/// \arg flags Flags for this memory area
vm_area_backed(size_t size, vm_flags flags);
virtual vm_mapper & mapper() override { return m_mapper; }
virtual const vm_mapper & mapper() const override { return m_mapper; }
virtual void commit(uintptr_t phys, uintptr_t offset, size_t count) override;
virtual void uncommit(uintptr_t offset, size_t count) override;
private:
vm_mapper_multi m_mapper;
};
/// Area that allows open allocation (eg, kernel heap)
class vm_area_open :
public vm_area
{
public:
/// Constructor.
/// \arg size Initial virtual size of the memory area
/// \arg space The address space this area belongs to
/// \arg flags Flags for this memory area
vm_area_open(size_t size, vm_space &space, vm_flags flags);
virtual vm_mapper & mapper() override { return m_mapper; }
virtual const vm_mapper & mapper() const override { return m_mapper; }
virtual void commit(uintptr_t phys, uintptr_t offset, size_t count) override;
virtual void uncommit(uintptr_t offset, size_t count) override;
private:
vm_mapper_single m_mapper;
};
IS_BITFIELD(vm_flags);

View File

@@ -165,6 +165,7 @@ page_table::iterator::ensure_table(level l)
m_table[unsigned(l)] = table;
parent = (phys & ~0xfffull) | flags;
__atomic_thread_fence(__ATOMIC_SEQ_CST);
}
page_table *
@@ -296,26 +297,3 @@ page_table::dump(page_table::level lvl, bool recurse)
}
}
}
page_table_indices::page_table_indices(uint64_t v) :
index{
(v >> 39) & 0x1ff,
(v >> 30) & 0x1ff,
(v >> 21) & 0x1ff,
(v >> 12) & 0x1ff }
{}
uintptr_t
page_table_indices::addr() const
{
return
(index[0] << 39) |
(index[1] << 30) |
(index[2] << 21) |
(index[3] << 12);
}
bool operator==(const page_table_indices &l, const page_table_indices &r)
{
return l[0] == r[0] && l[1] == r[1] && l[2] == r[2] && l[3] == r[3];
}

View File

@@ -177,25 +177,6 @@ struct page_table
};
/// Helper struct for computing page table indices of a given address.
struct page_table_indices
{
page_table_indices(uint64_t v = 0);
uintptr_t addr() const;
inline operator uintptr_t() const { return addr(); }
/// Get the index for a given level of page table.
uint64_t & operator[](int i) { return index[i]; }
uint64_t operator[](int i) const { return index[i]; }
uint64_t & operator[](page_table::level i) { return index[static_cast<unsigned>(i)]; }
uint64_t operator[](page_table::level i) const { return index[static_cast<unsigned>(i)]; }
uint64_t index[4]; ///< Indices for each level of tables.
};
bool operator==(const page_table_indices &l, const page_table_indices &r);
inline page_table::level operator+(page_table::level a, unsigned b) {
return static_cast<page_table::level>(static_cast<unsigned>(a) + b);
}

View File

@@ -22,6 +22,11 @@ syscall_handler_prelude:
push rbp
mov rbp, rsp
; account for the hole in the sysv abi
; argument list since SYSCALL uses rcx
mov rcx, r8
mov r8, r9
push rbx
push r11
push r12

View File

@@ -11,17 +11,19 @@
namespace syscalls {
j6_status_t
vma_create(j6_handle_t *handle, size_t size)
vma_create(j6_handle_t *handle, size_t size, uint32_t flags)
{
construct_handle<vm_area>(handle, size);
vm_flags f = vm_flags::user_mask & flags;
construct_handle<vm_area_shared>(handle, size, f);
return j6_status_ok;
}
j6_status_t
vma_create_map(j6_handle_t *handle, size_t size, uintptr_t base)
vma_create_map(j6_handle_t *handle, size_t size, uintptr_t base, uint32_t flags)
{
vm_area *a = construct_handle<vm_area>(handle, size);
a->add_to(&process::current().space(), base);
vm_flags f = vm_flags::user_mask & flags;
vm_area *a = construct_handle<vm_area_shared>(handle, size, f);
process::current().space().add(base, a);
return j6_status_ok;
}
@@ -31,7 +33,7 @@ vma_map(j6_handle_t handle, uintptr_t base)
vm_area *a = get_handle<vm_area>(handle);
if (!a) return j6_err_invalid_arg;
a->add_to(&process::current().space(), base);
process::current().space().add(base, a);
return j6_status_ok;
}
@@ -41,7 +43,7 @@ vma_unmap(j6_handle_t handle)
vm_area *a = get_handle<vm_area>(handle);
if (!a) return j6_err_invalid_arg;
a->remove_from(&process::current().space());
process::current().space().remove(a);
return j6_status_ok;
}
@@ -56,6 +58,19 @@ vma_close(j6_handle_t handle)
return j6_status_ok;
}
j6_status_t
vma_resize(j6_handle_t handle, size_t *size)
{
if (!size)
return j6_err_invalid_arg;
vm_area *a = get_handle<vm_area>(handle);
if (!a) return j6_err_invalid_arg;
*size = a->resize(*size);
return j6_status_ok;
}
} // namespace syscalls

98
src/kernel/vm_mapper.cpp Normal file
View File

@@ -0,0 +1,98 @@
#include "objects/vm_area.h"
#include "vm_mapper.h"
#include "vm_space.h"
vm_mapper_single::vm_mapper_single(vm_area &area, vm_space &space) :
m_area(area), m_space(space)
{}
vm_mapper_single::~vm_mapper_single()
{
m_space.clear(m_area, 0, memory::page_count(m_area.size()), true);
}
bool
vm_mapper_single::can_resize(size_t size) const
{
return m_space.can_resize(m_area, size);
}
void
vm_mapper_single::map(uintptr_t offset, size_t count, uintptr_t phys)
{
m_space.page_in(m_area, offset, phys, count);
}
void
vm_mapper_single::unmap(uintptr_t offset, size_t count)
{
m_space.clear(m_area, offset, count, true);
}
vm_mapper_multi::vm_mapper_multi(vm_area &area) :
m_area(area)
{
}
vm_mapper_multi::~vm_mapper_multi()
{
if (!m_spaces.count())
return;
size_t count = memory::page_count(m_area.size());
for (int i = 1; i < m_spaces.count(); ++i)
m_spaces[i]->clear(m_area, 0, count);
m_spaces[0]->clear(m_area, 0, count, true);
}
bool
vm_mapper_multi::can_resize(size_t size) const
{
for (auto &it : m_spaces)
if (!it->can_resize(m_area, size))
return false;
return true;
}
void
vm_mapper_multi::map(uintptr_t offset, size_t count, uintptr_t phys)
{
for (auto &it : m_spaces)
it->page_in(m_area, offset, phys, count);
}
void
vm_mapper_multi::unmap(uintptr_t offset, size_t count)
{
for (auto &it : m_spaces)
it->clear(m_area, offset, count);
}
void
vm_mapper_multi::add(vm_space *space)
{
if (m_spaces.count()) {
vm_space *source = m_spaces[0];
space->copy_from(*source, m_area);
}
m_spaces.append(space);
}
void
vm_mapper_multi::remove(vm_space *space)
{
size_t count = memory::page_count(m_area.size());
for (int i = 0; i < m_spaces.count(); ++i) {
if (m_spaces[i] == space) {
m_spaces.remove_swap_at(i);
space->clear(m_area, 0, count, m_spaces.count() == 0);
}
}
}

79
src/kernel/vm_mapper.h Normal file
View File

@@ -0,0 +1,79 @@
#pragma once
/// \file vm_mapper.h
/// VMA to address space mapping interface and implementing objects
#include <stdint.h>
#include "kutil/vector.h"
class vm_area;
class vm_space;
/// An interface to map vm_areas to one or more vm_spaces
class vm_mapper
{
public:
virtual ~vm_mapper() {}
/// Check whether the owning VMA can be resized to the given size.
/// \arg size The desired size
/// \returns True if resize is possible
virtual bool can_resize(size_t size) const = 0;
/// Map the given physical pages into the owning VMA at the given offset
/// \arg offset Offset into the VMA of the requested virtual address
/// \arg count Number of contiguous physical pages to map
/// \arg phys The starting physical address of the pages
virtual void map(uintptr_t offset, size_t count, uintptr_t phys) = 0;
/// Unmap the pages corresponding to the given offset from the owning VMA
/// \arg offset Offset into the VMA of the requested virtual address
/// \arg count Number of pages to unmap
virtual void unmap(uintptr_t offset, size_t count) = 0;
/// Add the given address space to the list of spaces the owning VMA is
/// mapped to, if applicable.
virtual void add(vm_space *space) {}
/// Remove the given address space from the list of spaces the owning VMA
/// is mapped to, if applicable.
virtual void remove(vm_space *space) {}
};
/// A vm_mapper that maps a VMA to a single vm_space
class vm_mapper_single :
public vm_mapper
{
public:
vm_mapper_single(vm_area &area, vm_space &space);
virtual ~vm_mapper_single();
virtual bool can_resize(size_t size) const override;
virtual void map(uintptr_t offset, size_t count, uintptr_t phys) override;
virtual void unmap(uintptr_t offset, size_t count) override;
private:
vm_area &m_area;
vm_space &m_space;
};
/// A vm_mapper that maps a VMA to multiple vm_spaces
class vm_mapper_multi :
public vm_mapper
{
public:
vm_mapper_multi(vm_area &area);
virtual ~vm_mapper_multi();
virtual bool can_resize(size_t size) const override;
virtual void map(uintptr_t offset, size_t count, uintptr_t phys) override;
virtual void unmap(uintptr_t offset, size_t count) override;
virtual void add(vm_space *space) override;
virtual void remove(vm_space *space) override;
private:
vm_area &m_area;
kutil::vector<vm_space*> m_spaces;
};

View File

@@ -1,10 +1,14 @@
#include "frame_allocator.h"
#include "kernel_memory.h"
#include "log.h"
#include "objects/process.h"
#include "objects/thread.h"
#include "objects/vm_area.h"
#include "vm_space.h"
// The initial memory for the array of areas for the kernel space
static uint64_t kernel_areas[16];
int
vm_space::area::compare(const vm_space::area &o) const
{
@@ -20,7 +24,12 @@ vm_space::area::operator==(const vm_space::area &o) const
}
vm_space::vm_space(page_table *p) : m_kernel(true), m_pml4(p) {}
// Kernel address space contsructor
vm_space::vm_space(page_table *p) :
m_kernel {true},
m_pml4 {p},
m_areas {reinterpret_cast<vm_space::area*>(kernel_areas), 0, 8}
{}
vm_space::vm_space() :
m_kernel(false)
@@ -36,7 +45,7 @@ vm_space::vm_space() :
vm_space::~vm_space()
{
for (auto &a : m_areas)
a.area->remove_from(this);
a.area->mapper().remove(this);
kassert(!is_kernel(), "Kernel vm_space destructor!");
@@ -61,6 +70,7 @@ vm_space::add(uintptr_t base, vm_area *area)
{
//TODO: check for collisions
m_areas.sorted_insert({base, area});
area->mapper().add(this);
return true;
}
@@ -70,12 +80,36 @@ vm_space::remove(vm_area *area)
for (auto &a : m_areas) {
if (a.area == area) {
m_areas.remove(a);
area->mapper().remove(this);
return true;
}
}
return false;
}
bool
vm_space::can_resize(const vm_area &vma, size_t size) const
{
uintptr_t base = 0;
unsigned n = m_areas.count();
for (unsigned i = 0; i < n - 1; ++i) {
const area &prev = m_areas[i - 1];
if (prev.area != &vma)
continue;
base = prev.base;
const area &next = m_areas[i];
if (prev.base + size > next.base)
return false;
}
uintptr_t end = base + size;
uintptr_t space_end = is_kernel() ?
uint64_t(-1) : 0x7fffffffffff;
return end <= space_end;
}
vm_area *
vm_space::get(uintptr_t addr, uintptr_t *base)
{
@@ -89,11 +123,28 @@ vm_space::get(uintptr_t addr, uintptr_t *base)
return nullptr;
}
void
vm_space::copy_from(const vm_space &source, uintptr_t from, uintptr_t to, size_t count)
bool
vm_space::find_vma(const vm_area &vma, uintptr_t &base) const
{
page_table::iterator sit {from, source.m_pml4};
for (auto &a : m_areas) {
if (a.area != &vma) continue;
base = a.base;
return true;
}
return false;
}
void
vm_space::copy_from(const vm_space &source, const vm_area &vma)
{
uintptr_t to = 0;
uintptr_t from = 0;
if (!find_vma(vma, from) || !source.find_vma(vma, from))
return;
size_t count = memory::page_count(vma.size());
page_table::iterator dit {to, m_pml4};
page_table::iterator sit {from, source.m_pml4};
while (count--) {
uint64_t &e = dit.entry(page_table::level::pt);
@@ -105,23 +156,39 @@ vm_space::copy_from(const vm_space &source, uintptr_t from, uintptr_t to, size_t
}
void
vm_space::page_in(uintptr_t virt, uintptr_t phys, size_t count)
vm_space::page_in(const vm_area &vma, uintptr_t offset, uintptr_t phys, size_t count)
{
using memory::frame_size;
uintptr_t base = 0;
if (!find_vma(vma, base))
return;
uintptr_t virt = base + offset;
page_table::flag flags =
page_table::flag::present |
(m_kernel ? page_table::flag::none : page_table::flag::user) |
((vma.flags() && vm_flags::write) ? page_table::flag::write : page_table::flag::none);
page_table::iterator it {virt, m_pml4};
for (size_t i = 0; i < count; ++i) {
uint64_t &e = it.entry(page_table::level::pt);
bool allowed = (e & page_table::flag::allowed);
e = (phys + i * memory::frame_size) |
(allowed ? page_table::flag::allowed : page_table::flag::none);
it.entry(page_table::level::pt) =
(phys + i * frame_size) | flags;
++it;
}
}
void
vm_space::clear(uintptr_t addr, size_t count)
vm_space::clear(const vm_area &vma, uintptr_t offset, size_t count, bool free)
{
using memory::frame_size;
uintptr_t base = 0;
if (!find_vma(vma, base))
return;
uintptr_t addr = base + offset;
uintptr_t free_start = 0;
size_t free_count = 0;
@@ -137,7 +204,7 @@ vm_space::clear(uintptr_t addr, size_t count)
if (free_count && phys == free_start + (free_count * frame_size)) {
++free_count;
} else {
if (free_count)
if (free && free_count)
fa.free(free_start, free_count);
free_start = phys;
free_count = 1;
@@ -149,7 +216,7 @@ vm_space::clear(uintptr_t addr, size_t count)
++it;
}
if (free_count)
if (free && free_count)
fa.free(free_start, free_count);
}
@@ -209,7 +276,7 @@ vm_space::handle_fault(uintptr_t addr, fault_type fault)
uintptr_t base = 0;
vm_area *area = get(addr, &base);
if (!area && !it.allowed())
if ((!area || !area->allowed(page)) && !it.allowed())
return false;
uintptr_t phys = 0;
@@ -226,12 +293,13 @@ vm_space::handle_fault(uintptr_t addr, fault_type fault)
? page_table::flag::global
: page_table::flag::user);
it.entry(page_table::level::pt) = phys | flags;
if (area) {
uintptr_t offset = page - base;
area->commit(phys, offset, 1);
return true;
}
it.entry(page_table::level::pt) = phys | flags;
return true;
}

View File

@@ -5,8 +5,8 @@
#include <stdint.h>
#include "kutil/enum_bitfields.h"
#include "kutil/vector.h"
#include "page_table.h"
struct page_table;
class process;
struct TCB;
class vm_area;
@@ -47,23 +47,19 @@ public:
/// Get the kernel virtual memory space
static vm_space & kernel_space();
/// Copy a range of mappings from the given address space
/// \arg source The address space that already contains the mappings
/// \arg from The starting virtual address in the source address space
/// \arg to The starting virtual address in this address space
/// \arg count The number of page entries to copy
void copy_from(const vm_space &source, uintptr_t from, uintptr_t to, size_t count);
/// Map virtual addressses to the given physical pages
/// \arg virt The starting virutal address
/// \arg area The VMA this mapping applies to
/// \arg offset Offset of the starting virutal address from the VMA base
/// \arg phys The starting physical address
/// \arg count The number of contiugous physical pages to map
void page_in(uintptr_t virt, uintptr_t phys, size_t count);
void page_in(const vm_area &area, uintptr_t offset, uintptr_t phys, size_t count);
/// Clear mappings from the given region
/// \arg start The starting virutal address to clear
/// \arg area The VMA these mappings applies to
/// \arg offset Offset of the starting virutal address from the VMA base
/// \arg count The number of pages worth of mappings to clear
void clear(uintptr_t start, size_t count);
/// \arg free If true, free the pages back to the system
void clear(const vm_area &vma, uintptr_t start, size_t count, bool free = false);
/// Mark whether allocation is allowed or not in a range of
/// virtual memory.
@@ -106,6 +102,18 @@ public:
static size_t copy(vm_space &source, vm_space &dest, void *from, void *to, size_t length);
private:
friend class vm_mapper_single;
friend class vm_mapper_multi;
/// Find a given VMA in this address space
bool find_vma(const vm_area &vma, uintptr_t &base) const;
/// Check if a VMA can be resized
bool can_resize(const vm_area &vma, size_t size) const;
/// Copy a range of mappings from the given address space
void copy_from(const vm_space &source, const vm_area &vma);
bool m_kernel;
page_table *m_pml4;

View File

@@ -105,16 +105,19 @@ public:
}
iterator begin() {
if (!m_count) return iterator {0};
iterator it {m_nodes - 1, m_nodes + m_capacity};
return ++it;
}
const iterator begin() const {
if (!m_count) return iterator {0};
iterator it {m_nodes - 1, m_nodes + m_capacity};
return ++it;
}
const iterator end() const {
if (!m_count) return iterator {0};
return iterator(m_nodes + m_capacity);
}

View File

@@ -45,7 +45,7 @@ public:
}
/// Move constructor. Takes ownership of the other's array.
vector(vector&& other) :
vector(vector &&other) :
m_size(other.m_size),
m_capacity(other.m_capacity),
m_elements(other.m_elements)
@@ -55,6 +55,15 @@ public:
other.m_elements = nullptr;
}
/// Static array constructor. Starts the vector off with the given
/// static storage.
vector(T *data, size_t size, size_t capacity) :
m_size(size),
m_capacity(capacity),
m_elements(&data[0])
{
}
/// Destructor. Destroys any remaining items in the array.
~vector()
{

View File

@@ -4,7 +4,11 @@
push rbp
mov rbp, rsp
; address of args should already be in rdi, etc
; args should already be in rdi, etc, but rcx will
; get stomped, so shift args out one spot from rcx
mov r9, r8
mov r8, rcx
mov rax, %2
syscall
; result is now already in rax, so just return
@@ -17,5 +21,7 @@
%define SYSCALL(n, name, a) SYSCALL name, n
%define SYSCALL(n, name, a, b) SYSCALL name, n
%define SYSCALL(n, name, a, b, c) SYSCALL name, n
%define SYSCALL(n, name, a, b, c, d) SYSCALL name, n
%define SYSCALL(n, name, a, b, c, d, e) SYSCALL name, n
%include "syscalls.inc"