Exam starts at 14:00 Tue Nov 26 and ends at 14:00 Wed Nov 27.
M3: A virtual memory manager

Use the provided frame table to implement a virtual memory manager that is responsible for managing the memory mapped into your processes which supports on-demand memory mapping of an application and an allocate-on-demand memory heap.

Current Implementation

The current implementation simply pre-maps in the pages for the single binary. Any actual VM page faults will trigger an assert() which halts the system, under the assumption something has gone wrong. The executable itself is mapped in with untracked frames (i.e. we leak them), and finally, the current sos application malloc implementation uses a static array.

A small malloc() intro

malloc is the standard library function to allocate memory in C. In our system, malloc is provided by the musl libc library. malloc manages memory from a bigger memory pool. In the SOS code you are provided, malloc uses memory from a pre-allocated array in the initial data section as the memory pool. This pool is fixed in size, and malloc returns NULL when the pool is exhausted. See the diagram below for an approximate picture of the memory layout. The code musl uses to allocate memory from the static region for SOS is in projects/aos/sos/src/sys/morecore.c.

SOS applications use an independent implementation that uses a pre-allocated memory pool in the application's data section (as shown in the middle of the diagram). The code musl libc uses in applications is in projects/aos/libsosapi/src/sys_morecore.c. So just for emphasis, there are two versions of the morecore code in the system.

One of the tasks of this milestone is to leverage virtual memory to create a dynamically allocated memory region for malloc's use, usually termed the heap, as shown at the bottom of the diagram. The dynamic region is allocated on-demand via VM faults, and can be expanded in range dynamically by requesting that SOS increase the brk point.

In this milestone, you will modify the application-level morecore routines in projects/aos/libsosapi/src/sys_morecore.c to use your dynamically-allocated memory region. Again, be sure you're modifying the morecore routines for sos applications, not SOS's own morecore routines.

Malloc and musl libc peculiarities

The malloc implementation in musl libc (i.e. the application C library) has two behaviours for implementing memory allocation of the memory pool:

Aarch64 page tables

Aarch64 has a four-level page table structure, where all structures are 4K in size and use 9 bits of the virtual address. The paging structures are as follows:

For a page to be mapped successfully, all 4 paging structures must exist first. In the provided code (map_frame_impl in mapping.c) this is done lazily, and no record is maintained of the allocated paging structures. When a mapping operation fails due to a missing paging structure, seL4 returns the amount of bits that could not be resolved, which can be retrieved with seL4_MappingFailedLookupLevel().

The Milestone

In this milestone you will:

Provided code

A frame table has been provided to provide allocation and management of 4KiB untyped objects as memory frames for processes within SOS.

You should become familiar the interface in projects/aos/sos/src/frame_table.h and the implementation in projects/aos/sos/src/frame_table.c as you will need to extend this in a later milestone.

The frame table provides mechanisms to allocate and free frames that may be used to map memory into the address spaces of applications, as is already done in elf.c. These frames are all also mapped into the address space of SOS allowing their contents to be read and written.

Rather than providing pointers to each frame, the frame table provides 19-bit frame references that uniquely identify each frame being managed by the frame table.

Design alternatives

Probably the main thing that you should consider here is the layout of your processes address space. Some things you will want to consider is where you place various parts of memory such as the stack, heap and code segments. You may also have some other regions in your process address space, one of these is the IPC Buffer.

You should also think about if you want to make different ranges of the address space have different permissions, eg: you may want to make code read-only to prevent bugs, or have a guard page at the end of your stack to prevent overflow.

While not needed for this milestone, you should think about what book keeping is required to delete an address space and free all the resources associated with it.


Assessment

Demonstration

The main demonstration here will be to show a user process running with a high stack pointer (> 0x8000000000). You should also demonstrate a user process using malloc() from a heap.

#include <utils/page.h>

#define NBLOCKS 9
#define NPAGES_PER_BLOCK 28
#define TEST_ADDRESS 0x8000000000

/* called from pt_test */
static void
do_pt_test(char **buf)
{
    int i;

    /* set */
    for (int b = 0; b < NBLOCKS; b++) {
        for (int p = 0; p < NPAGES_PER_BLOCK; p++) {
          buf[b][p * PAGE_SIZE_4K] = p;
        }
    }

    /* check */
    for (int b = 0; b < NBLOCKS; b++) {
        for (int p = 0; p < NPAGES_PER_BLOCK; p++) {
          assert(buf[b][p * PAGE_SIZE_4K] == p);
        }
    }
}

static void
pt_test( void )
{
    /* need a decent sized stack */
    char buf1[NBLOCKS][NPAGES_PER_BLOCK * PAGE_SIZE_4K];
    char *buf1_ptrs[NBLOCKS];
    char *buf2[NBLOCKS];

    /* check the stack is above phys mem */
    for (int b = 0; b < NBLOCKS; b++) {
        buf1_ptrs[b] = buf1[b];
    }
    assert((void *) buf1 > (void *) TEST_ADDRESS);

    /* stack test */
    do_pt_test(buf1_ptrs);

    /* heap test */
    for (int b = 0; b < NBLOCKS; b++) {
        buf2[b] = malloc(NPAGES_PER_BLOCK * PAGE_SIZE_4K);
        assert(buf2[b]);
    }
    do_pt_test(buf2);
    for (int b = 0; b < NBLOCKS; b++) {
        free(buf2[b]);
    }
}

You should also be able to explain to the tutor how your code works and any design decisions you took.

Show Stoppers

Better Solutions