The following sections describe L3's virtual memory management
facilities: address spaces, data spaces and memory managers, and their
interaction. 3.1 Overview
3.1.1 Address Spaces
An address space (aka virtual address space) defines the set of
valid virtual addresses that any thread executing within the task
owning the address space is allowed to reference. An address space is
owned by exactly one task. Creation and Destruction
An address space is created when the task is created, and destroyed
when the task is destroyed. When a new task is created, its address
space contains only one valid memory range, namely a mapping of its
standard data space (see below).
If a task needs to establish other mappings after it has been started,
it can only do so with a DSMapRegion system call.
Memory Range Establishment
The semantics associated with a valid range of virtual memory are provided by memory managers. When a new memory range is established in an address space, a thread id of a manager providing those semantics needs to be specified. No kernel calls exist for a task to directly affect the semantics associated with its memory ranges. A task has such control only by virtue of choosing memory managers providing the desired functionality or by communicating directly with its memory managers to direct their actions.
In order to establish a new range of virtual memory in an address space, the task needs to specify a map vector, a data structure containing the specific details of the memory range (placement, size, alignment, access mode, data space to use and data space offset and size (the map region)). There exist two helper functions DSFillMapVectorExt and DSFillMapVectorStd which assist in building a map vector.
DSMapRegion is the system call to establish a new range of virtual memory in an address space. This call communicates the map vector address and protection attributes to the kernel. After specifying a map vector in a DSMapRegion system call, the vector remains under kernel control until the region is unmapped and may not be recycled for other purposes.
Memory ranges can be made invalid (i.e., can be unmapped) by calling
DSMapRegion specifying the appropriate map vector and access
mode NO_ACCESS.
3.1.2 Data Spaces
A data space is an abstract memory object which can be mapped into the address space of tasks. The semantics associated with a data space are provided by the memory manager that backs it. Data spaces can be of variable sizes up to 4 GB.
From the kernel's perspective, data spaces are uniquely identified by a <user_task, memory_manager, data_space_number> triple, where the data space number is an identifier which is never interpreted by the kernel. It is the manager's duty to decide whether the same data space number in two different user tasks address the same memory object or not (in the standard memory manager, it does not; see section [here]).
Data spaces can be manipulated by means provided by the memory
manager, and of course by modifying memory ranges mapped from them.
3.1.3 Memory Managers
A memory manager (sometimes also called a pager) provides the semantics associated with the act of referencing memory regions which have mapped data spaces backed by it by providing (or denying) access to pages of data that have been referenced by user tasks.
Every thread can be a memory manager for every other thread. There is
no special initialization necessary to become a memory manager, and
the kernel provides no special access protection for them (as with for
all L3 IPC). Once the id of some thread has been specified in a
DSMapRegion system call, the kernel starts treating the
thread as the memory manager for the region (and send page fault
messages to it, which, of course, can be ignored or refusingly replied
to by the thread). Basic Manipulation of Address Spaces
Manipulation of a task's address space takes the following basic form:
Of course, memory ranges specified by the memory manager will most probably be memory backed by other memory managers. It is the memory manager's responsibility to ensure that memory referenced by a returned flexpage currently resides in main memory (perhaps by touching the respective physical memory pages and thereby invoking other memory managers) so that the faulting task will not immediately fault again.
The kernel should be viewed as using main memory as a (directly accessible) cache for the contents of the various data spaces.
If a thread requests write access for a page and the memory manager
permits it, then it is certain that the page has been modified. This
way it is possible for a memory manager to keep track of modified
pages.
The Standard Memory Manager
The standard memory manager pages data spaces backed by it against the system's paging space, the background. It maintains an ownership relation between its data spaces and tasks: Only the owning task can map any specific data space at a time.
Special Properties of the Standard Memory Manager.
There are several ways the standard memory manager is special or different from other memory managers:
The Supervisor maps the specified standard data space into the first 32 MB (address range 0--0x2000000) of the newly created task's address space.
Manipulating Data Spaces Backed by the Standard Memory Manager.
Standard memory manager data spaces are created by copying a special data space called nilspace (DSCopy). Nilspace is special in that it does not own any pages. By copying nilspace the new data space gets an unique identifier. If created in this way, a data space still has no pages allocated from the background.
There are several possibilities to populate a data space with pages allocated from the background:
The following sections describe the C interface for manipulating data spaces. All calls except the system call DSMapRegion and the helper function DSFillMapVectorExt can only manipulate data spaces backed by the standard memory manager.
XXX need to clean up the following sections... 3.2.1 Fill Map Vector
This function is provided twice, ones for the filling of a map vector for a data space provided by the standard pager and the other for map vectors for data spaces provided by external pagers. The difference are the task identifiers of both the external pager and the task or thread, which wants to map the data space.
The Map Vector filled ones is used for both mapping and unmapping of the given region of the data space.
void DSFillMapVectorStd(TMapVector *m_v, int lower_bound, int upper_bound, DataSpaceT ds, int size, ByteT align, int offset) void DSFillMapVectorExt(TMapVector *m_v, int lower_bound, int upper_bound, DataSpaceT ds, TTask pager_id, TTask my_task, int size, Byte align, int offset)
For supporting the map function there exists a function for filling the map vector.
Parameters:
void *DSMapRegion(TMapVector *map_vector, Byte access, int *size)
The function MapRegion forces the kernel to map or to unmap a region of a data space specified in the map_vector.
Parameters:
By giving NO_ACCESS the mapped region will be unmapped.
void DSCopy(DATASPACE source, DATASPACE *destination)
Parameter:
DATASPACE DSNext(DATASPACE data_space)
This function provides the Identifier of the next data space of the calling task. If the identifier is 0 for nilspace, the function provides the identifier of the standard data space of the task. Calling the function with the identifier of the last data space of the task, the function provides nilspace.
Parameter:
DATASPACE DSNilspace()
This function provides the data space identifier of nilspace. In general, this is 0.
DATASPACE DSAssociatedDataspace(void *address)
This function provides the data space identifier for a given address.
void DSCopyPages(DATASPACE source, DATASPACE destination, int from_page, int to_page, int pages)
This function copies a number of pages from one datspace to another.
int DSPages(DATASPACE data_space)
This function provides the number of pages of the given data space.
void DSDeletePages(DATASPACE data_space, int from_page, int to_page, int pages)
This function deletes a number of pages of the given data space. The pages won't really deleted, they will only be unmapped.
DATASPACE DSDelete(DATASPACE data_space)
This function deletes the specified data space. The data space hasn't been mapped, but it can be mapped.
TBool DSExists(DATASPACE data_space)
This function traces whether the given data space exists or not.
void DSMove(DATASPACE *source, DATASPACE *destination)
This function moves a data space to another. The source data space is deleted after moving. If the destination data space doesn't exist, it will be created.
DATASPACE DSNext(DATASPACE data_space)
This function provides the identifier of the following data space of the given data space of one task. Every data space of a task has got an index, which is used to get the next data space.
int DSNextPage(DATASPACE data_space, int pageno)
This function provides the next used page of a given data space. There is a page sequence in every data space. If the page number -1, the function provides the first used page of the data space. There can be gaps in the page sequence.
int DSDataspaces(TTask task)
This function provides the total number of all (named and unnamed) data spaces of the given task. The maximum number is 16383.
void DSCopyPagesInside(DATASPACE data_space, int from_page, int to_page, int pages)
This function copies a number of pages inside one data space. The copying starts at the page from page and copies n pages to the page to page. In this case lazy copying is used.
int DSIndex(DATASPACE data_space)
This function provides the index of a data space inside the sequence of data spaces of one task.
int DSStorage(DATASPACE data_space)
This function provides the storage, used by the given data space. The storage size is given in bytes, but page aligned, i.e. the result can be divided by page size 4096 without any rest.
int DSType(DATASPACE data_space)
This function provides the type of the given data space, if there was a type number assigned to the data space.
Data spaces can be mapped into the address space of a task. The part of the data space which is mapped is called a map region. The conditions for the mapping of a region is specified in a so called map vector. There must be a map vector for each mapped region. This vector is filled by the mapping task, but controlled by the kernel. Don't use one map vector twice at the same time! Different regions of one data space can be mapped at the same time.
If a data space is mapped without errors the application can use it inside the requested size. By mapping a region of the data space the task gets the part of the data space which is described by the values for offset and size specified in the map vector. The offset is the beginning of the map window relativly to the beginning of the data space. If the task exceeds the borders of the mapped region, an error is created by the kernel. Otherwise the task can use every address inside the map region. If the page doesn't reside in the real memory, the kernel will create a page fault, which is handled by the pager which created the data space. In general this will be the standard pager. The mapping task won't see the page fault.
The map vector is a structur like the following:
struct { int lower_bound; union { DataSpaceIdT Long; DataSpaceOldIdT Short; } ds; int upper_bound; int size; int offset; } TMapVector;
TDataSpaceId is a 32bit long identifier either consisting of the V2 - data space id and three integers set to 0 or consisting of four parts, if the data space is provided by the standard pager:
struct { int ds; int reserve[3]; } DataSpaceOldIdT; struct { int magic_wx; int ds_no; int thread_low; int thread_high; } DataSpaceIdT;