Though the trace files must be generated on Linux, this assignment can be solved anywhere you can compile standard C++.
Each of the example programs requires a valgrind address trace to run. There's some instructions below about making your own, but here are some traces I made: tgz, zip. The one called a3_lslog.txt is a short trace from running the Unix file listing command ls, the others are small GUIs, a toy (xeyes) and a a calculator tool (xcalc). It is quite possible to make longer ones, but these already take a while for the program to process.
Download the code and make it compile, and download the traces. The executable runs from the command line, and it takes four or five arguments. The name of the trace file to process, the name of the process to use, the number of real memory pages, the number of bits in the page offset, and the frequency of running the maintenance operation. The maintenance operation parameter is only used by NRU, and refers tells how often to run the procedure to clear all the reference bits. The units are references, so if the parameter is given as 1000, NRU clears the referenced bits every 1000 memory references. The number of page offset bits is the number of bits in the rightmost part of the address when it is broken up for translation. For 4k pages, that is 12 bits. This is also the default value. After running, it prints statistics. For instance,
Study the existing code to see how the algorithms are coded. Each algorithm has its own class, derived from the abstract base class ReplacementAlg defined in replacement.h. The base class takes care of reading the file of memory references, maintaining the page table, and deciding if each reference causes a fault. If so, it calls the abstract virtual method fault which returns the number of the real page chosen to use to bring in the new page. This is where the replacement algorithm runs.
You will need to add two new classes, one for clock and one for aging, and you will need to modify the vm.cpp program to included your new class files, and to recognize their names when given on the command line. You should find code for these things already present, commented out.
For clock, you might want to start with the FIFO class and modify it. Clock is just FIFO with second chance, so modify the program to check the ref bit before evicting a page. If it's set, clear it and go on. As the hand moves through each page number, use the base class method get_PTE_by_frame to see what page (if any) is using that frame, and to access its referenced bit.
For the page aging class, your constructor will need to allocate an array of 8-bit ages per page. Implement the virtual method maint to update the ages. This is called periodically by the base class according to the maintenance frequency setting. Your method will scan through each real page number, and update each age, again using get_PTE_by_frame to get and clear the reference bit. You might look at the one in NRU for a starting point. Your fault method will then scan through the ages looking for the smallest one.
Page aging should make one important modification to the algorithm as described in the text: When you bring in a new page after a fault, initialize its age to 128 rather than waiting for it to be updated at the next maintenance scan. This will keep the page from being immediately selected again if the next fault occurs before the next regular age update.
For both algorithms, fill all unused pages before replacing pages in use. This may happen naturally, but make sure it does.
The trace files can be quite large, containing many millions of memory references. These small simulations can therefore take a while to finish. I have also noticed that aging is very sensitive to the frequency parameter, and usually doesn't do well when the number of pages is small. Clock seems to perform consistently well on these traces. Here are the results from a few of mine: