ECE344 - Operating Systems

Assignment 5: Pumbaa Operating System Kernel

Due Date: Sunday, March 25, 2000, 11:59:59pm.

Objectives

The objective of this assignment is to more accurately emulate the operation of an operating system kernel, where the kernel and user-level code run in different address spaces.


Procedure

In assignment 4 you implemented a user-level thread package. In this assignment, you are to separate the functionality that implemented the Pumbaa threads into a user-level library portion and a kernel portion. Each will run in separate Unix processes, and a message-passing protocol will be used to communicate between the two.


Specification

In assignment 4, you were encouraged to split function calls into a upper-level (i.e. "U_") routine and a lower-level (i.e. "L_") routine. In assignment 5, this split is necessary. The high-level part (modified slightly) will go into a user-level library, and the low-level part (modified slightly) will go into the kernel. The two parts will thus reside in different address spaces and share no data and cannot call each others' routines directly. One restriction on the kernel-side functions is that they cannot allocate any memory dynamically with malloc().

The two parts are to communicate via message passing. For this purpose, a message structure is defined in msg.h:

   struct Msg {
      int opcode;
      int args[4];
   };
Such a message can be sent from the user-level library code to the kernel to request for service (i.e. a kernel call). Also, the kernel can respond with a message to return the result. On requesting service from the kernel, the opcode is used to identify which service is required, and the arguments may contain any other data required for the service. For example, when requesting that a new thread be created, the message might be initialized as follows:
   msg->opcode = THREAD_CREATE;
   msg->args[0] = pc;
   msg->args[1] = sp;
   msg->args[2] = priority;
Since you are writing this code, you can use whatever protocol you find appropriate as long as you do not change the message structure.


Code provided to you

A number of routines will be provided to you in binary form here. For the user-level library: This code is made available in trap.o, and should be linked with pumbaa.c, which contains all other user-level library code, and usertest.c that contains the test code and main(). The only modifications needed for the U_-routines are that each must allocate a message structure and initialize it appropriately. Then, instead of calling the corresponding L_ function directly, it is called indirectly by calling the trap() function.

For the kernel, the following routines are provided and you must use them as is:

This code can be found in kernel.o. Of course, you will never call main(), and all of the system calls we have implemented so far can be implemented without requiring the use of getmem() and putmem(), so you can view their use as optional, allowing you to add additional, more sophisticated system calls at a later date.


Code you must provide

In a file ksupport.c, you are to provide for the following kernel support functions: You should also write all the L_-level routines that were required for assignment 4, such as L_CreateThread(), L_Yield(), etc... Note that the L_-level routines no longer need to get and store the PC and SP values, as that is now done by the trap handling code and the values are passed to handleTrap().


Keeping the abstraction levels separate

Note that after you have implemented this all, there are five different levels of code: It is important to separate each level into different files. Also, note that Level 4 and 3b execute in the kernel address space, while level 1, 2 and 3a execute in the application address space (and if you had multiple application spaces, in each application space independently).


Interaction between the Layers

The interaction between user program and kernel then occurs as follows. When the user program wishes to issue a CreateThread() kernel call, it calls the appropriate library routine, U_CreateThread. This library routine sets up a message and passes it to the kernel via a trap. When the kernel gets the trap, it calls handleTrap(), which stores away the PC and SP of the user thread that issued the call, interprets the message passed in through the registers, and calls the appropriate kernel routine that is capable of servicing the request, in this case L_CreateThread(). L_CreateThread() does what it has to do, and may possibly change Active, and then returns. At that point, the kernel returns from the trap, passing back the response message back if there is one.


The Idle Process

There is no longer an "idle thread" in the application address space. Instead, you should not return from handleTrap() if there is no thread ready to run. While you are debugging, it might be useful to have a loop like this:
   while( nothing ready to run ) {
      PrintInfoOnEachExistingThread();
      sleep(1);
}

Running the Kernel

The trap function being provided to you does not really trap into the kernel; it just emulates that type of functionality. It does this by sending messages back and forth between two Unix processes. (Do "man msgctl" and "man msgsnd".) Because of this, the kernel and application must be run in a certain way. Call your kernel executable kernel and your application executable appl.

There are two ways in which you can start the two processes.

  1. Have the kernel start up the application automatically by typing "kernel appl". That is, the name of the application executable is given as an argument to kernel. (This functionality is already implemented in the main() routine of the kernel, which is provided to you. It uses the fork() and exec() Unix system calls.)
  2. You can start up each program separately (say in different windows). kernel will then print out a queue id, which you must then give as input to appl when it asks for it.

Testing

The same applications that were used to test assignment 4 can be used here. Also, feel free to implement some of the basic synchronization experiments of assignment 2. Finally, try determining the overhead (in terms of time) of a context switch and a kernel call. Beware: These can easily be quiz/exam-type questions.


Submission

In your home directory, create a directory called ece344; create a subdirectory called as5 and then in that directory create files with your code. You should only submit source files. To submit the assignment you should use the submission procedure described in the policies section.

This directory should contain a copy of the supplied files trap.o and kernel.o. It should also contain your source files pumbaa.c, ksupport.c and any other source or header files you require for your project. You should also have a sample application usertest.c. Finally, it must contain a Makefile that will correctly compile and link two executables: appl (comprised of usertest.c, pumbaa.c, trap.o and any other support files) and kernel (comprised of kernel.o, ksupport.c, and any other support files necessary).

Note: As part of the evaluation of your program, a new usertest.c file will be used in place of the supplied file. This file will interface with your thread library using the same conventions as Assignment #4.