Each assignment indicates how difficult it is:
Easy: less than an hour. These exercise are typically often warm-up exercises for subsequent exercises.
Moderate: 1-2 hours.
Hard: Hard labs are optional. You are not required to do them. More than 2 hours. Often these exercises don't require much code, but the code is tricky to get right.
In general, the problem solutions for our labs do not require lots of code (tens to a few hundred lines), but some of the code is conceptually complicated and often details matter a lot. Additionally, the manifestation of a bug in operating system code can be bewildering and may require much thought and careful debugging to understand and fix. For this reason, adhere to the following lab commandments.
C Pointers - Make sure you understand C and pointers. I've seen students confused by C pointers, where they somewhat randomly insert * and &. Take your time and learn how C pointers work because they are used throughout the Xv6 code. The C Programming Language (second edition) by Kernighan and Ritchie is a succinct description of C. Some useful pointer exercises are pointers.c (here). Unless you are already thoroughly versed in C, do not skip or skim the pointer exercises above. If you do not really understand pointers in C, you will suffer untold pain and misery in the labs, and then eventually come to understand them the hard way.
A few pointer common idioms are in particular worth remembering:
int *p = (int*)100
, then
(int)p + 1
and (int)(p + 1)
are different numbers: the first is 101
but
the second is 104
.
When adding an integer to a pointer, as in the second case,
the integer is implicitly multiplied by the size of the object
the pointer points to.p[i]
is defined to be the same as *(p+i)
,
referring to the i'th object in the memory pointed to by p.
The above rule for addition helps this definition work
when the objects are larger than one byte.&p[i]
is the same as (p+i)
, yielding
the address of the i'th object in the memory pointed to by p.Although most C programs never need to cast between pointers and integers, operating systems frequently do. Whenever you see an addition involving a memory address, ask yourself whether it is an integer addition or pointer addition and make sure the value being added is appropriately multiplied or not.
Git Checkpoints - If you have an exercise partially working, checkpoint your progress by committing your code. If you break something later, you can then roll back to your checkpoint and go forward in smaller steps. To learn more about Git, take a look at the Git user's manual, or, you may find this CS-oriented overview of Git useful.
Lab Test Failures - If you fail a test, make sure you understand why your code fails the test. Insert print statements until you understand what is going on.
Print Statements - You may find that your print statements may produce much output that you would like to search through; one way to do that is to run make qemu inside of script (run man script on your machine), which logs all console output to a file, which you can then search. Don't forget to exit script.
GDB - In many cases, print statements will be sufficient, but sometimes being able to single step through some assembly code or inspecting the variables on the stack is helpful. To use gdb with xv6, run make make qemu-gdb in one window, run gdb-multiarch (or riscv64-linux-gnu-gdb) in another window, set a break point, followed by followed by 'c' (continue), and xv6 will run until it hits the breakpoint. See GDB - the GNU Debugger for helpful tips on using GDB with our Xv6 labs. (If you start gdb and see a warning of the form 'warning: File ".../.gdbinit" auto-loading has been declined', edit ~/.gdbinit to add "add-auto-load-safe-path...", as suggested by the warning.)
Assembly Code - If you want to see what the assembly is that the compiler generates for the kernel or to find out what the instruction is at a particular kernel address, see the file kernel.asm, which the Makefile produces when it compiles the kernel. The Makefile also produces .asm for all user programs. For example, the user program ls.c has the assembly file ls.asm.
Kernel Panics - If the kernel panics, it will print an error message listing the value of the program counter when it crashed.
$ addr2line -e kernel/kernel pc-value $ man addr2line
(gdb) b panic // set breakpoint br> (gdb) c // continue Xv6 br> When the kernel hits the break point, (gdb) bt // generate a backtrace.See GDB - the GNU Debugger for more information on backtrace.
Kernel Hangs - If your kernel hangs (e.g., due to a deadlock) or cannot execute further (e.g., due to a page fault when executing a kernel instruction), you can use gdb to find out where it is hanging. Restart with Xv6 in one window and gdb in another.
(gdb) c // continue Xv6 br> When the kernel appears to hang hit Ctrl-C in the qemu-gdb window and enter (gdb) bt // generate a backtrace.See GDB - the GNU Debugger for more information on backtrace.
qemu Monitor - qemu has a "monitor" that lets you query the state of the emulated machine. You can get at it by typing control-a c (the "c" is for console). A particularly useful monitor command is info mem to print the page table. You may need to use the cpu command to select which core info mem looks at, or you could start qemu with make CPUS=1 qemu to cause there to be just one core.
bash
established as default, and it will be what you learned in CPSC 225. On my MacOs, I use the Z-shell, which is simiilar to bash
. Both bash
and Z-shell have tab completion, which I like.
/bin
directory.
bash
), enter the following: pwd: enter-your-pw
bash
shell aliases allow you to create shorthand versions of freqently used commands.
Some users prefer shortened versions of the ls
commands such as
alias ll='ls -alF' alias la='ls -A' alias l='ls -CF'Some users collect their aliases in a file ~/.bash_aliases. For this to work, the users place the following conditional in their ~/.bashrc file.
if [ -f ~/.bash_aliases ]; then . ~/.bash_aliases fiThere are several ~/. files. Two of them are ~/.bash_profile and ~/.bashrc
[[ -r ~/.bashrc ]] && . ~/.bashrcYou ~/.bashrc file may contain something like the following, which allows you to place your unique aliases in the file ~/.bash_aliases.
if [ -f ~/.bash_aliases ]; then . ~/.bash_aliases fi
alias gccv='riscv64-linux-gnu-gcc'With this I can create some RISC-V assembly from a .c file as
$ gccv -S test.c
emacs
and nano
. However, I think using vim
is your best alternative. My first code editor was a punched card. I wrote code using punched cards from 1974 until 1981. After punched cards, I used a keyboard with scrolling paper to edit code. You could list lines, add lines, and edit lines. In 1983, I wrote code on a VT100 connected to a VAX, using the EDT
editor. Sometime in the late 1980's or early 1990's, I began using vim
. I initially selected emacs
, but it was not available on all computers so I selected vim
. Once you get used to vim
, you can edit source code efficiently.
There are many vim tutorials. OpenVim provides this Interactive Vim Tutorial.
.vimrc
.vimrc
is read by vim
to apply your specific features. The following is my .vimrc
thoughts, followed by the contents of my .vimrc
file.
Viewing line numbers as you edit are sometimes nice. The set number
and set nonumber
manipulate line numbers.
I do not like tabs in my code so I map tabs to 4 spaces.
Incremental search in text editors displays real-time search results as you type search strings.
Highlight search highlights text as you type search strings.
I map Ctl-l to list all of the files in a vertical split window. You can move to a file in the window, hit enter, and the file is opened in a new tab.
My .vimrc
file.
" custom vim settings go into this file set number set tabstop=4 set shiftwidth=4 set expandtab set incsearch set hlsearch syntax on set mouse=a let g:netrw_banner = 0 let g:netrw_liststyle = 0 " Values for netrw_browse_split " 1 open file in new horizontal split " 2 open file in new vertical split " 3 open file in new tab " 4 open in previous window let g:netrw_browse_split = 3 let g:netrw_altv = 1 let g:netrw_winsize = 15 let g:netrw_list_hide = '^\.\=/\=$,.DS_Store,.git,*\.o,\.*\.swp,.h' let g:netrw_hide = 1 " Uncomment the following commands to have Vexp called on Vim startup "" augroup ProjectDrawer "" autocmd! "" autocmd VimEnter * :Vexplore "" augroup END map <c-l> :Vex<CR>
When editing source code for a program that is in multiple .c
files, you want to bounce from one .c
file to another discovering the definitions of functions and data. The ctags
program creates a tags
file, which allows you to begin editing in one file and easily jump to another file that contains the definitions of functions and data. There are various ways to create a tags
file, but the simplist is to place source code in a directoy and run the ctags
program. The Xv6 program has two primary directories of source code. The kernel
directory contains to OS kernel source code. The user
directory contains the utility program source code. You can create a tags
file in both directories. For example, to create a tags
file in the kernel
directory.
$ cd kernel
$ tags *.c *.h
When you use vim
to edit a file in the kernel
directory, vim
reads the tags
file. Now you can use Ctl-] and Ctl-t to bounce from the current file being shown to another file containing the definition of the function or data. Suppose I am editing file.c
, and my cursor is on a line calling the function acquire
as mimiced below.
struct file* filedup(struct file *f) {acquire
(&ftable.lock);
Pressing Ctl-] opens the file spinlock.c
and positions the cursor on the function acquire
. When you are finished examining the function acquire
, you return to file.c
by pressing Ctl-t. Both file.c
and spinlock.c
remain a buffers in your vim editing session.
The tags
file may have multiple entries for a tag, and Ctl-] jumps to the first match. You can view additional matches by pressing g] or g Ctl-], which show all matches and allow you to select a destination. Other options are for you to enter :tselect or :tjump, which also show all matches and allow you to select a destination.