CSCI 3310
Operating Systems

Bowdoin College
Fall 2022
Instructor: Sean Barker

Project 4 - Virtual Memory Pager

Assigned:Sunday, November 6.
Groups/Repos Due:Wednesday, November 9, 11:59 pm.
Code Due:Tuesday, December 6, 11:59 pm.
Writeup Due:Thursday, December 8, 11:59 pm.
Collaboration Policy:Level 1
Group Policy:Pair-optional (but recommended!)

In this project, you will implement part of a virtual memory paging subsystem. In particular, you will implement an external pager, which is a process that handles virtual memory requests for other application processes. This program will be analogous to the core virtual memory components of an operating system that uses paging.

Pager Overview

Your pager will handle address space creation and destruction, page faults, and simple argument passing between virtual and physical address spaces. The pager will manage a fixed range of the virtual address space (called the arena) of each application that uses it. While running, the pager will handle all access requests to virtual memory addresses located in the arena. Valid pages in the arena will be stored in (simulated) physical memory or in (simulated) disk. Your pager will manage these two resources (physical memory and disk space) on behalf of all applications using the pager.

In addition to handling page faults, your pager will provide two system calls to applications: vm_extend and vm_syslog. An application uses vm_extend to ask the pager to make another virtual page of its arena valid. You can think of vm_extend like a very low-level memory allocation routine, on top of which a higher-level allocation library like malloc could be built (though your test applications will be calling vm_extend directly). The vm_syslog function is used to ask the pager to print a message in memory that exists in the virtual address space of the calling process (which might seem trivial on first glance, but is actually somewhat tricky)!

Infrastructure Overview

Your external pager works in tandem with the CPU's hardware memory management unit (MMU) and exception-handling mechanism (colloquially, the hardware infrastructure). The MMU is automatically invoked on every virtual memory access and performs the following tasks:

  1. The MMU extracts the page number from the address and looks up the corresponding page table entry (PTE), which is contained in the page table pointed to by the page table base register (PTBR). The PTEs read by the MMU contain a frame number and two additional protection bits that determine whether loads (i.e., reads) and/or stores (i.e., writes) to this page should trigger a fault.
  2. For accesses to a page in which the appropriate protection bit is not set, the MMU triggers a fault, which transfers control to the pager's fault handler and then retries the faulting instruction after the handler finishes. Note that faults are controlled solely by the protection bits, not by any other factors such as whether the page is resident (though in general, accessing a non-resident page should trigger a fault).
  3. For memory accesses that do not trigger a fault, the MMU translates the virtual address to a physical address and accesses that physical address. For these accesses, all functions are performed in hardware, and thus the pager is not involved at all.

Some MMUs automatically maintain reference and dirty bits (for use in page replacement), while other MMUs leave this task to be handled in software. The MMU in this project does not automatically maintain reference or dirty bits, so the pager will need to maintain this information itself. More generally, although the MMU will read the PTEs and invoke faults appropriately, it will never change any of the data stored in the PTEs; maintaining the PTEs is solely the job of the pager.

Both faults and system calls invoke the hardware exception mechanism. When a system call instruction is executed, the exception mechanism transfers control to the registered kernel handler for the exception. The following diagram shows how your pager will interact with applications that use the pager. An application makes a request to the system via the system calls vm_extend, vm_syslog and vm_yield, or by trying to access a page without the relevant protection bit set (thus triggering a fault).

                        initialize VM system         -->    vm_init
                        create process               -->    vm_create
                        end process                  -->    vm_destroy
                        switches to new process      -->    vm_switch

vm_yield         -->    may switch to new process
vm_extend        -->    system call handler          -->    vm_extend
vm_syslog        -->    system call handler          -->    vm_syslog
faulting load    -->    exception handler            -->    vm_fault
faulting store   -->    exception handler            -->    vm_fault

+-----------+           +----------------+                +----------------+
|APPLICATION|           | INFRASTRUCTURE |                | EXTERNAL PAGER |
+-----------+           +----------------+                +----------------+

Note in the above diagram that there are two versions of vm_extend and vm_syslog: one for applications and one for the pager. The application-side versions of these functions are implemented in the provided library libvm_app.a and are called by the application process. The pager-side versions of these functions are implemented by you in your pager. You can think of the application versions as system call wrappers, while the pager versions are the kernel routines that are invoked via the system calls. When the application calls the wrapper functions, the infrastructure takes care of invoking vm_extend or vm_syslog in the pager. The actual declarations for both the application and pager functions are detailed later in the writeup.

The hardware infrastructure in this project (i.e., the MMU and the exception functionaliy) is emulated through a provided software infrastructure. To use this infrastructure, each application that uses the external pager includes vm_app.h and links with the provided libvm_app.a library, while the external pager itself includes vm_pager.h and links with the provided libvm_pager.a library. You do not need to understand the mechanisms used to emulate the hardware operation (but in case you're curious, the infrastructure uses mmap, mprotect, signal handlers, named pipes, and remote procedure calls; suffice to say, there's a lot going on to simulate the regular hardware features of the system in a relatively seamless way).

Linking with these libraries enables application processes to communicate with the pager process in the same way that applications on real hardware communicate with the operating system. Specifically, applications issue load and store instructions (i.e., reads and writes to memory), and these instructions are translated or faulted by the infrastructure exactly as in the above description of the MMU. For faulting instructions and system calls, the infrastructure transfers control to the external pager via function calls.

Address and Page Table Specification

A virtual address is composed of a virtual page number and a page offset, as follows:

         bit 63-13              bit 12-0
   | virtual page number  |   page offset     |

The (simulated) MMU uses a single-level, fixed-size page table. A page table is an array of page table entries (PTEs), one PTE per virtual page in the arena. The MMU locates the active page table through the page table base register (PTBR). In this case, rather than an actual hardware register, the PTBR is a global variable that is declared and accessed by the infrastructure, but will be controlled by your pager. The following portion of vm_pager.h details the arena, page table, PTEs, and PTBR. Remember that the arena is the chunk of the virtual address space that is managed by the pager.

 * ***********************
 * * Definition of arena *
 * ***********************

/* page size (in bytes) for the machine */
#define VM_PAGESIZE 8192

/* virtual address at which application's arena starts */
#define VM_ARENA_BASEADDR    ((void*) 0x60000000)

/* virtual page number at which application's arena starts */

/* size (in bytes) of arena */
#define VM_ARENA_SIZE    0x20000000

 * **************************************
 * * Definition of page table structure *
 * **************************************

 * Format of page table entry.
 * ppage: the physical page (frame) for this virtual page, if applicable.
 * read_enable: bit determining whether loads to this virtual page will fault.
 *    (0 ==> fault, 1 ==> no fault)
 * write_enable: bit determining whether stores to this virtual page will fault.
 *    (0 ==> fault, 1 ==> no fault)
typedef struct {
    unsigned long ppage : 51;   /* bits 0-50 of pte */
    unsigned int read_enable : 1; /* bit 51 of pte */
    unsigned int write_enable : 1;  /* bit 52 of pte */
} page_table_entry_t;

 * Format of page table.  Entries start at virtual page VM_ARENA_BASEPAGE,
 * i.e. ptes[0] is the page table entry for virtual page VM_ARENA_BASEPAGE.
typedef struct {
    page_table_entry_t ptes[VM_ARENA_SIZE / VM_PAGESIZE];
} page_table_t;

 * MMU's page table base register.  This variable is defined by the
 * infrastructure, but it is controlled completely by the student's pager code.
extern page_table_t* page_table_base_register;

Make sure you understand the behavior of the fields of the page table entries. Whenever a page is accessed by the MMU, it is accessed either as a load (i.e., read) or a store (i.e., write). The two protection bits read_enable and write_enable determine whether reads or writes (respectively) are permitted; if not, access will trigger a call to the pager's vm_fault routine.

