Project 3
Project 3
Prof. Forsyth
1 Introduction
Read the entire document before starting. There are critical pieces of information and hints
along the way.
In this project, you will be implementing part of a virtual memory system simulator. You have been given a
simulator which is missing some critical parts. You will be responsible for implementing these parts. Detailed
instructions are in the files to guide you along the way. If you are having trouble, we strongly suggest
that you take the time to read about the material from the book and class notes.
Warning: In past semesters, this project has caused students a lot of trouble due to the sheer amount of
concepts to understand. It is important that you not just understand the concept of virtual memory, but also
have a good understanding of C and how to use GDB for debugging. So, start early. If you are struggling
with writing the code, then step back and review the concepts. Be sure to start early, ask Piazza questions,
and visit us in office hours for extra help!
This project is divided into 10 problems. The files that you will be modifying are the following:
• va_splitting.h - Break down a virtual address into its components.
• mmu.c - Initialize any system- and memory access-related bookkeeping.
• proc.c - Initialize any process-related bookkeeping.
• page_fault.c - Implement the page fault handler.
• page_replacement.c - Implement the frame eviction algorithms Clock Sweep and Approximate LRU.
• stats.c - Calculate the Average Memory Access Time (AMAT)
It will be a good idea to peek into following the files:
• mmu.h - Defines the structures used by mmu.c
• proc.h - Defines the structures used by proc.c
• pagesim.h - Defines simulation parameters as well as global structures.
• pagesim.c - Reads a trace file of memory operations and calls each operation’s corresponding function
implemented in proc.c.
• stats.h - Defines parameters that can be used when calculating AMAT.
• swap.c - Initializes functions to support a queue that are used in swapops.c
• swapops.c - Initializes functions to keep track of the frames swapped out to physical memory. Discussed
in section 8
• types.h - Defines different types that are used throughout the simulation
• util.c - Initializes a random function used for the random replacement algorithm
page frames for storing this data. You are responsible for placing and initializing these structures
in memory.
Note: Since user data page frames and operating system page frames such as the frame table and page
tables coexist in the same physical memory, we must have some way to differentiate between the two, and
keep user pages from replacing system pages.
For this project, we will take a simple approach: Every page frame has a “protected” bit in its frame table
entry, which is set to “1” for system frames and “0” for user frames. In other words, we’ll set the protected
bit to 1 for the frames holding paging system meta-data and 0 for the page frames holding user data pages.
3 Address Splitting
In most modern operating systems, user programs access memory using virtual addresses. The hardware
and the operating system work together to turn the virtual address into a physical address, which can then
be used to address into physical memory. The first step of this process is to translate the virtual address
into two parts: the higher order bits for the VPN, and the lower bits for the page offset.
Implement the vaddr_vpn and vaddr_offset functions in va_splitting.
These will be used to split a virtual address into its corresponding page number and page offset. You will
need to use the parameters for the virtual memory system defined in pagesim.h (PAGE_SIZE, MEM_SIZE,
etc.).
4 Initialization
When the simulation starts, it will need to first set up the frame table (sometimes known as a “reverse
lookup table”).
Implement the system_init function in mmu.c.
For simplicity, we always place the frame table in physical frame 0. You need to initialize the frame_table
pointer to the start of this frame. Don’t forget to mark the first entry (which corresponds to the frame
holding the frame table) of the frame table as “protected” because we will never evict the frame table.
Then, during your page replacement, you will need to make sure that you never choose a protected frame
as your victim.
After setting up the frame table, we will need to set up a page table at the start of each process.
Implement the proc_init() function in proc.c.
Since processes can start and stop any time during your computer’s lifetime, we must be a little more
sophisticated in choosing which frames to place their page tables. For now, we won’t worry about the
logistics of choosing a frame–just call the free_frame function you’ll write later in page_replacement.c.
Then make the appropriate flags for that frame table entry. (HINT: Do we ever want to evict the frame
containing the page table while the process is running?) After picking the frame for a process’s page table,
remember to update the ptbr in the process pcb struct.
You may add any global variables or helper functions you deem necessary.
Each frame contains PAGE_SIZE bytes of data, therefore to access the start of the i-th frame in memory, you
can use mem + (i * PAGE_SIZE).
pages). If the evicted frame is dirty, then you will need to swap_write() (discussed in section 8) it into the
swap space and clear the dirty bit.
8 Swap Space
If the evicted page is dirty, you will need to write its contents to disk. The area of the disk that stores
the evicted pages is called the swap space. Swap space effectively extends the main memory (RAM)
of your system. If physical memory is full, the operating system kicks some frames to the hard disk to
accommodate others. When the “swapped” frames are needed again, they are restored from the disk into
physical memory. Without the swapping mechanism, when the system runs out of RAM and we start evicting
physical frames, we lose the data stored in these frames, and the process whose pages were originally mapped
to the evicted frames loses its data forever. Therefore, upon selecting a victim, we need to make sure that
its data is swapped out to disk and restored when needed.
Recall that during your page fault handler, you must fill the newly provided frame with any data that was
previously written to this page that was evicted to the swap space. To do this, we have provided the method
swap_exists() that checks if the faulting page was swapped out to disk previously. If it has, then you need
to restore it, using swap_read() If the faulting page has not been swapped previously, then you need to
zero out the freed frame to prevent the current process from potentially reading the memory of some other
process.
To write the contents of a victim’s page to disk, we provide a method called swap_write(), where you can
pass in a pointer to the victim’s page table entry and a pointer to the frame in memory. This will simulate
swapping the page to disk.
9 Finishing a Process
If a process finishes, we don’t want it to hold onto any of the frames that it was using. We should unmap
any frames so that other processes can use them (remember not to unmap pages that are now being used
by other processes). Also: If the process is no longer executing, can we release the page table?
As part of cleaning up a process, you will need to also free any swap entries that have been mapped to pages.
You can use swap_free() to accomplish this.
Implement the function proc_cleanup() in proc.c.
Once you have implemented the Clock Sweep algorithm, you will be able to run the simulator with the
-rclocksweep argument to use the algorithm as your page replacement strategy.
Remember again that if the protected bit is set, it should never be chosen as a victim frame.
Hint: Remember what the bitwise operators do and how they are used.
Once you have implemented the Approximate LRU algorithm, you will be able to run the simulator with
the -rlru argument to use the algorithm as your page replacement strategy.
After writing your stats function in section 11, compare the performance of the three algorithms. What
do you observe?
Remember again that if the protected bit is set, it should never be chosen as a victim frame.
11 Computing AMAT
In the final section of this project, you will be computing some statistics.
• accesses - The total number of accesses to the memory system
• page faults - Accesses that resulted in a page fault
• writebacks - How many times you wrote to disk
• AMAT - The average memory access time of the memory system
We will give you some numbers that are necessary to calculate the AMAT:
• MEMORY ACCESS TIME - The time taken to access memory SET BY SIMULATOR
• DISK PAGE READ TIME - The time taken to read a page from the disk SET BY SIMULATOR
• DISK PAGE WRITE TIME - The time taken to write to disk SET BY SIMULATOR
You will need to implement the compute_stats() function in stats.c
Project 3 - Virtual Memory Computer Systems and Networks Fall 2024
Figure 3: This diagram gives a general overview of how the simulator works.
Once your Clock Sweep algorithm has been implemented, you can run the program with the -rclocksweep
argument in order to test. For example, you should run:
$ make
$ ./ vm - sim -i traces / < trace >. trace - rclocksweep
Once your LRU algorithm has been implemented, you can run the program with the -rlru argument in
order to test. For example, you should run:
Project 3 - Virtual Memory Computer Systems and Networks Fall 2024
$ make
$ ./ vm - sim -i traces / < trace >. trace - rlru
We highly recommend starting with “simple.trace.” This will allow you to test the core functionality of
your virtual memory simulator without worrying about context switches or write backs, as this trace contains
neither.
...or by using the actual function name being called from the main loop:
$ ( gdb ) break sim_cmd ! set breakpoint at call to sim_cmd
$ ( gdb ) r -i traces / < trace >. trace -r < algorithm >
! ( wait for breakpoint )
$ ( gdb ) s ! step into the function call
13.5.6 Backtracing
If your program crashes, you can use backtracing to see the call stack at the point of the crash. For example:
$ ( gdb ) backtrace
Project 3 - Virtual Memory Computer Systems and Networks Fall 2024
Feel free to ask about gdb and how to use it in office hours and on Piazza. Do not ask a TA
or post on Piazza about a segfault without first running your program through GDB.
The second half of the output file name includes the type of replacement algorithm that should be run
when comparing the output. Ex. astar-random.log should be compared with the output from using random
replacement algorithm (−rrandom) as shown below.
$ ./ vm - sim -i traces / astar - random . trace - rrandom > my_output . log
$ diff my_output . log outputs / astar - random . log
You MUST implement the Clock Sweep algorithm in order to test against all the *-clocksweep.log output
files.
NOTE: To get full credit you must completely match the TA generated outputs for each trace.
14 How to Submit
Run make submit to automatically package your project for submission. Submit the resulting tar.gz zip on
Canvas.
Always re-download your assignment from Canvas after submitting to ensure that all necessary
files were properly uploaded. If what we download does not work, you will get a 0 regardless
of what is on your machine.
This project will be demoed. In order to receive full credit, you must sign up for a demo slot and
complete the demo. We will announce when demo times are released.
Project 3 - Virtual Memory Computer Systems and Networks Fall 2024
...or by using the actual function name being called from the main loop:
$ ( gdb ) break sim_cmd ! set breakpoint at call to sim_cmd
$ ( gdb ) r -i traces / < trace >. trace -r < algorithm >
! ( wait for breakpoint )
$ ( gdb ) s ! step into the function call
15.0.6 Backtracing
If your program crashes, you can use backtracing to see the call stack at the point of the crash. For example:
$ ( gdb ) backtrace
Remember: Always run your program through GDB before asking for help with a segfault.
These are just a few basic GDB commands to get you started. Feel free to explore more commands and
consult GDB documentation for advanced debugging techniques.