CS460 Lab #4 (JOS #1)

  • Jim Plank, Rich Wolski

  • JOS -- An operating system for a simulated MIPS machine

    The labwork in the class is going to focus on writing JOS -- an operating system for a simulated MIPS machine. Obviously we don't have the machinery to let you write a real OS for a real machine, so we're doing the next best thing: we will be writing the operating system for a simulated machine.

    One of the problems with this project is that students often get confused concerning which parts of the code are the simulated machine, and which parts are the OS. Students will try to either change or circumnavigate the simulator to fix problems instead of accepting the simulator as a given and working around it in the OS. We will try to keep this from being a problem. To do so, you should try to adopt the following model. The simulator calls the code you write in exactly three situations: when the machine starts running (initialization time), when a user program running in the simulator causes an exception (system call, page fault, etc.) and when a device interrupts. Each time this happens, your code can see the user program's register set and main memory, as well as any data structures you have defined in your code. So your job is to

    And that's it. Nothing more. We'll talk about the best way to organize your code for these three cases as we go along but you should try to keep in mind what the simulator is doing and what the OS (you) is (are) doing.

    Basically, things will work as follows. There are two object files for the simulator:

    There is also a header file /home/cs560/labs/lab4/simulator.h which contains constants and simulator procedure definitions that you will need in your JOS code. The TA's lab 4 page will tell you where to find these files, and how to compile your executable.

    When you run the executable, your operating system gains control. This happens in a subroutine called JOS() (no arguments), that you have to write. This state of affairs is not so different from an actual operating system. If you were implementing JOS for an actual machine, you'd need to define an initial entry point for the hardware to "jump through" at start-up. In fact, the "boot up" process is when the hardware loads and executes a pre-defined routine that it finds in a place the hardware specifies (e.g. the boot record). You will link your code to the two object files, and an executable file will be created. When you run the executable, it starts the simulator. Upon instantiation, the simulator calls JOS() and your operating system gains control. From then on, the interaction between the operating system and the simulator is acheived through well defined communication points. The simulator program will call you when the program requires service (which it tells you through an exception) or when a device requires service (through an interrupt). You, however, do not return once called. Instead, you have two ways to return to the simulator after you are finished doing that thing you do. You can call run_user_code() if you want to run (or go back to running) a user program or noop() if you want to "idle" the machine. All exceptions call exceptionHandler() passing in an exception type as an argument, and interruptHandler()) which gets an interrupt type as an argument.

    Basically, a computer is a collection of resources (memory, CPU, screen, keyboard, etc) that interact in a specific way. It is the job of the operating system to manage these resources in such a way that enables the user make use of the resources with paradigms that are familiar to him/her (like writing programs and executing them). Typically, each resource exports a low-level hardware interface that consists of control registers, data transfer locations, and a set of interrupt response codes. The resources that the simulator provides are as follows:


    User Programs for JOS

    User programs for JOS must be compiled in such a way that the simulator may run them. This is done using a special C compiler. You may use the application programs that are in the directory /home/cs560/test_execs, which are compiled into executable files in the same directory.

    Note that the protection of these executables is not r-x. That is because these are not executable on our machines. They are only executable by the simulator of JOS -- in other words, you can only execute them by loading them into JOS.


    Loading User Programs in JOS

    The basic steps for loading a user program in JOS is: When you start initializing the stack, you need to follow a few rules. The stack will grow toward lower memory addresses. Thus, when you want to initialize the stack with certain values (like argv and envp), you should put those values in memory locations at the top end of the user's memory, and then set StackReg to be just below these memory locations. As a convention, the user program will look for argc in main_memory[StackReg+12], argv in main_memory[StackReg+16], and envp in main_memory[StackReg+20]. It will not make use of any memory values greater than main_memory[StackReg] for allocating call frames on the stack.

    There is a quirk of the simulator that causes bizarre behavior if you use the top 8 bytes of memory. Thus, do not use them. When you start stuffing stuff into the stack, do not stuff anything into these eight bytes.


    Alignment

    As with all computers, the MIPs simulator assumes that all 4-byte quantities like integers and pointers are aligned. This will be an issue when you are setting up argc and argv.

    The Assignment

    Look at /home/cs560/labs/lab4/start/jos.c. This is a rudimentary JOS implementation that loads the file a.out into user memory and executes it. The a.out program must be very restricted -- the only external procedure that it may call is _exit(). It does not have to call _exit() however, because _exit() is called automatically whenever main() returns.

    Copy /home/cs560/labs/lab4/start/* into your own area, and compile it so that you can run it. Test it on some a.out files that you copy from the test_execs directory (try halt and cpu).

    Now, you are to make the following changes to jos.c:

    1. Implement restricted versions of the system calls read() and write(). The syntax of read() and write() are exactly like Unix's read() and write(). Read their man pages if you are not familiar with read() and write() already. Now, implement read() so that it reads from the console whenever the first argument is zero, and implement write() so that it writes to the console whenever the first argument is one or two. Any other first argument should return with an error. The way that the system call driver is written in the simulator is that if you return a negative value to a system call, it will return -1 to the user program, and set the errno value to be the returned value times -1. This is how you can return different errors from system calls. See /usr/include/sys/errno.h for standard errno's.

      Remember that if the user specifies an address of x, that is not address x in JOS. It is the user's address x.

      An important fact of OS design is that users can do incorrect things, but the operating system can't. Thus, JOS should be ready for any arguments to read() and write(), and if the arguments are incorrect, it must deal with that gracefully (i.e. return with an error) and not abort or dump core or leave JOS in such a state that future system calls will break.

    2. Change JOS further so that it will call a given file with a given set of arguments. These can be compiled into the program, but in an easy-to-change place (i.e. as global variables in jos.c). Now, not only should JOS load the file and execute it, but it should set up memory so that the program sees the arguments as argc and argv.
    You do not need to hand this assignment in two different parts --- simply hand in a new version of JOS with all of these changes implemented.

    Structuring your code

    You are going to want to use the kthreads library to structure your code. There are two files whose purpose is to help you with this:

    The code for step 5 of WHAT_DR_PLANK_DID is in the directory /home/cs560/labs/lab4/Step5. When you get to step 5, it will be a good idea to look at my code, and perhaps copy it and start from there. (More priceless advice from Professor Wolski: ``For those of you tempted to begin your enjoyment of Lab #4 by redefining the counting numbers to start with 5 and simply starting here, realize that you may be asked to expound upon steps 1 through 4 at some future social gathering. It would be gauche not to be prepared, would it not?'')


    Appendix -- Various Things


    Simulator Subtleties

    The simulator is built so that when you do something incorrect it will quit. However, some of the error messages will not be very helpful. For example, you are not allowed to return from an interrupt or exception and let the simulator pick up where it left off. An error message will be printed if you try to do so. Instead of returning, you must call run_user_code() or noop(). The former will cause the machine to switch to user mode, load the registers, and begin executing code. The noop() function will put the machine into idle mode and wait for an interrupt to occur. Another example of something else you are not allowed to do is write a character to the console while it is already trying to write one out (that is before the interrupt occurs). For example the following code will not work:
            console_write('H');
            console_write('i');
    
    It will stop the program with the following message:
            Assertion failed: line 107, file "machine/console.c"
            Abort
    
    Hopefully the file name will clue you in on where you made an error.

    The end of the user's address space

    Do not use the last 8 bytes of the user's address space. Start the stack at least 8 bytes from the limit. I don't know why -- just do it.

    Debug Flags

    There are various flags that you can use when invoking jos. You can list them out by executing jos help. The only one of these that might be of any help is the -d e option. This causes a debug message to be printed out when an exception or interrupt occurs.

    Miscellaneous

    Don't copy over any of the executables or object files. Rather just keep soft links to them. In case bugs are found, they can be fixed and you will always be referencing the latest versions of the code.