Exception Handling

The trouble with programmed I/O is that it both wastes CPU resources and it has potential for incorrect operation.

What we really want:

  1. (Since most I/O devices are slow), have I/O devices signal the CPU when they have a change in status. This would be more efficient than polling the devices at the devices' maximum operating speed.
  2. The I/O devices tell the processor that they become "ready."

In order to do this we need:

  1. Hardware (wires) from devices to the CPU.
  2. A way for special software to be invoked when the a device signals on the wire.

The modern solution bundles the software to deal with these signals (interrupts) and other situations into an exception handler. (Effectively part of the OS.)

Exceptions

There are 2 categories of exceptions: interrupts and traps. All processors use the same mechanism (a combination of hardware and software) to deal with exceptions.

Note: The Java and C++ languages have overloaded the term exception. Processors have used this term since the 1950s. OO languages developed many years later. For this class, the term exception does not refer to Java or C++ exceptions.

The 2 categories:

  1. interrupts

    examples:

    When should the interrupt be dealt with? Answer: as soon as conveniently possible, and before the condition that caused the interrupt might cause yet-another interrupt (losing the first!)

  2. traps examples:

    When should the trap be dealt with? Answer: right now! The user program cannot continue until whatever caused the trap is dealt with.

Exception Handling

The mechanism for dealing with exceptions is simple; its implementation can get complex. The implementation varies among architectures.

Situation: a user program (also called an application) is running (executing), and a device generates an interrupt request.

Mechanism to respond: the hardware temporarily suspends the user program, and instead runs code called an exception handler. After the handler is finished doing whatever it needs to, the hardware returns control to the user program. The user program continues from where it left off.

Limitations of exception handler:
Since it is being invoked (potentially) in the middle of a user program, the handler must take extra care not to change the state of the user program.

So, how can it do anything at all?
-- The key to this answer is that any portion of the processor state that the handler wishes to change must be saved before the change and restored before returning to the user program.
-- The handler often uses the system stack to temporarily save register values.

When to handle an interrupt -- 2 possiblilities:

  1. Right now! Note that this could be in the middle of an instruction. In order to do this, the hardware must be able to know where the instruction is in its execution and be able to "take up where it left off".

    This is very difficult to do, because the hardware is complex. But, it has been done in simpler forms on a few machines. Example: arbitrary memory to memory copy on IBM 360.

  2. Wait until the currently executing instruction finishes, then handle. THIS IS THE METHOD OF CHOICE. It handles interrupts inbetween 2 instructions.

    The instruction fetch/execute cycle must be expanded to

    1. If an interrupt is pending, handle it.
    2. instruction fetch
    3. PC update
    4. decode
    5. operand load
    6. operation
    7. store results

The MIPS R2000 exception handling mechanism

When an exception occurs, the hardware does the following things. Note that there is no inherent ordering of #1-4. They all happen "between" instructions, and before #5.

  1. processor sets state giving cause of exception

    within the Cause register -- coprocessor C0, register $13, a 32-bit register
    bits 6..2 (5 bits) specify the type of the exception, called the ExcCode.

    Here are some mappings of encodings to causes.
    Examples:

    	 00000  (0)  Interrupt
    	 00100  (4)  load from an illegal address
    	 01000  (8)  syscall instruction
    	 01100  (12) arithmetic overflow
          
  2. changes to kernel mode, saving the previous mode in a hardware stack (3 levels deep)

    The mode is in the Status register -- coprocessor C0, register 12, bit 1.

    user mode = 1
    kernel mode = 0

    defined in the processor's architecture are 2 modes,

  3. disable further interrupts

    bit 0 of the Status register the field is called IEc (Interrupt Enable, Current) determines whether interrupts are currently

    enabled = 1
    disabled = 0

    If interrupts are disabled, then the hardware is not checking to see if there are further interrupts to handle. Disabling makes sure that the handling of an interrupt is not interrupted.

  4. save current PC

    coprocessor C0, register 14, called the Exception Program Counter.

    Gives return address within user program. Where to return to when done handling the exception.

  5. jumps to hardwired address 0x8000 0080.
    This is where the exception handler code is.

Then, the instruction fetch and execute cycle starts up again, only now the code within the exception handler is being executed. This handler code does the following:

  1. save some registers (on system stack).

    The handler needs to use registers too! It may not change (clobber, overwrite) the register contents of the user program. So, it saves them (on stack or in memory).

  2. Figure out exception type. (in ExcCode)
    
         mfc0  $k0, $13        # get Cause register
         andi  $k0, $k0, 0x3c  # Mask out all but ExcCode
    
    
  3. use ExcCode in combination with a jump table to jump to the correct location within the exception handler.
  4. handle the exception (whatever it is!)
  5. restore registers saved in step 1.
  6. atomically: (as if done in 1 step, not 3)

See Useful diagrams for the MIPS R2000, as distributed in class!

The EPC (Exception Program Counter).

The Status Register.

The Cause Register.

some terms

about Jump Tables

A clever mechanism for implementing a switch statement. A jump to one of many locations.

Keep a table of addresses (case1, case2, and case3):


   JumpTable:  .word case0   # different syntax!
	       .word case1   # the address assigned to these labels
	       .word case2   #   becomes the contents of an array element
 
    
    sll  $8, $8, 2          # case number shifted left 2 bits
			    # (need a word offset into table, not byte)
    lw   $9, JumpTable($8)  # load address into $9
    jr   $9                 # jump to address contained in $9

    .
    .
    .

 case0:   #code for case0 here
    .
    .
    .
 case1:   #code for case1 here
    .
    .
    .
 case2:   #code for case2 here

Note that the cases do not have to go in any specific order.

not-yet-seen Addressing mode: label($rb)
Effective address is gotten by: label + ($rb)

label does not fit into 16 bit displacement field of load/store instruction. So, the MAL->TAL synthesis of this must be something like:


	 la  $1, label
	 add $1, $1, $rb

then use 0($1) as addressing mode in load/store instruction.

some advanced topics

Priorities

A problem: Multiple interrupt requests can arrive simultaneously.
Which one should get handled first?

Possible solutions:

What should get given the highest priority?
clock? power failure? thermal shutdown? arithmetic overflow? keyboard? I/O device ready?

Priorities are a matter of which is most urgent, and therefore cannot wait, and how long it takes to process the interrupt.

So, what ordering ought to be imposed?

Reentrant Exception Handlers

The best solution combines priorities with an exception handler that can itself be interrupted. There are many details to get right to make this possible.

Copyright © Karen Miller, 2006