Critically, note that a non-resident page access is not the only scenario in which you might wish an attempted page access to fault. This point is discussed further later on.

If the protection bits do not trigger a fault, the MMU will access the ppage field (the frame/physical page number) and then access the requested memory address. For faulting accesses, the MMU will automatically retry the access after handling the fault via vm_fault.

A physical page (frame) may be associated with at most one virtual page at any given time (i.e., no sharing is allowed).

Client Application Interface

Applications use three system calls to communicate explicitly with the simulated operating system: vm_extend, vm_syslog, and vm_yield. The declarations for these system calls are given in vm_app.h:

 * vm_extend
 * Ask for the lowest invalid virtual page in the process's arena to
 * be declared valid.  Returns the lowest-numbered byte of the newly
 * valid virtual page.  For example, if the valid part of the arena
 * before calling vm_extend is 0x60000000-0x60003FFF, the return value
 * will be 0x60004000, and the resulting valid part of the arena will
 * be 0x60000000-0x60005FFF. The newly-allocated page is initialized to
 * all zero bytes. Returns null if the new page cannot be allocated.
extern void* vm_extend();

 * vm_syslog
 * Ask external pager to log a message of nonzero length len. Message data
 * must be in the part of the address space controlled by the pager.
 * Returns 0 on success or -1 on failure.
extern int vm_syslog(void* message, unsigned len);

 * vm_yield
 * Ask operating system to yield the CPU to another process. The
 * infrastructure's scheduler is non-preemptive, so a process runs
 * until it calls vm_yield or exits.
extern void vm_yield();

As a matter of interest, the vm_extend system call is roughly equivalent to the real-world sbrk system call in Linux (which is used internally by malloc or new to change the usable size of the heap).

Importantly, note the difference between a regular print statement (e.g., calling printf) and calling vm_syslog: syslogging is asking the pager (rather than the application) to print the specified message. Hence, calling vm_syslog results in output from the pager process but does not produce any output on the application side.

The following is a sample application program that uses the external pager. This application allocates one virtual page within the arena, writes five bytes to it, then asks the pager to log the five bytes just written. As explained above, since this program is using vm_syslog to print the message, no output will be produced by the application process.

// - a sample application program that uses the external pager

#include "vm_app.h"

int main() {
    char* p;
    p = (char*) vm_extend(); // p is an address in the arena
    p[0] = 'h';
    p[1] = 'e';
    p[2] = 'l';
    p[3] = 'l';
    p[4] = 'o';
    vm_syslog(p, 5); // pager logs "hello"
    return 0;

Pager Specification

The functions that you must implement in your pager are declared in vm_pager.h. These declarations are shown below. Note you will not implement a main function; instead, main is already implemented in libvm_pager.a and included in your pager during compilation. The infrastructure will invoke your pager functions as described previously.

 * vm_init
 * Initializes the pager and any associated data structures. Called automatically
 * on pager startup. Passed the number of physical memory pages and the number of
 * disk blocks in the raw disk.
extern void vm_init(unsigned memory_pages, unsigned disk_blocks);

 * vm_create
 * Notifies the pager that a new process with the given process ID has been created.
 * The new process will only run when it's switched to via vm_switch.
extern void vm_create(pid_t pid);

 * vm_switch
 * Notifies the pager that the kernel is switching to a new process with the
 * given pid.
extern void vm_switch(pid_t pid);

 * vm_fault
 * Handle a fault that occurred at the given virtual address. The write flag
 * is 1 if the faulting access was a write or 0 if the faulting access was a
 * read. Returns -1 if the faulting address corresponds to an invalid page
 * or 0 otherwise (having handled the fault appropriately).
extern int vm_fault(void* addr, bool write_flag);

 * vm_destroy
 * Notifies the pager that the current process has exited and should be
 * deallocated.
extern void vm_destroy();

 * vm_extend
 * Declare as valid the lowest invalid virtual page in the current process's
 * arena. Returns the lowest-numbered byte of the newly valid virtual page.
 * For example, if the valid part of the arena before calling vm_extend is
 * 0x60000000-0x60003FFF, vm_extend will return 0x60004000 and the resulting
 * valid part of the arena will be 0x60000000-0x60005FFF. The newly-allocated
 * page is allocated a disk block in swap space and should present a zero-filled
 * view to the application. Returns null if the new page cannot be allocated.
extern void* vm_extend();

 * vm_syslog
 * Log (i.e., print) a message in the arena at the given address with the
 * given nonzero length. Returns 0 on success or -1 if the specified message
 * address or length is invalid.
extern int vm_syslog(void* message, unsigned len);

Remember that all process scheduling aspects (e.g., the sorts of things you were concerned with in Project 3) are handled by the infrastructure here. Although your pager is notified when certain actions occur (e.g., a new process is created or a context switch is occurring), your pager is not responsible for initiating or managing these tasks. Instead, your pager should simply act appropriately based on the infrastructure's notifications in order to continue performing the pager functions. As mentioned in the previous section, the infrastructure's scheduler is simple and non-preemptive; each process using the pager simply runs until it completes or voluntarily yields the CPU by calling the vm_yield system call (which then results in calling the vm_switch pager function, assuming that there was another runnable process to switch to).

More details on the proper behavior of vm_fault and vm_syslog are provided below.

Page Replacement

If a fault occurs on a non-resident virtual page, you must find a physical page (frame) to associate with the virtual page. If there are no free physical pages, you must create a free physical page by evicting a virtual page that is currently resident.

The pager must use the second-chance (clock) algorithm to select a victim. The clock queue is an ordered list of all valid, resident virtual pages in the system (i.e., global replacement). To select a victim, check the next physical page in the queue. If it has been accessed in any way since it was last, continue searching to the next page in the queue (and clear its reference bit).

If the next physical page in the queue has not been accessed, then its virtual page should be evicted. Dirty and clean pages are treated the same when selecting a victim page to evict (i.e., don't continue searching past a dirty eviction candidate in order to locate a clean candidate). Additionally, you should not write out a dirty page to disk unless you're actually evicting that page. In other words, although you should be maintaining dirty bits for the purpose of avoiding disk work (as in the enhanced second-chance algorithm discussed in class), otherwise, the replacement algorithm should be the simple second-chance algorithm only.

Syslog Behavior

The vm_syslog routine is called with a pointer to an array of bytes in the current process's virtual address space and the length of that array. The pager should first check that the entire message is in valid pages of the arena. Return -1 (and don't print anything) if the message length is zero, or if any part of the message is not in a valid arena page.

After checking the message validity, the pager should copy the entire message into a C++ string in the pager's address space, then print the string to cout. You should use the following exact formatting for your print statement (assuming your C++ string is named str):

    cout << "syslog \t\t\t" << str << endl;

You must treat access to the message by vm_syslog exactly the same as if the application had accessed the message itself (e.g., for purposes of residency, reference, and so forth). In other words, syslogging a message should produce the same behavior as if the application had accessed each byte of the message, starting from the lowest virtual address and proceeding towards the highest virtual address.

The print statement in vm_syslog should be the only output send to cout by your pager code. However, the pager infrastructure generates a significant amount of additional output itself (detailing the current operation of the pager). You can disable the infrastructure output during testing by passing the -q flag when running the pager.

Deferring Work

There are many points in this project where you have some freedom over when many types of work occur, such as zero-fills, faults, and disk I/O operations. You must defer such work as far into the future as possible (or even better, avoid it entirely). Performing appropriate work deferral is part of the required project specification (not simply a best practice) and is necessary to pass many of the autograder tests. Deferring work to the maximum extent possible is one of the trickier parts of the project!

As a simple example of work deferral (or avoidance), if a page that is being evicted does not need to be written to disk, don't do so. However, make sure that you don't modify the page replacement algorithm (or any other aspect of the specification) in order to defer or avoid work.

There are cases where you might need to maintain extra state for the purpose of later deferring or avoiding work. Carefully look for these cases!

If you could possibly defer or avoid some action at a cost of performing a different action, keep in mind the relative costs of various operations. Triggering a fault (about 5 microseconds) is cheaper than zero-filling a page (about 30 microseconds), which in turn is much cheaper than a disk I/O operation (about 10,000 microseconds). For instance, if you have a choice between voluntarily incurring an extra fault or causing an extra disk I/O, you should take the extra fault.

Simulated Hardware Interface

This section describes how the external pager accesses simulated hardware (i.e. physical memory, disk, and the MMU).

Physical memory is structured as a contiguous collection of N physical pages (frames), numbered from 0 to N-1. The number of frames is configurable via the -m command-line argument when executing the pager (e.g. by running ./pager -m 8), and must be between 2 and 128 (inclusive). If the -m flag is not specified, the default number of frames is 4.

The disk is modeled as a single device that is a fixed number of "blocks" long, where each disk block is the same size as a physical memory page (i.e., each disk block can hold one page).

The pager controls the operation of the MMU by modifying the contents of the page tables and the page_table_base_register (PTBR) variable. Remember that although the MMU accesses the page tables automatically when accessing an address in the arena, it is your pager's responsibility to allocate and maintain the page tables appropriately.

The following portion of vm_pager.h describes the variables and utility functions for accessing the disk and physical memory (other than the page tables and PTBR, which were shown previously):

 * *********************************************
 * * Public interface for the disk abstraction *
 * *********************************************
 * Disk blocks are numbered from 0 to (disk_blocks-1), where disk_blocks
 * is the parameter passed to vm_init.

 * disk_read
 * Read the specified disk block into the specified physical memory page.
extern void disk_read(unsigned block, unsigned ppage);

 * disk_write
 * Write the contents of the specified physical memory page onto the specified
 * disk block.
extern void disk_write(unsigned block, unsigned ppage);

 * ********************************************************
 * * Public interface for the physical memory abstraction *
 * ********************************************************
 * Physical memory pages are numbered from 0 to (memory_pages-1), where
 * memory_pages is the parameter passed to vm_init.
 * The pager accesses the data in physical memory through the variable
 * pm_physmem, e.g. ((char*) pm_physmem)[5] is byte 5 in physical memory.
extern void* pm_physmem;

Note that unlike the page tables and PTBR, the pager is not responsible for initializing either the disk or physical memory. The pager is given the number of physical pages and disk blocks via vm_init, but it should not try to allocate actual disk blocks or physical memory space. Instead, it will work with disk_read, disk_write, and pm_physmem, which are already defined and initialized by the infrastructure. You should not make any assumptions about the starting contents of physical memory or the disk blocks.

Test Cases

As in the previous project, you will submit a suite of test cases that exercises your pager. Each test case for the pager will be a C++ program that uses the pager via the client interface defined in vm_app.h and should be run without any command-line arguments or input files.

The autograder will check your test cases against the built-in buggy pagers according to the pager (NOT the application) output. In other words, a test case exposes a buggy pager by causing the buggy pager to generate pager output that differs from the correct pager output. An application call to vm_syslog will directly correspond to a line of pager output (the message to be logged), but most output generated by the pager will come from the infrastructure printing various details about the active processes and page tables (which will be influenced by what the pager is doing).

To better illustrate the above point, consider the following scenarios:

To be clear, there's nothing wrong with printing whatever you want in your test cases, so long as you're clear on what is and is not considered by the autograder. Only the pager-side output (which, outside of vm_syslog, is generated entirely by the infrastructure) is considered.

The filename of each test case must be of the form, where N is a number representing the number of physical memory pages to create. For example, you might name a test case, indicating that the test is to be run using 4 frames. Remember that the minimum number of frames is 2 and the maximum number is 128.

Your test suite may contain up to 20 test cases. Each test case may cause a correct pager to generate at most 256 KB of output and must take less than 60 seconds to run (these limits are much larger than needed). You will submit your suite of test cases together with your pager to the autograder.

You should test your pager with both single and multiple application processes running. However, your submitted test suite need only consist of single process tests; none of the bugs in the pagers used to evaluate your test suite require multi-process applications to be exposed.

Advice & Implementation Tips

General and specific advice on tackling the project is provided below.

Finite State Machines (yes, you're actually going to use these)

One of the first things you should do is to write down a state-based flowchart (aka finite state machine) for the life of a virtual page, from creation via vm_extend to destruction via vm_destroy. Represent each virtual page as a series of bits (i.e., the relevant metadata of a page), where each state in the flowchart represents a specific setting of bits. In order to do this, you will need to decide what bits you need to represent each state. Some of these bits are straightforward (e.g., the page protection bits and the reference and dirty bits), but other bits may be needed as well. Ask yourself what events can happen to a page at each stage of its lifecycle (one example: the application reads the page) and what effect each such event will have on its state bits. As you design the state machine, try to identify all of the places where work can be deferred or avoided. Your first impression may be that this exercise seems unnecessarily theoretical, but the correctness of your program will ultimately depend on correctly designing and following the state machine. I am happy to offer feedback on state machine designs!

One concrete and practical use of your state machine is to help in designing your test suite. For example, you can trace through different transition paths that a page can take through the state machine, then write a short test case that causes a page to take each path.


One of the key mental hurdles in this project is understanding the full role of vm_fault within the pager. Remember that the MMU calls vm_fault based on the page protection bits only, and doesn't have any explicit information on (for example) whether a page is resident or not. Clearly, you should set things up so that any access to a non-resident page triggers a fault, but this is not the only case in which faults should occur. Faulting is how your pager takes control from an active process and is given an opportunity to update internal state, even if doing so does not involve bringing a non-resident page into physical memory. Thus, for any scenario where you need your pager to update any state or perform any bookkeeping (even if accessing a resident page), you will need to ensure that faults occur as needed by setting read_enable and/or write_enable appropriately.

As a simple illustrative example, if you have a page with a reference bit of 0, then the next time the page is accessed, you will want to flip its reference bit to 1. The only way through which this update can occur is via a fault, even if the page is a resident page.

Implementation Plan

When you are ready to begin coding, you should start with the basic pager functions that are automatically called by the infrastructure for any new process; namely, vm_init, vm_create, and vm_switch (there's also vm_destroy, but that's not initially essential). You will need to set up your core data structures within these functions.

Once these basic functions are working, move onto vm_extend and vm_fault, which will allow an application to actually make use of the arena memory.

Although it may seem counterintuitive, you should probably avoid tackling vm_syslog until the rest of the pager is mostly working. You can pass several of the autograder tests without having implemented syslog at all (remembering that most of the output used to check your pager comes from the infrastructure itself), and vm_syslog itself is nontrivial for a number of reasons that are probably not apparent at first.

Pager Functions

Specific tips on each of the pager functions are provided below:


For diagnostic printing in your pager, don't use cout, as any extra output sent to standard out (except for syslog) will cause your pager to fail test cases. Instead, print to standard error, i.e., using cerr instead of cout. This will allow you to leave active debugging statements when submitting to the autograder.

Liberal use of assertion statements is always a good idea to check for unexpected conditions generated by bugs.

Debugging in GDB is possible, but complicated somewhat by the impact of the infrastructure on the pager operation, particularly if the pager is missing basic functionality. Effective use of GDB may be initially limited until you've implemented the most basic parts of the pager (e.g., vm_init, vm_create and vm_switch).


As usual, starter code will be distributed via GitHub and each group will share one repository.

Write your pager code inside and write each test case in a separate file named, where N is the number of physical memory pages to use for that test (which must be between 2 and 128). The included is an example of a test case. The pager infrastructure is Linux-specific and will not run on any other system. Your program may use any functions in the standard C++ library, including (and especially) the STL. You should not use any libraries other than the standard C++ library.

The public functions in vm_pager.h are declared extern (meaning that code outside of the pager can call them), but all other functions and global variables in your pager should be declared static (meaning that code outside of the pager will not see them) to prevent naming conflicts.

Compiling and Running

Compiling and testing your pager requires your pager code in as well as an application program that uses the pager, such as Note that both the pager and the application program are separate executables, so they must be compiled and run separately.

You can compile the pager using the include Makefile by simply running make, which will output an executable named pager. The initial file contains a dummy implementation of each required pager function, so you can compile the pager immediately. Separately, to compile an application program (test case) named into an executable named mytest, you can run make mytest.N. You can substitute whatever test program name you wish (e.g., make sample.4 to build the sample test case).

To run your pager and an application, first start the pager process by running ./pager. Remember that you can suppress the regular infrastructure output by passing the -q flag and/or specify the number of physical memory pages via the -m flag (note that the number of disk blocks is non-configurable). While the pager is running, you can then run one or more application processes that will interact with the pager. For example, you could run ./sample.4 to run the sample test case and interact with the pager. The same user must run the pager and any applications that use the pager, and all processes must run on the same machine.

Sometimes the pager may get "stuck" and appear to still be running, but applications are unable to connect to it (in particular, this might happen if a buggy application previously crashed while using the pager). If an application appears unable to communicate with the pager, you may need to restart the pager process and try again.

Submitting to the Autograder

You can submit your program to the autograder as follows (the pager must be named, but the test programs can be named whatever you wish in the appropriate format):

submit3310 4 ...

To more easily submit all your source files at once ( and all of your test programs), you can use a shell wildcard, like so:

submit3310 4 *.cc

The nature of this project means that the autograder evaluation of your submission may take substantially longer than in past projects. Expect a finished and reasonably efficient pager to take roughly 10-15 minutes to complete the primary pager test suite (an incomplete, buggy, or inefficient pager may take more or less time). Evaluating your test cases will take additional time; given the number of buggy pagers, it is very possible for a submission that includes a large number of test cases to take 1-2 hours to complete testing. Be patient and plan accordingly! However, if you have not received autograder feedback within 4 hours of submission, there is likely something wrong and you should let me know.

Note that your submissions are timestamped according to when you submit them, not when the feedback email is generated (so if you submit at 11:55 pm, it will still count as that day's submission even if you don't get feedback until after midnight).


In addition to writing the program itself, you will also write a short paper (~3-5 pages) that describes your pager. Your paper should include the following:

  1. a brief introductory section providing an overview of the project
  2. a design section that describes your major design choices and the data structures you used, focusing particularly on work deferral in the pager (if a figure makes your explanation more clear, use one!)
  3. an implementation section that overviews the structure of your code (at a reasonably high level - should not duplicate your code)
  4. an evaluation section that describes how you tested your pager
  5. a conclusion that summarizes your project and reflects on the assignment in general

Remember that the writeup is a supplement (not a substitute) for the code itself. Strive to minimize any degree to which your writeup is 'code-like'. Instead, you should approach the writeup like a technical paper (providing clear, prose explanations that can be supplemented by reading the code). You need not try to include every technical detail in the writeup; focus on the major challenges and design decisions as you might explain them to another computer science student.

To submit your group's writeup, add your writeup to your Git repository as a PDF named writeup.pdf (by the writeup deadline). Remember to commit and push and after adding your file. Typesetting your writeup in LaTeX is encouraged but not required.


Your project will be graded on program correctness, design, and style, as well as the quality of your project writeup. Remember that the autograder will only check the correctness of your program (and nothing else)!

You can (and should) consult the Coding Design & Style Guide for tips on design and style issues. Please ask if you have any questions about what constitutes good program design and/or style that are not covered by the guide.