If a very severe interrupt occurs, say a divide by zero, this will quit the program. How is this done, is there a special instruction in the processor or is it a software routine? And after quitting the program, where will the CPU jump to?
There are traps, faults and aborts. Divide-by-zero is a fault which means that it is potentially fixable.
On x86, the CPU will save some registers on the stack (CS, EIP, EFLAGS, SS, ESP) and maybe an error code and then start the fitting Interrupt Service Routine. The adress of the ISR is found in the Interrupt Vector Table which is located in the first kilobyte of RAM and is setup by the OS. From here on the OS takes over. After the handling is done, the OS may make the CPU return to the program that caused the error.
CPUs don't have a notion of “program aborting”. Some CPUs have features that help operating system manage separate programs, but the concept of separate programs exists primarily at the level of the operating system.
The specification of the CPU specifies what happens for each instruction for all data. For example, it might specify that
div r1, r2, r3 divides the integer value stored in
r2 by the integer value stored in
r3, stores the result into
r1, and then moves the instruction pointer to the next instruction. It might furthermore specify that in case
r2 is zero, the CPU executes a trap — this is what most CPUs do on a division by zero. Typically, on a trap:
- The CPU saves the current value of the program counter into a register assigned to this purpose.
- The CPU sets the reason for the trap (a constant that means “division by zero”) into a register assigned to this purpose.
- The CPU reads an address from somewhere and sets the program counter to this address. This address is called the trap vector. This “somewhere” is usually either a register, or a memory location which is calculated from a value in a register.
- The CPU switches to a privileged mode (e.g. kernel mode, ring 0).
(Note that details vary from CPU to CPU, I am only describing typical behavior.) Traps are very similar to interrupts; the difference is that a trap is due to an event happening in the CPU whereas interrupts are triggered by external events.
The operating system decides what happens when a trap occurs by providing some code and setting the trap vector to its address. The trap handling code typically analyses the reason for the trap and decides what to do, which may or may not include terminating the current process. For example, some operating systems allow application code to react to trap (e.g. with signal handlers on Unix) — then the kernel's trap handler looks up the address of the application trap handler in some internal kernel data structure and jumps to that after setting the processor in the appropriate mode.
An example of a trap which often doesn't result in the death of the application is a memory fault, when the application tries to access a virtual address which is not mapped in the MMU. On operating systems that implement swap, the trap handler looks up the virtual address to see if there is actually data that is mapped there in the application but that is currently swapped out to disk. If that is the case, the trap handler loads the content of the page from disk and updates its memory management data structures. Then it jumps back to the application, arranging to cause the application to execute the memory access instruction again. This time the instruction will succeed, and the application is unaware that the first attempt fails (except via some indirect means such as timing).
If the trap handler decides to terminate the program, it does so in pretty much the same way as if the program had called the
exit system call: it frees the resources allocated to the process, then invokes the scheduler to execute whatever other threads have something to do.
A processor usually have several modes. Let's take a modern processor, with no legacy of 16-bits mode while being on a 64-bit processor (ie: get rid of x86). On an ARM Core, you've got the following modes:
Usually, programs run as User. When something happens on the hardware (incoming IP packet, for instance), you get an interrupt, and you go either on IRQ or FIQ (the difference between IRQ and FIQ doesn't matter here). The OS uses System and Supervisor modes (once again, we don't care about the difference between those two). Abort and Undefined are used for error handling.
What does an OS ? It manages resources and prepare User mode in order to run processes. This includes setting stacks, MMU mapping, and so on.
When a runs an undefined, CPU detects the error and goes in Undefined mode. OS set a routine on Undefined, it may checks for an handling mechanism (for instance, this may be a call to a co-processor, the OS may power it up, and then re-run the instruction). If there is no such mechanism, it marks the program as dead, and then goes to Supervisor mode. On supervisor mode, the OS code is run, it frees the dead task memory, loads an other task, and goes back on User mode on the newly loaded task.
Each mode has its own stack, memory, and place to run. OS are designed to protect their own memory from the User mode tasks (commonly named User land), if the hardware allows to do so.
And what if there is an issue in Undefined routine ? If there is an undefined instruction in the Undefined routine, the processor return to the first instruction of the Undefined routine, and there is an infinite loop. Bugs in the OS are painful.