System Architecture

(INFT12-212 and 72-212)
Lab Notes for Week 5: Exceptions and Exception Handlers

1  Introduction

This week we are looking at the mechanism used on the MIPS CPU to deal with unexpected events that divert the CPU away from its usual flow of execution.

2  Unexpected Events

As a programmer, you expect your programs to start at main(), go downwards instruction by instruction, following branches and jumps as required, until the program completes.
In fact, most CPUs provide a mechanism to deal with events that are not part of the normal program operation, and which divert the CPU away from its usual flow of execution. They can be classified into three groups.
  1. A system call is where a program requests the operating system to perform some action on its behalf. A specific instruction is issued: on the MIPS this is the syscall instruction; the generic name for the instruction is the TRAP instruction. The CPU jumps to some location unknown to the program where the machine code exists to deal with the request.
    The operating system generally has greater privileges than the program, e.g. to access disk devices directly. If we gave the same access to ordinary programs, people would be able to read the files of other users. We call the two modes of operation user mode for the unprivileged program's mode, and kernel mode for the operating system.
    The code that deals with a system call is known as a system call handler. Eventually, the handler performs the request and returns the CPU back to the program, leaving kernel mode and returning to user mode.
  2. If the running program performs some illegal operation, e.g. a divide by zero, then the CPU issues an exception. Again, the CPU switches to kernel mode, and is diverted to some location unknown to the program where the machine code exists to deal with the exception.
    Unlike system calls, which are predictable because they require a specific instruction, exceptions can occur at any time in a program's execution. For example, div $t0, $t1, $t2 may or may not cause an exception, it depends on the value in $t2.
    The exception will occur during the instruction's execution. The exception handler's job is to decide if the problem is recoverable or not. If the problem cannot be recovered, the program is terminated without the CPU ever returning back to the program.
    If the exception can be recovered, the handler takes the steps required to do this, and then returns the CPU back to the culprit instruction, so that it can be performed again. In this situation, the program will have no idea that anything went wrong.
    As an example of a recoverable error, let's say a program has been allocated 1Mbyte of stack memory, but it is running a recursive function which has used nearly all of the stack. On the next function call, the $sp is decremented and falls below the allocated memory. The sw $ra, 20($sp) instruction will try to write to memory which doesn't exist, and this will cause an exception. The exception handler, seeing the $sp just below the stack memory, can allocate some more memory to the stack, and allow the program to restart.
    As with system call handlers, exception handlers are placed in the operating system and run in kernel mode.
  3. Finally, a peripheral device can send an interrupt in to the CPU, indicating that the device needs some attention immediately. For example, a network packet might have arrived on the network interface, which has only enough memory to store one packet. The CPU needs to copy the packet somewhere else before the next one arrives, or the first packet will be lost.
    Again, the CPU switches to kernel mode, and is diverted to some location unknown to the program where the machine code exists to deal with the interrupt. Like exceptions, interrupts are unpredictable, and are dealt with by the operating system.

3  Task 1: Example Exceptions

We have already seen the use of the syscall instruction, so we will skip system calls and move on to exceptions. For each of the following programs, load them into MARS, read the program, determine what type of exception it will cause, and then single-step the programs to observe which instruction causes the exception. Down below (the Cause register) is a list of event types that the MIPS CPU recognises. For each program above, determine the MIPS name for the type of exception caused.

4  Dealing with Unexpected Events

Before a system call, exception or interrupt, the CPU is executing in user mode with all of its registers intact. Across function calls, registers are saved by caller or callee, but that's the reponsibility of the program writer.
Once a system call, exception or interrupt has occurred, the CPU starts executing instructions at a completely new location: it's like an enforced jump. Even worse, the registers all have their values as expected by the program; if we believe we are going to return back to the program, all registers have to be preserved before the handler does anything, and restored just before the handler returns back to the program.
On the MIPS CPU, for all types of events the CPU jumps to the address 0x80000080 which is in kernel memory, and the CPU switches to kernel mode. In kernel mode, the CPU has access to several coprocessor registers which records the information about the event: In MARS, the Coproc 0 tab shows the values of these registers:
Figs/coproc0.png

4.1  The Status Register

