Appendix B

Test Programs

1. Introduction

In this Appendix, there is an explanation of the use of Registers and Arguments. There is also a description of the various hacks which are necessary because we're really running on a single threaded machine; this has a profound affect on the way user programs are run. Finally, there's a description of the various test programs.

2. Use of Registers and Arguments

The hardware has arguments Z502_REG_ARG_n where "n" is 1 to 5. These arguments are used by the system call macros as a way of communicating between the user program and the operating system. To see how these work, look at the macro definitions of the system calls in the include files. In general, these arguments should not be explicitly used in a user/test program; they will be destroyed whenever a system call is executed. The arguments are visible within the operating system however, where you should be able to use the values there to interpret what the test program has conveyed in the system call. Essentially invisible to the test program is a global variable SYS_CALL_CALL_TYPE; this is similar to the arguments in that it conveys the system call number between the test program and the hardware; don't mess with it - it's of no interest to you, really.

The hardware has registers Z502_REG_m, where "m" is 1 to 9. These are general purpose registers which can be used for any purpose within the test program; they will be saved across a context switch and are a means of assuring that your program is reentrant and multitasking. All other variables you may define in "C" may not make it across a context switch. This is because you are actually LEAVING the test routine with each system call; the system call is doing a "return". When the routine is next entered, any variables which were automatic will be regenerated on the stack - the previous instance will be lost. If you declare the variables as static, then their value will remain upon each invocation of the routine; however, other processes, executing the same routine, will muck with the static values you're trying to maintain for a particular process. SO - use the registers and everything will be fine. It should be pointed out, that the programs assume that the registers are all 0 the first time the program is entered; the hardware guarantees this to be the case.

There are other useful registers. One of these is the program counter, Z502_REG_PROGRAM_COUNTER the use of which is described in the next section.

3. Program Attributes

Because we're trying to run a multiprocess system on a uniprocess machine, some latitude has to be made for the format of user processes. Specifically, the programs have the following format:

switch( Z502_REG_PROGRAM_COUNTER )
    {
    case n: Z502_REG_PROGRAM_COUNTER++;
            /* some action, culminating in a
               system call.                    */
            break;
    case n+1: .
              .
              .
    case final: TERMINATE_PROCESS( );
    }


The program counter is stored/retrieved every time the "process" is run; thus it can be used as a pointer to which instruction should be executed next; that's just what a program counter should do. The system call macro has a "return" embedded in it; thus the program is exited with each call. Before the system call, therefore, it's important to update the program counter to aim at the next case statement to be executed.

Several C macros have been used in test.c to implement the above format. The goal of these macros is to make the code look cleaner. These macros are defined in "syscalls.h" where you should look for more details.

Strange things happen if you simply "return" from the program without the benefit of a system call. You may be able to write a handler to catch this instance; it's essentially the same as an illegal system call.

3.1 Portability

In an attempt to make this code more portable, several type definitions have been made.

  1. INT32 is typedef'd as "long"; in fact, wherever there was a doubt, items were then declared as INT32. Problems occur if you use "int" since this means different things on different machines.
  2. Z502_ARG is a type which is a union of a long and a pointer to a void. This allows argument to take on either type and makes "lint" much happier. See the two OS include files and base.c for examples of how this is used.

4. User Test Programs

4.1 test1a

Exercises the system calls GET_TIME_OF_DAY, SLEEP, and TERMINATE_PROCESS. It does the exercise in a very "gentle" fashion and is fine for a starter program.

4.2 test1b

Tries to break CREATE_PROCESS. Tries all kinds of illegal and quasi-legal inputs to the system call. Then does the same thing to GET_PROCESS_ID.

4.3 test1c

Creates multiple instances of test1a. It's an excellent test to see if your multiprocess system is working. This test should determine if FIFO scheduling is working - do this by creating all the processes with the same priority.

4.4 test1d

Creates multiple instances of test1a. It's an excellent test to see if your multiprocess system is working. This test should determine if PRIORITY scheduling is working - do this by creating all the processes with different priorities.

4.5 test1e

SUSPEND_PROCESS and RESUME_PROCESS with errors. There are lots of ways of demonstrating that your Operating System can handle typical errors which might be generated by the system calls.

4.6 test1f

Here SUSPEND_PROCESS and RESUME_PROCESS operate successfully. This will be demonstrated by how they effect process scheduling.

4.7 test1g

Use CHANGE_PRIORITY with many errors generated.

4.8 test1h

Use CHANGE_PRIORITY successfully. Again, the real effect will be visible by recording changes in priority scheduling.

4.9 test1i

Test SEND_MESSAGE and RECEIVE_MESSAGE by generating errors.

4.10 test1j

Test SEND_MESSAGE, RECEIVE_MESSAGE and successfully have two processes talking back and forth to each other.

4.11 test1k

Tests to show that illegal system call number, and PRIVILEGED_INSTRUCTION_FAULT are correctly handled.

4.12 test2a

Exercises simple memory reads and writes. Watch out, it expects that what it reads from memory is the same thing that it wrote.

4.13 test2b

Exercises not-so-simple memory reads and writes. You'll find that some of the addresses are tricky - the program will finally terminate because an illegal address is generated; this assumes that you have some kind of error handler and that illegal addresses result in program termination.

4.14 test2c

This program allows a simple way to work through the disk system calls. Your operating system will implement these calls in much the same way you did the timer queues; except now there will be one queue for each disk. The test simply writes a number of disk requests and then reads them back to make sure the data got stored properly.

4.15 test2d

This program runs several instances of test2c. It's really designed as a scheduler test - to ensure that you correctly get the scheduler working when there are a number of disk requests outstanding. The philosophy here should be the same as when there were a number of processes alternately sleeping and processing.

4.16 test2e

This program simply walks through virtual memory, touching every few pages. This should exercise your page handling adequately.

4.17 test2f

This routine again walks through memory, but touches each page several times. It is designed to provide a health exercise of page faults.

4.18 test2g

Runs multiple copies of test2gx. These tests each open the same shared memory and then use that shared memory to send information back and forth to each other.