CPSC 331 | Operating Systems | Fall 2005 |
Additional material will be added as it becomes relevant. |
Some updates have been made in the Nachos directory structure, and the "Setup" and "Finding Your Way Around" sections have been updated accordingly. |
Nachos is found in the course directory /classes/f05/cs331/. The subdirectory nachos contains the Nachos source code and should be copied to your own directory before starting the first Nachos assignment. The bin, lib, and decstation-ultrix subdirectories contain utility programs necessary for creating user programs to run under Nachos. Do not copy this directory.
Since Nachos projects are group projects, how you manage your working directories is up to you - but you'll probably want to have one directory where the most recently working version of the project is stored. For this, choose one team member's account to host the Nachos code. To grant the other team member access to the directory, do:
fs setacl dirname username rwldi
where "dirname" is replaced by the name of the Nachos directory (e.g. ~/cs331/nachos) and "username" is replaced by the username of the other team member. rwldi is the set of permissions being granted: read, write, list, delete, insert (i.e. create new files).
Check that all of the Nachos subdirectories have the correct permissions with
fs listacl dirname username rwldi
and update them as needed.
With multiple people working with the same set of files, the chance of accidental deletions goes up. Be careful, and make backups often!
The Nachos package in the nachos directory has several main subdirectories: build, code, userprog, patches, and precompiled. Some of the directories may not be present in the initial distribution but will be available as they are needed later in the semester.
The build directory is where all of the compiled object files and the Nachos executable reside. In the initial distribution, you'll find a Makefile. Look at the comments in the Makefile or see below ("Compiling and Running Nachos") for information on how to compile Nachos.
The Nachos source code is found in the code directory, which is organized into several subdirectories:
You may add to and modify files in any of these directories except the machine directory - developers can't usually make changes to the hardware, so you can't either!
The userprog directory contains sample user programs, along with a Makefile for compiling them.
The patches directory contains diff files needed to "upgrade" the provided code for various projects, and the precompiled directory contains compiled solutions for earlier projects so that you can work on later projects without having working code.
As you sift through the Nachos source code, the grep and find commands can be very useful for finding files, function calls, method implementations, or other things which you know the name of but can't locate. (In particular, method implementations aren't always where you might expect - e.g. Machine::Run() is in mipssim.cc even though the Machine class is defined in machine.h.) See the "Useful Commands" section below or look them up in the Linux manual pages.
The instructions for compiling Nachos can be found in the Makefile in the nachos/build directory. They are summarized below. Since Nachos consists of many files, you will be using make to automate the compilation process.
Compilation always takes place in the build directory - all of the compiled object files and the final nachos executable will end up there, not in the source code directories.
You may get the following compiler warning:
../code/threads/synch.cc: In member function `bool Lock::IsHeldByCurrentThread()': ../code/threads/synch.cc:196: warning: control reaches end of non-void function
It is safe to ignore this for now - it should go away once you complete the implementation of the Lock class in the first Nachos assignment.
Try running Nachos: nachos -u will print out some information about Nachos' commandline arguments, or you can run nachos -T 1 to run the built-in thread test - you should see output about threads 0 and 1 looping.
If you have only changed C++ code in existing Nachos files, then
If you have added any new files (.cc or .h files) or added or deleted #includes in any of the existing files, then
The make depend step is important for updating dependencies if you have done something that might cause them to change. It is always safe to do make depend even if you have only changed existing C++ code; it just shortens the compilation process if you only do it when necessary.
Important Note: If make is behaving strangely or you run Nachos and it doesn't seem like the changes you just made have taken effect, do the following:
One of the many nice features of emacs is that you can compile within the editor, and then can be taken to the exact line where a compiler error occurred.
To compile:
Note that if you want to execute make depend or make clean, you'll need to switch to the build directory and run these from your terminal window (see above).
To locate and fix compiler errors:
If you want to really automate the process, you can set up emacs so that a single keypress (I use the insert key) will prompt you to save any unsaved files and will then invoke make. (No more confusion because you forgot to save something before compiling.) To do this, paste the following at the end of your ~/.emacs file and restart emacs:
;; define a function to save buffers and then compile (defun save-and-compile () "save and make" (interactive) (save-buffer) (compile "make") (other-window 1) (end-of-buffer ()) ; (goto-char (point-max)) (other-window 1) ) ;; bind save-and-compile to insert (global-set-key [kp0] 'save-and-compile) (global-set-key [insert] 'save-and-compile)
Then just make sure the current buffer displays a Nachos source code file, and press insert. Ask about making these changes if you've never fiddled with your ~/.emacs file (or don't know what one is), or if you want to use a key other than insert.
Writing a user program is mostly just a matter of writing a C program - Nachos can, in theory, run any C program. The wrinkles: the system calls and library functions supported by Nachos, the fact that you're working in C and not C++, and the limitations of cross-compiler used to compile the programs.
System calls and library functions:
User programs, since they run under Nachos, can only use the system calls
supported by Nachos. Furthermore, they cannot use many of the common Unix
libraries. Nachos' system calls are a pretty limited set,
and you might be surprised
at how many useful things are system calls or library routines. (Things like
printf and scanf are particularly frequently used.) A very primitive I/O
library is provided in userprog/iolib.h to make some basic I/O
operations like printing a string or integer or reading a line of text
easier. (You can look at the implementation in userprog/iolib.c to
see how they are implemented - they are just wrappers around the Read() and
Write() system calls.) However, it is still very annoying to write a very
complex user program. Feel free to expand the I/O library or build other
libraries to help with common tasks.
C, not C++
One of the biggest things is that C doesn't have classes. So, C doesn't have
a string class. (C-strings are arrays of characters.)
Cross-compiler limitations:
Not only are you limited to C, you are limited to a subset of C. Amongst the
things that you might want to do are such diverse elements as:
declaring variables in the middle of a function (nope, in Nachos user programs all variable declarations must take place before any other statements in the function body)
use // single line comments (nope, gotta use the /* ... */ commenting style)
overload functions (nope, give all your functions different names)
Compiling a Nachos user program is not done the same way as compiling Nachos itself (or any other program you might write). Two reasons:
The Nachos machine simulator simulates a MIPS-architecture machine, which is not what our PCs in the lab are. Thus, a cross-compiler is needed which can run on the PCs but produces machine code for a MIPS-architecture computer.
Nachos doesn't run exactly MIPS-compiled code - there's a slight difference in the format of the executables, and so the cross-compiler output has to be fixed up before Nachos can run the program.
The good news is that there is a Makefile in the userprog directory which handles all of this for you. You will just need to tell the Makefile about your program:
add the name of the executable you want to the PROGRAMS = line near the beginning of the Makefile (if you wrap to a new line, end all but the last line with \)
add a set of lines of the following format:
myprog.o: myprog.c iolib.h ../code/userprog/syscall.h $(CC) $(CFLAGS) -c myprog.c myprog: myprog.o start.o iolib.o $(LD) $(LDFLAGS) start.o iolib.o myprog.o -o myprog.coff $(COFF2NOFF) myprog.coff myprog
Here, myprog is the name of the executable that will be produced and myprog.c is the name of the source file. These lines provide a template for what you would need for your program - if it makes system calls and uses the I/O library in userprog/iolib.h, you can just copy this and replace every occurrence of "myprog" with the name of your source file/executable. If you don't use the I/O library, you can leave out all mentions of iolib.h and iolib.o.
Once you've modified the Makefile, use make to compile and see the section below for information on how to run your program. Note: for some reason, it doesn't work to compile the user programs on the Lansing lab machines (you'll get Internal compiler error: program cpp got fatal signal 11). However, if you ssh to math.hws.edu and compile there, it works. Please only use math.hws.edu for compiling user programs - do the rest of your work on a lab machine (or some other machine) so math.hws.edu doesn't get bogged down.
The first user program to start up is specified via the -x option to Nachos. Following the -x is the complete pathname to the executable to run. For example, if you are in your build directory (where the Nachos executable is) and want to run the userprog/halt program, use:
nachos -x ../userprog/halt
You can use any pathname to specify the location of the program you want Nachos to run, including absolute paths. This example assumes that your Nachos directories are set up in the standard way.
Nachos is capable of displaying some debugging information. This can be turned on by running Nachos with the -d option and specifying what categories of debugging messages you want. The choices (defined in lib/debug.h) are:
+ | turn on all debug messages |
t | threads |
s | synchronization (semaphores) |
i | interrupts |
m | machine emulation |
d | disk emulation |
f | file system |
a | address spaces |
c | system calls |
n | network emulation |
u | user-defined messages |
For example, to run with the builtin thread and interrupt debugging messages, use
nachos -d ti
You can print debugging messages using cout anywhere in the Nachos kernel code, but remember to remove all such output before handing in your program!
You can also use Nachos' builtin debugging message mechanism to print your debugging messages. The advantage to this approach is that you can run with and without the debugging output, just by specifying different commandline options. If you use this approach, you do not need to remove your debugging messages before handin. (It might be useful to have them in place later, for example.) To print a debugging message, use
DEBUG(<flag>,<what to print>);
For example,
DEBUG(dbgUser,"x is " << x);
would result in cout << "x is " << x occurring if the debug flag dbgUser was specified when Nachos started. Note that dbgUser is a constant defined in lib/debug.h; the corresponding flag that would be specified on the command line is 'u'.
You should use dbgSynch for messages in the Lock and Condition classes which would be useful in general debugging situations. (See the Semaphore class for an example of what sorts of messages this might include.) Use dbgUser for all other messages.
A mechanism has been set up for running kernel tests. To run a tester, start Nachos with the -T <#> option e.g.
nachos -T 1
will run test #1 (the thread tester). The basic tests are:
0 | library functions |
1 | threads |
2 | semaphores |
These tests aren't especially comprehensive, but they do test some basic functionality.
To add your own tester, you should do the following:
Create a free function implementing your test which takes no parameters and returns void. As is standard, the prototype for the tester function goes in the header file (threads/tester.h) and the implementation in implementation file (threads/tester.cc).
Add a line in the TesterSetup function body (in threads/tester.cc) which registers your new tester function with the kernel.
See the provided testers for examples.
grep searches files for occurrences of a given pattern. The basic usage is
grep <pattern> <file(s)>
where <pattern> replaced by the pattern you are looking for and <file(s)> is replaced by a list of filenames to search.
For example,
grep Run *
will search all files in the current directory (* is a wildcard which matches all files in the current directory) for the string "Run", and prints the matching lines along with the name of the matching file. grep is case-sensitive, so use
grep -i Run *
if you want to find all occurrences of Run, run, rUn, etc. You can specify several sets of files to search e.g.
grep Run machine.cc mipssim.cc ../lib/*.h
to search machine.cc, mipssim.cc, and all header files (ending with .h) in the directory specified by the relative path ../lib.
find searches for files or directories with a particular name. The basic usage is
find <dir> -name "<pattern>" -print
where <dir> is replaced by the name of the directory where the search begins and <pattern> is replaced by the pattern specifying the files/directories you are looking for.
For example,
find . -name "*.cc" -print
finds all files/directories whose names end with .cc, searching the current directory (.) and its subdirectories. To find all files/directories with names starting with s in ../lib and its subdirectories, use
find ../lib -name "s*" -print
If you want to find a particular file, there's no need for the wildcard in the pattern:
find . -name "machine.cc" -print
In emacs, you can search forward from the cursor position with C-s (control + s) and backward with C-r. After pressing C-s or C-r, begin to type the string you want to search for. (It will appear in the "minibuffer" at the bottom of the emacs window.) If you want to search for additional occurrences, press C-s or C-r more than once. To stop searching and have the cursor remain at the location where the thing was found, you can press an arrow key or C-a (the keyboard shortcut for putting the cursor at the beginning of the current line). To cancel the search and return the cursor to where it was when you started the search, press C-g (the universal quit-an-emacs-command command).