The goal of this assignment is to familiarize you with how programs are loaded, dynamically paged, and some of the mechanics of signal handling and memory mapping.
You are going to write a program that takes the file name of another program as an argument. Your program will load the named program into memory and let it execute.
Start by tracing the execve system call and describe in pseudo-code the steps that occur in creating the memory image of a new process. Focus on how the user-space memory is set up rather than how kernel data structures are modified. Pay attention to load_elf_binary
.
HintsYou probably should set up the stack for the program under test, since the kernel places data on the stack (like the ELF auxiliary vectors (AUXV)) that libc uses to initialize thread-local storage and other goodies. You should zero all register contents before starting the program under test. Libc checks rdx, and if it is non-zero it interprets it as a pointer during program shutdown.
To make your job easier, you can statically link your pager program. You should also statically link the program under test. (These are two separate programs). I'd suggest linking each into disjoint address regions, and I'd suggest placing your pager program into a region that is currently unused by system libraries. Documentation for gcc
and the loader will instruct you on how to statically link and how to change the placement of your code. Your pager does not need to dynamically relocate itself or the program under test, though supporting dynamic linking will surely get you extra credit. You must test for and gracefully exit in case your program under test wants to load itself on top of your pager program.
To start, have your pager map the entire program (with the proper permissions). Map an anonymous region for the program's bss. Write at least two test programs. Measure their execution time and memory use. Describe what functionality your programs test. What happens when the program under test calls malloc?
Now implement demand paging. To start, only map a single page of the executable under test. Set up signal handlers to catch segmentation violations and any other signal you need. In the signal handler, determine what address your executable is trying to access, and map only that page. Run the same tests on you demand paged loader as your all-at-once loader. Demonstrate at least one program that runs faster for the all-at-once loader, and one that uses less memory for the demand paging loader. Present your findings.
Consider the following program.
int
main() {
int *zero = NULL;
return *zero;
}
What does your demand pager do with this program? What should it do? Implement and describe a method for your demand pager to preserve memory access errors present in the program under test.
Now implement a hybrid loader that maps all text and initialized data at program startup, but maps all bss memory on demand. But for every fault, you may map 2 pages, the demand page and another page. Implement a prediction algorithm that choses the second page. Make whatever comparisons for whatever metrics you find most interesting. Find a workload that runs faster with your prediction algorithm than without and explain why. Also map 3 pages according to some heuristic and compare that to whatever is most illuminating.
Please report how much time you spent on the lab.