You should read through and understand as much of the Pintos source code that you mean to modify before starting work on project. In a sense, this is why we have you write a design document; it should be obvious that you have a good understanding, at the very least at a high level, of files such as
userprog/process.c for the User Programs project. We see groups in office hours who are really struggling due to a conceptual misunderstanding that has informed the way they designed their implementations and thus has caused bugs when trying to actually implement them in code.
You should learn to use the advanced features of GDB. Often times, debugging your code usually takes longer than writing it. However, a good understanding of the code you are modifying can help you pinpoint where the error might be; hence, again, we strongly recommend you to read through and understand at least the files you will be modifying in this project (with the caveat that it is a large codebase, so don’t overwhelm yourself).
These projects are designed to be difficult and even push you to your limits as a systems programmer, so plan to be busy and have fun!
In the past, many groups divided each assignment into pieces. Then, each group member worked on their piece until just before the deadline, at which time the group reconvened to combine their code and submit. This is a bad idea. We do not recommend this approach. Groups that do this often find that two changes conflict with each other, requiring lots of last-minute debugging. Some groups who have done this have turned in code that did not even compile or boot, much less pass any tests.
Instead, we recommend integrating your team’s changes early and often, using git. This is less likely to produce surprises, because everyone can see everyone else’s code as it is written, instead of just when it is finished. These systems also make it possible to review changes and, when a change introduces a bug, revert back to working versions of code.
We also encourage you to program in pairs, or even as a group. Having multiple sets of eyes looking at the same code can help avoid subtle bugs that would’ve otherwise been very difficult to debug.
We encourage each group to have regular meetings (e.g. twice a week) to make sure everyone is on the same page. In-person meetings are generally more productive, since people tend to be more attentive.
If you’re meeting through a call (e.g. Zoom), we recommend you enable live transcription. Moreover, you should take detailed notes during each meeting on a central document to reference back to.
Compiler warnings are your friend! When compiling your code, we have configured GCC to emit a variety of helpful warnings when it detects suspicious or problematic conditions in your code (e.g. using the value of an uninitialized variable, comparing two values of different types, etc). When you run
make to compile your code, by default it echoes each command it is executing, which creates a huge amount of output that you usually don’t care about, obscuring compiler warnings. To hide this output and show only compiler warnings (in addition to anything else printed to standard error by the commands
make is running), you can run
make -s. The
-s flag tells
make to be silent instead of echoing every command. Do NOT pass the
-s flag when running
make check or you won’t see your test results! Only use the
-s flag when compiling your code.
If your code is buggy, the first thing you should do is check to see if the compiler is emitting any warnings. While sometimes warnings might be emitted for code that is perfectly fine, in general it’s best to remedy your code to fix warnings whenever you see them.
The page allocator in
threads/palloc.c and the block allocator in
threads/malloc.c clear all the bytes in memory to
0xcc at time of free. Thus, if you see an attempt to dereference a pointer like
0xcccccccc, or some other reference to
0xcc, there’s a good chance you’re trying to reuse a page that’s already been freed. Also, byte
0xcc is the cpu opcode for “invoke interrupt 3,” so if you see an error like
interrupt 0x03 (#bp breakpoint exception), then Pintos tried to execute code in a freed page or block.
An assertion failure on the expression
sec_no < d->capacity indicates that Pintos tried to access a file through an inode that has been closed and freed. Freeing an inode clears its starting sector number to
0xcccccccc, which is not a valid sector number for disks smaller than about 1.6 TB.
Depending on the machine you’re using, compiling the Pintos code may take a while to complete. You can speed this up by using make’s
-j flag to compile several files in parallel. For maximum effectiveness, the value provided for
-j (which specifies the number of “jobs”, ie. how many things to compile in parallel) should be equal to the number of (logical) CPUs on your machine. This can be found by running the
nproc command in your shell. You can combine all of this into one command by running
make -j $(nproc) instead of running just
make whenever you want to compile your code.
Please be warned however that you should only pass the
-j flag to
make when compiling your code. You should not use it when running your tests with
make check. While it is actually safe to do so for Project User Programs, this is not the case for other projects, so it’s best not to get into the habit of it.
You’ll often find yourself having to type in the same/similar long commands (e.g.
loadusersymbols). Instead of retyping these every time or copying and pasting, you can use reverse-i-search using
Ctrl-R. This will alow you to quickly search through your command history. If there are multiple matches, you can cycle through them by pressing
Within GDB, you can specifically shorten your commands which GDB will automatically match as long as there are no ambiguities. For instance, you can type
n instead of
next. Moreover, pressing enter without typing any command will repeat the command, which is useful for stepping through the code.
Rarely you may find yourself in a bizarre situation where the behavior of your kernel isn’t changing even though you’re certain you’ve changed your code in a way that should produce an obvious effect. If this occurs, you can destroy all compiled objects and caches, and restore your current terminal and shell parameters to sane values by running the following.
Once you’ve done the above, recompile your code and try whatever it was you were doing again.