Advanced Operating Systems (LV 7680)
M3 - A pager
- Due: Week 8
- Marks: 4
- Use your memory manager from M2 to write a simple VM fault handler that 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 apps/sos/src/sys/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 in musl libc uses in applications
is in libs/libsos/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 libs/libsos/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:
- Musl expects a
brk
syscall that has similar semantics to the Linuxbrk
syscall. The current implementation issys_brk
inlibs/libsos/src/sys_morecore.c
and it returns memory from a static array, as described above. - If
malloc
is called to allocate more than 112KiB, then musl libc does not allocate from the heap or increase the heap viasys_brk
, but instead it usesmmap
to create an large anonymous mapping. The sample implementation of this is thesys_mmap2
function inlibs/libsos/src/sys_morecore.c
and it currently steals memory from the top of the static morecore region, and leaks memory on when it's released withmunmap
The Milestone
In this milestone you will:
- Implement a page table to translate virtual memory addresses to frame
table entries. You need to consider where you will keep track of
cptrs
to both levels of the page table and to the frames themselves. Note: ARM uses a 16K level-one page directory and a 1K level-two page table, i.e. 12-bits index the top level, and 8-bits index the second level. You do not have to follow this split, you have freedom to choose whatever split is convenient for your implementation of SOS. However, you do need to theoretically keep track of the cptrs to an in-kernel page tables to be able to de-allocate them. So if SOS applications have a 2GB address space, you potentially need to keep track of 2^11cptrs
. - Using our code as a guide only, create your own version of the
map_page()
. Your version (saysos_map_page()
) will need to populate and use your data structures to keep track of address spaces, virtual addresses, frame physical addresses andcptrs
. Note: The existingmap_page()
is used internally and thus needs to continue to work as before, so be careful if modifying it. - Design your application-level address space layout and permissions - including the stack, heap and other sections. You may wish to consider a region-like abstraction like OS/161.
- Change the current application-level
malloc
implementation to use virtual memory by modifying thesys_brk()
to use the heap memory region, thus removing the need for a static array (just allow the application to fault in memory within the heap region), as shown at the bottom of the above diagram.- At this point, you do not need to support the
brk
system call to expand the region. You can choose a fixed (large-ish) virtual memory range. You can add abrk()
system call in a later milestone. - You are not required to support the
mmap
syscall for anonymous memory in this project. We don't expect you to support applications callingmalloc
for sizes over 112KiB (you shoould gracefully fail, though, those requests in the library). Note: there is a small bonus available for those who do implementmmap/munmap
.
- At this point, you do not need to support the
- Modify the bootstrap ELF-loading code to use your mapping functions, not the frame table directly.
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 (> 0x20000000
). You should also demonstrate a
user process using malloc()
from a heap.
#include
#define NPAGES 27
#define TEST_ADDRESS 0x20000000
/* called from pt_test */
static void
do_pt_test(char *buf)
{
int i;
/* set */
for (int i = 0; i < NPAGES; i++) {
buf[i * PAGE_SIZE_4K] = i;
}
/* check */
for (int i = 0; i < NPAGES; i++) {
assert(buf[i * PAGE_SIZE_4K] == i);
}
}
static void
pt_test( void )
{
/* need a decent sized stack */
char buf1[NPAGES * PAGE_SIZE_4K], *buf2 = NULL;
/* check the stack is above phys mem */
assert((void *) buf1 > (void *) TEST_ADDRESS);
/* stack test */
do_pt_test(buf1);
/* heap test */
buf2 = malloc(NPAGES * PAGE_SIZE_4K);
assert(buf2);
do_pt_test(buf2);
free(buf2);
}
You should also be able to explain to the tutor how your code works and any design decisions you took.
Show Stoppers
- A one-level page table (unless it is a Hashed Page Table)
- Designs that leak the
cptrs
of frames or seL4-internal page tables. - Page table look-ups that are not constant time (HPTs can tolerate a small amount if collision chaining).
- Designs that simply map memory on any fault regardless of location in the application virtual address space.
- Designs that allow user applications to map in
NULL
- A stack and heap that can run into each other
- Assuming allocation will never fail (ok to panic, but need to check result of allocation calls)
- A design that pre-maps all pages so that the VM fault handler is not invoked (or even implemented).
Better Solutions
- Designs that probe page table efficiently - minimal control flow checks in the critical path, and minimal levels traversed.
- Designs that don't use SOS's
malloc
to allocate SOS's page tables (to avoid hitting the fixed size of the memory pool). - Designs that have a clear SOS-internal abstraction for tracking ranges of virtual memory for applications.
- Solutions that minimise the size of page table entries (e.g. only contain the equivalent of a PTE and a cptr).
- Enforcing read-only permissions as specified in the ELF file or API calls.