Extended and documented the memory management API.

Physical paging have been extended with Page::Insert() and Page::GetStats()
which allows the physical paging system to add new pages to the physical page
allocator, and still keep the "free/used pages" count accurate, and providing
this information to the kernel (and user-space at some point).

The virtual memory API has been extended with RemapKernel(), RemapUser(),
MapRangeKernel(), UnmapRangeKernel(), MapRangeUser(), and UnmapRangeUser().
This huge number of related functions have been created in the hope that it
hides the internal complexity of portable virtual memory management and avoid
bugs. It is crucial that the correct group of functions are used when solving
a problem and that they are not mixed in a manner not documented.

I probably overdocumented the code - hopefully it should help avoiding making
stupid or bothersome code.

Another problem is that code calling Page::Get() often should call something
like Page::AlwaysGetPageEvenIfYouHaveToSwap(). I'd be swell to have a function
that always gets a page under heavily-swapping conditions. Possibly Page::Get()
could become that?
This commit is contained in:
Jonas 'Sortie' Termansen 2011-08-12 02:58:55 +02:00
parent 4c1cb806ba
commit d392045559
1 changed files with 296 additions and 10 deletions

View File

@ -25,37 +25,323 @@
#ifndef SORTIX_MEMORYMANAGEMENT_H
#define SORTIX_MEMORYMANAGEMENT_H
// Forward declarations.
typedef struct multiboot_info multiboot_info_t;
namespace Sortix
{
// This is the physical page allocator API. It splits the physical memory
// of the local machine into chunks known as pages. Each page is usually
// 4096 bytes (depends on your CPU). Pages are page-aligned, meaning they
// all are located on a multiple of the page size (such as 4096 byte).
//
// A long list of memory addresses is used to store and retrieve page
// addresses from. To allocate a physical page of memory, simply call the
// Page::Get() function. When you are done using it, you can free it using
// the Page::Put() function, which makes the page available for other uses.
//
// To use a physical page, the CPU must be in physical mode. Since it is
// undesirable to be in physical mode, using the physical page requires
// using the virtual memory API (see below).
//
// This API completely bypasses the memory swapping system.
//
// If you just want memory for use by the kernel, allocate it using 'new'.
namespace Page
{
#ifdef MULTIBOOT_HEADER
void Init(multiboot_info_t* BootInfo);
#endif
addr_t Get();
void Put(addr_t Page);
// Initializes the paging system. Accepts a multiboot structure
// containing the layout of the physical memory in this machine.
void Init(multiboot_info_t* bootinfo);
inline addr_t AlignDown(addr_t page) { return page & ~(0xFFFUL); }
inline addr_t AlignUp(addr_t page) { return AlignDown(page + 0xFFFUL); }
// Allocates a physical page and returns its physical address, or
// returns 0 if no page currently is available in the system.
addr_t Get();
// Deallocates a physical page allocated using Get(), which lets the
// the system reuse the page for other purposes. Page must have been
// allocated using Get().
void Put(addr_t page);
// Inserts a physical page into the paging system. This page must not
// have been allocated using Get() and must have been allocated safely
// through other means (such as information provided by the bootloader).
void Insert(addr_t page);
// Rounds a memory address down to nearest page.
inline addr_t AlignDown(addr_t page) { return page & ~(0xFFFUL); }
// Rounds a memory address up to nearest page.
inline addr_t AlignUp(addr_t page) { return AlignDown(page + 0xFFFUL); }
// Retrieves statistics about the current page usage in the system, how
// big pages are, now many are free, and how many are used. Each
// parameter must be a legal pointer to the locations wherein the stats
// will be stored. This function always succeeds.
void GetStats(size_t* pagesize, size_t* numfree, size_t* numused);
}
// This the virtual memory API. Virtual Memory is a clever way to make the
// RAM just as you want it. In effect, it it makes the RAM completely empty
// (there is no RAM). You can then add memory where you desire. You can take
// a physical page and put it in several places, and you can even add
// permissions to it (read-only, read-write, kernel-only). Naturally, the
// amount of physical pages is a limit (out of memory).
//
// There can exist several virtual address spaces, and it is possible for
// them to share physical pages. Each process in the system has its own
// virtual address space, but the kernel is always mapped in each address
// space. While the kernel can access all of the virtual address space,
// user-space programs can only access what they are allowed, cannot access
// the kernel's memory and cannot access each other's address spaces. This
// prevents programs from tampering with each other and from bringing the
// whole system down.
//
// Sortix has several conventions for the layout of the virtual address
// space. The kernel uses the top of the address space, and user-space is
// generally allowed to use the bottom and the middle for stuff such as
// program code, variables, the stack, the heap, and other stuff.
//
// To access physical memory, you must allocate a physical page of memory
// and map it to a virtual address. You can then modify the memory through
// a pointer to that address.
//
// You should select the correct set of functions when writing new code.
// Using the functions incorrectly, using the wrong functions, or mixing
// incompatible functions can lead to gruesome bugs.
//
// For conveniece, the functions have been grouped. Combining (un)mapping
// functions from groups is bad style and is possibly buggy. Assertions may
// be present to detect bad combination.
//
// If you modify kernel virtual pages, then the effects will be share across
// all virtual address spaces.
//
// If you modify user-space virtual pages, then the effects will be limited
// to the current process and its personal virtual address space.
//
// If you just want memory for use by the kernel, allocate it using 'new'.
namespace VirtualMemory
{
// Initializes the virtual memory system. This bootstraps the kernel
// paging system (if needed) such that the initial kernel's virtual
// address space is created and the current process's page structures
// are mapped to conventional locations.
// This system depends on the physical page allocator being initialized.
void Init();
void Flush();
// Creates a new, fresh address space only containing the kernel memory
// regions, and a fresh user-space frontier of nothingness. The current
// address space remains active and is not modified (besides some kernel
// pages used for this purpose). Returns the physical address of the new
// top level paging structure, which is an opaque identifier as it is
// not identity mapped. Returns 0 if insufficient memory was available.
addr_t CreateAddressSpace();
// Switches the current address space to the virtual address space
// 'addrspace', which must be the result of CreateAddressSpace() or
// another function that creates or copies address spaces.
addr_t SwitchAddressSpace(addr_t addrspace);
// =====================================================================
// Function Group 1.
// Mapping a single kernel page.
//
// These functions allow you to map a single physical page for use by
// the kernel.
//
// Usage:
//
// addr_t physicalpage = Page::Get();
// if ( physicalpage == 0 ) { /* handle error */ }
//
// const addr_t mapto = 0xF00;
// VirtualMemory::MapKernel(mapto, physicalpage);
//
// /* access the memory */
// char* mem = (char*) mapto;
// mem[0] = 'K';
//
// ...
//
// addr_t physpage = VirtualMemory::UnmapKernel(mapto);
//
// /* when physpage is no longer referred, free it */
// Page::Put(physpage);
// Maps the physical page 'physical' to the virtual address 'where',
// with read and write flags set, but only accessible to the kernel.
// 'where' must be a virtual address in the range available to the
// kernel, and must not currently be used. Given legal input, this
// function will always succeed - illegal input is a gruesome bug.
// 'where' must be page aligned. The effect of this function will be
// shared in all addresses spaces - it is a global change.
// You are allowed to map the same physical page multiple times, even
// with Group 2. functions, just make sure to call the proper Unmap
// functions for each virtual address you map it to, and don't free the
// physical page until it is no longer referred to.
// The virtual page 'where' will point to 'physical' instantly after
// this function returns.
void MapKernel(addr_t where, addr_t physical);
bool MapUser(addr_t where, addr_t physical);
// This function is equal to unmapping 'where', then mapping 'where' to
// 'newphysical' and returns previous physical page 'where' pointed to.
// The same requirements for MapKernel and UnmapKernel applies.
addr_t RemapKernel(addr_t where, addr_t newphysical);
// Unmaps the virtual address 'where' such that it no longer points to
// a valid physical address. Accessing the page at the virtual address
// 'where' will result in an access violation (panicing/crashing the
// kernel). 'where' must be a virtual address already mapped in the
// kernel virtual memory ranges. 'where' must be page aligned.
// 'where' must already be a legal kernel virtual page.
// Returns the address of the physical memory page 'where' points to
// before being cleared. Before returning the physical page to the page
// allocator, make sure that it is not used by other virtual pages.
addr_t UnmapKernel(addr_t where);
// =====================================================================
// Function Group 2.
// Mapping a single user-space page.
//
// These functions allow you to map a single physical page for use by
// user-space programs.
//
// Usage:
//
// addr_t physicalpage = Page::Get();
// if ( physicalpage == 0 ) { /* handle error */ }
//
// const addr_t mapto = 0xF00;
// if ( !VirtualMemory::MapUser(mapto, physicalpage) )
// {
// Page::Put(physicalpage);
// /* handle error */
// }
//
// /* let user-space use memory safely */
//
// addr_t physpage = VirtualMemory::UnmapUser(mapto);
//
// /* when physpage is no longer referred, free it */
// Page::Put(physpage);
// Maps the physical page 'physical' to the virtual address 'where',
// with read and write flags set, accessible to both kernel and user-
// space. 'where' must be a virtual address not in the kernel ranges,
// that is, available to userspace. 'where' must be page aligned and
// not currently used. Returns false if insufficient memory is available
// and returns with the address space left unaltered. Illegal input is
// also a gruesome bug. This function only changes the address space of
// the current process.
// You are allowed to map the same physical page multiple times, even
// with Group 1. functions, just make sure to call the proper Unmap
// functions for each virtual address you map it to, and don't free the
// physical page until it is no longer referred to.
// The virtual page 'where' will point to 'physical' instantly after
// this function returns.
bool MapUser(addr_t where, addr_t physical);
// This function is equal to unmapping 'where', then mapping 'where' to
// 'newphysical' and returns previous physical page 'where' pointed to.
// The same requirements for MapUser and UnmapUser applies.
addr_t RemapUser(addr_t where, addr_t newphysical);
// Unmaps the virtual address 'where' such that it no longer points to
// a valid physical address. Accessing the page at the virtual address
// 'where' will result in an access violation (panicing/crashing the
// program/kernel). 'where' must be a virtual address already mapped
// outside the kernel virtual memory ranges. 'where' must be page
//aligned. 'where' must already be a legal user-space virtual page.
// Returns the address of the physical memory page 'where' points to
// before being cleared. Before returning the physical page to the page
// allocator, make sure that it is not used by other virtual pages.
addr_t UnmapUser(addr_t where);
// =====================================================================
// Function Group 3.
// Mapping a range of kernel pages.
//
// These functions allow you to specify a range of virtual pages that
// shall be usable by the kernel. Memory will be allocated accordingly.
//
// Usage:
//
// const addr_t mapto = 0x4000UL;
// size_t numpages = 8;
// size_t bytes = numpages * 4096UL;
// if ( !VirtualMemory::MapRangeKernel(mapto, bytes) )
// {
// /* handle error */
// }
//
// /* use memory here */
// ...
//
// VirtualMemory::UnmapRangeKernel(mapto, bytes);
// Allocates the needed pages and maps them to 'where'. Returns false if
// not enough memory is available. 'where' must be page aligned. 'bytes'
// need not be page aligned and is rounded up to nearest page. The
// region of 'where' and onwards must not be currently mapped.
// The memory will only be readable and writable by the kernel.
// The memory will be available the instant the function returns.
// The whole region must be within the kernel virtual page ranges.
bool MapRangeKernel(addr_t where, size_t bytes);
// Deallocates the selected pages, and unmaps the selected region.
// 'where' must be paged aligned. 'bytes' need not be page aligned and
// is rounded up to nearest page. Each page in the region must have been
// allocated by MapRangeKernel. You need not free a whole region at once
// and you may even combine pages from adjacent regions.
void UnmapRangeKernel(addr_t where, size_t bytes);
// =====================================================================
// Function Group 4.
// Mapping a range of user-space pages.
//
// These functions allow you to specify a range of virtual pages that
// shall be usable by the user-space. Memory will be allocated
// accordingly.
//
// Usage:
//
// const addr_t mapto = 0x4000UL;
// size_t numpages = 8;
// size_t bytes = numpages * 4096UL;
// if ( !VirtualMemory::MapRangeUser(mapto, bytes) )
// {
// /* handle error */
// }
//
// /* let user-space use memory here */
// ...
//
// VirtualMemory::UnmapRangeUser(mapto, bytes);
// Allocates the needed pages and maps them to 'where'. Returns false if
// not enough memory is available. 'where' must be page aligned. 'bytes'
// need not be page aligned and is rounded up to nearest page. The
// region of 'where' and onwards must not be currently mapped.
// The memory will be readable and writable by user-space.
// The memory will be available the instant the function returns.
// The whole region must be outside the kernel virtual page ranges.
bool MapRangeUser(addr_t where, size_t bytes);
// Deallocates the selected pages, and unmaps the selected region.
// 'where' must be paged aligned. 'bytes' need not be page aligned and
// is rounded up to nearest page. Each page in the region must have been
// allocated by MapRangeUser. You need not free a whole region at once
// and you may even combine pages from adjacent regions.
void UnmapRangeUser(addr_t where, size_t bytes);
#ifdef PLATFORM_X86
// Define where the kernel heap is located, used by the heap code.
const addr_t heapLower = 0x80000000UL;
const addr_t heapUpper = 0xFF800000UL;
// Physical pages may be safely temporarily mapped to this address and a
// good dozen of pages onwards. Beware that this is only meant to be temporary.
// good dozen of pages onwards. Beware that this is only meant to be
// a temporary place to put memory.
const addr_t tempaddr = 0xFF800000UL;
#endif