3 Virtual Memory Management

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:

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:

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:

3.2 C Bindings for System Calls Related to Data Spaces

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:

m_v
The parameter represents the address of the map vector for this map region. The vector is filled before the first mapping and will be unchanged until the unmapping of the region.

lower bound
The parameter lower bound defines the lower bound of the mapping address in the address space of the mapping thread.

upper bound
The parameter upper bound defines the upper bound of the mapping address in the address space of the mapping thread. The kernel tries to find a start address inside the interval specified by lower and upper bound. If both addresses ar identical the kernel maps the region at this address.

pager_id
The parameter pager_id specifies the requested pager. If this parameter has the value 0 the standard pager is chosen.

size
The parameter size specifies the size of the map region. The lower four bytes of size should be 0. Otherwise the size will be/ rounded to the next bigger value.

align
The parameter align specifies the requested alignment. It can be 0 default alignment should be used. 3..31 use alignment 2**align other values reserved

my_task
The parameter my_task specifies the mapping task. Only the lower byte is used for the operation.

size
The parameter size specifies the size of the map region. Beginning at data space base + offset size bytes will be mapped into the address space of the task.

offset
The parameter offset specifies the beginning of the maop region inside the data space.

3.2.2 Map Region

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:

map_vector
The parameter map_vector specifies the map vector for the mapping operation. All significant values are specified in this vector. Note: You can't use one map vector for different regions at the same time!

access
The parameter access speciefies the wanted access to the map region. access can be one of:

  1. 0 NO_ACCESS
  2. 1 READ_ONLY
  3. 2 NO_CHANGE
  4. 3 READ_WRITE

By giving NO_ACCESS the mapped region will be unmapped.

size
The parameter size is an integer variable, where the size of the mapped region is given back if the operation succeeds.

return value
The return value is an address in the address space of the mapping task, where to the map region is mapped.

3.2.3 Copy Data

void DSCopy(DATASPACE source, DATASPACE *destination)

Parameter:

source
The parameter source represents the identifier of the data space which should be copied by this operation. If this data space id is nilspace a new data space is created.

destination
The parameter destination represents the identifier of the destination data space. If source is nilspace, destination will be the identifier of a new data space.

3.2.4 Next Data Space

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:

data_space
The parameter data space describes the identifier of the data space whose next data space (the one which has the next index) should be provided.

return value
The return value is the identifier of the next data space. This is either the id of the standard data space, if the input was nilspace, or nilspace, if the input was the id of the last data space of the task.

3.2.5 Nilspace

DATASPACE DSNilspace()

This function provides the data space identifier of nilspace. In general, this is 0.

return value
The return value is the data space id of nilspace, 0.

3.2.6 Associated Data Space

DATASPACE DSAssociatedDataspace(void *address)

This function provides the data space identifier for a given address.

address
The parameter address is an address inside the data space associated with this address.

return value
The return value of this function is the identifier of the associated data space or nilspace, if there is no data space mapped at this address.

3.2.7 Copy Pages

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.

source data space
The parameter source data space represents the id of the data space whose pages will be copied.

destination data space
The parameter destination data space represents the identifier of the destination to which the pages will be copied.

from page
The parameter from page specifies the start page inside the source data space, from which n pages will be copied.

to page
The parameter to page specifies the start page in the destination data space, where the pages will be copied to.

pages
The parameter pages specifies the number of pages, which will be copied.

3.2.8 Pages

int DSPages(DATASPACE data_space)

This function provides the number of pages of the given data space.

data space id
The parameter data space id specifies the data space.

3.2.9 Delete Pages

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.

data space id
The parameter data space id represents the data space, from which a number of pages should be deleted.

from page
The parameter from page specifies the start page, from which the pages should be deleted.

to page
The parameter to page specifies the end page, up to which the pages should be deleted.

pages
The parameter pages specifies the number of pages to be deleted.

3.2.10 Delete, Exists

DATASPACE DSDelete(DATASPACE data_space)

This function deletes the specified data space. The data space hasn't been mapped, but it can be mapped.

data space id
The parameter specifies the data space to be deleted.

return value
The return value is no data space, if the operation succeeded.

3.2.11 Exists

TBool DSExists(DATASPACE data_space)

This function traces whether the given data space exists or not.

data space id
The parameter specifies the data space to be checked.

return value
The return value is 1 if the data space exists, 0 otherwise.

3.2.12 Move

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.

source
This parameter represents the source data space, which will be moved.

destination
This parameter represents the destination data space, which will be existing after the moving.

3.2.13 Next

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.

data space
The parameter specifies the identifier of the data space, whose follower is searched.

return value
The return value is the identifier of the follower of the given data space.

3.2.14 Next Page

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.

data space
This parameter specifies the data space, whose pages are searched.

pageno
This parameter represents the number of the page, whose following page is searched. If it is -1, the first page is searched.

return value
The return value is the number of the following page. If there are no more pages, the return value is -1.

3.2.15 Dataspaces

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.

task
This parameter specifies the task, whose number of data spaces is searched.

return value
The return value is the total number of all data spaces of task.

3.2.16 Copy Pages

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.

data space
The parameter data space specifies the data space, where the copying will take place.

from page
The parameter from page defines the start page of the copy operation.

to page
The parameter to page specifies the start page of the destination region, where the pages are copied to.

pages
The parameter pages specifies the number of pages, which will be copied.

3.2.17 Index

int DSIndex(DATASPACE data_space)

This function provides the index of a data space inside the sequence of data spaces of one task.

data space
The parameter data space specifies the data space identifier of the data space, whose index is searched.

return value
The return value is the index of the given data space. The index is part of the data space identifier.

3.2.18 Storage

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.

data space
This parameter specifies the data space, whose size should be provided.

return value
The return value is the used storage in bytes.

3.2.19 Type

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_space
The parameter specifies the data space, whose type should be provided.

return value
The return value is the type of the data space.

3.3 Low-level Kernel Interface

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;   

Marion Schalm, Jean Wolter, Michael Hohmuth
26.12.1995 (unfinished)