whereEPC
<-PC
Cause
<- exception/interrupt typePC
<- interrupt handler address
EPC
and Cause
are (something like) special registers in the machine,
the exception/interrupt type is an id that indicates what unusual event occurred,
and the interrupt handler address is determined in a way that is part of the specification
of the architecture. (For instance, it might be defined to be 0xC0000000,
or any other specific address.)
The net effect is that control passes to the handler, which can use Cause
to
determine what happened and EPC
to identify the instruction that was executing when
the exception occurred. In all normal circumstances, the handler is part of the OS, which
carefully set things up during boot so that that handler routine would be at the right location.
EPC
. An interrupt is an asychronous event - it has nothing to
do with the instruction
that was executing at the time.
Here are some examples.
Example Exceptions
breakpoint
instruction
syscall
instruction
For exceptions, the OS looks for a registered handler. On a syscall
it's the OS itself.
For others, it depends -- could be a debugger. If no suitable handler is found, the
"process" (running appplication) is killed.
For interrupts... well, basically the OS handles whatever it is they require. (Interrupts allow us to get away from polled IO - putting the CPU into a busy loop checking the status of the IO device and transferring bytes between it and memory. The (more efficient) alternative is direct memory access, DMA - the IO hardware is given an address to transfer to/from, and a read or write command. Then the CPU goes about its business. The device interrupts when it's done.)
Saving the registers is a bit of a trick - you need to use registers to get at memory, so how do you save them without changing any? In MIPS the convention is that two registers ($k0 and $k1) are reserved for kernel use - from the application's point of view, the values in those registers may change at any time. Those two are enough to manage to save all the rest.
Handling an interrupt can be tricky. What if another one happens when we're already in the handler? For that reason there is some mechanism to enable and disable (or mask) interrupts. In the simplest case, as part of raising the interrupt the hardware also does the following:
disable all interruptsThe OS will re-enable them as part of returning to the interrupted program.
In more realistic cases, there is a hardware mask register that indicates which interrupts can be delivered at the moment - the OS may be willing to service some right now, but others will have to wait. Interrupts that are waiting (for the mask to be set to re-enable them) are called pending.
What if more than one interrupt/exception occurs during a single cycle? There is a (fixed) prioritization - the highest priority one is deliverd, and the others are pending.
There is a general issue having to do with the instruction that was executing when the interrupt occurred. For example, if the instruction were
addi $t0, $t0, 1
EPC
have completed, while the one at EPC
has not. (This issue is more pointed in CISC's, which may have individual instructions that do
things like copying one in-memory string to another. What if an addressing exception occurs
after 1000 characters have been copied?)
One aspect of this is memory protection. In the simplest case, we introduce two
new registers - base
and length
.
Before dispatching a process (handing it the CPU), the OS sets these two registers to
describe the portion of memory the process can legally access.
The running program is allowed to access memory between
base
and base
+length
.
We do this by adding base
to the PC and to each effective address the program calculates.
If the address (before adding base
) is greater than length
, an
exception is raised.
There's a problem, though. What keeps an application from resetting base
and length
, so that it can get at whatever it wants?
The answer is that we need to protect setting those registers. In the cheesy approach we'll
take in Cebollita, the registers are in memory mapped locations that lie outside the range
addressable when any program is executing.
In less cheesy situations, there are special
instructions (like, say, mtc0
in MIPS) required to set those registers,
and execution of those instructions is privileged - if an application tries to
execute a privileged instruction an exception is raised.
But, in either case, how can the OS itself achieve the desired effect? This brings us to the final piece of the puzzle. The hardware has yet another register giving the current privilege level. In addition to the things mentioned above, when an exception occurs the hardware performs
Privilege
<- 1
This disables use of the base
and length
registers, and makes execution
of privileged instructions legal. Before returning to the application, the OS sets Privilege
back to 0.
base
and length
is an archaic mechanism for enforcing memory protection.
(We'll be using it in the next assignment, though.) Memory protection is actually enforced by
virtual memory, which we'll get to in a while.
The other mechanisms described here are correct in idea/spirit, but have been implemented in a simple form in Cebollita; real machines will likely have somewhat more complicated implementations. (As an example of what that means, Cebollita has only one bit of privilege information, so supports only "priviliged" and "unprivileged" execution modes. A real processor might have several bits, and support several distinct levels of privilege, each with different rights.)