Figs/statusreg.png
The fields in the Status register are shown in the above diagram from Hennessy and Patterson, which says:
The interrupt mask field contains a bit for each of the six hardware and two software interrupt levels. A mask bit that is 1 allows interrupts at that level to interrupt the processor. A mask bit that is 0 disables interrupts at that level. When an interrupt arrives, it sets its interrupt pending bit in the Cause register, even if the mask bit is disabled. When an interrupt is pending, it will interrupt the processor when its mask bit is subsequently enabled. The user mode bit is 0 if the processor is running in kernel mode and 1 if it is running in user mode. The exception level bit is normally 0, but is set to 1 after an exception occurs. When this bit is 1, interrupts are disabled and the EPC is not updated if another exception occurs. This bit prevents an exception handler from being disturbed by an interrupt or exception, but it should be reset when the handler finishes. If the interrupt enable bit is 1, interrupts are allowed. If it is 0, they are disabled.

4.2  The Cause Register

Figs/causereg.png
The fields in the Cause register are shown in the above diagram from Hennessy and Patterson, which says:
The interrupt pending bits become 1 when an interrupt is raised at a given hardware or software level. The exception code register describes the cause of an exception through the following codes:
Number Name Description
0 INT External interrupt
4 ADDRL Address error exception (load or instruction fetch)
5 ADDRS Address error exception (store)
6 IBUS Bus error (misalignment) on instruction fetch
7 DBUS Bus error on data load or store
8 SYSCALL System call
9 BKPT Breakpoint exception
10 RI Reservesd instruction exception
12 OVF Arithmetic overflow exception

5  An Example Exception Handler

Writing a syscall, exception or interrupt handler is no easy job. Things that have to be done are: We are going to look at the sample exception handling code for SPIM, which simply prints out a text description of the event that occurred, and restarts the program after doing nothing else, even if the event was something terminal like a divide by zero!

Download this file, but for now do NOT load it into the MARS simulator: There are a lot of comments here, but I'll give a quick overview here.

5.1  Comments on This Code

While I don't fully understand all of this code, there are some issues which need to be addressed:
  1. Saving the initial registers at a fixed location is not good; they should be saved on the stack or somewhere specific to the running program. If an exception or syscall is interrupted, then the interrupt code will re-save the register (with different values) back in the fixed locations, thus destroying the registers which came from before the interrupt or syscall.
  2. The code increments the old PC so as to skip the offending instruction. This might be OK for exceptions, but it is dead wrong for dealing with syscalls and interrupts. In next week's lab, we will see an interrupt handler where the PC is not incremented before the eret.

5.2  Task 2: Setting the Exception Handler in MARS

We now want to see the above exception handler code in action, so we need to do two things:
  1. Set up the above code to handle exceptions in MARS, and
  2. Run some programs which will cause exceptions.
In MARS, go to Settings in the drop-down menus.
Figs/mars_exception_handler.png
Set the Initialise Program Counter to global `main' tickbox. Now click on the Exception Handler item. A new dialog box will appear.
Figs/mars_ehandler2.png
Select the Include tickbox, and browse to find the exception handler file that you downloaded above. Choose OK to set it. You have now set MARS up to have a new exception handler instead of the built-in one.

5.3  Task 3

Re-run all of the programs from Task 1 that caused exceptions. This time when you run them, MARS will load the above exception handler code. When the exception occurs, the CPU will jump to the code in the above handler, which will print out a description of the exception, and then return back to the program.
For each program:

5.4  Task 4

Go back into the MARS menu and disable the use of the exception handler, as we won't be using it for future programs, and you really DO want your program to crash when an exception occurs!

6  Task 5: Polling I/O

Unfortunately, writing a decent example of interrupt-driven I/O for MARS is tricky, and then I'd have to explain it to you! Instead, here is a program which polls a UART-like device, and echoes input from a simulated keyboard back out to a simulated screen: Load it up into MARS and read the short amount of code. The program polls a keyboard control register until the register indicates that there is a character ready to read. Then the program reads it.
Similarly on output, the program polls a display control register until the register indicates that a new character can be accepted. When this is true, the program puts the character into the display data register, which has the effect of putting the character on the display.
Assemble the program, but before you run it, turn on Tools -> Keyboard and Display Simulator. This opens up a new window:
Figs/kybrdsim.png
Click on Connect to MIPS before you run the program.
When the program is running, type characters into the white text area at the bottom of the above window, and you should see the characters echoed back to you in the display text area at the top of the window.
If things don't seem to be working, click on Reset to set the two devices back to their initial settings.
Something else to try is to run the program at a slower speed e.g. 15 instructions/second. See if all the characters that you type make it out to the display. If not, try and explain why not.

7  Outlook for the Next Lab

In the next lab, we will look at system call handlers and context switching on the MIPS CPU.


File translated from TEX by TTH, version 3.85.
On 25 Nov 2011, 11:15.