Getting down to basics: Running Linux on a 32-/64-bit RISC architecture - Part 2Let me tell you a story. Well, in fact, let me tell you several stories/threads illustrating how the MIPS hardware provides the low-level features that prop up the Linux kernel -
Story/thread #1 -The life and times of an interrupt: What happens when some piece of hardware signals the CPU that it needs attention? Interrupts can cause chaos when an interrupt-scheduled piece of code interrupts another code halfway through some larger operation, so that leads on to a section on threads, critical regions, and atomicity.
Story/thread #2 - What happens on a systemcall: After a userland application program calls a kernel subroutine, what happens and how is that kept secure?
- How addresses get translated in Linux/MIPS: A fairly long story of
virtual memory and how the MIPS hardware serves the Linux memory map.
(The first two threads are covered in this part in this series and the third covered next in Part 4. Part 1, if you remember, was a high level view of GNU/Linux and how it operates on the MIPS 32k/64k architecture.)
Thread #1: The Life and Times of an
It all starts when something happens in the real world: Maybe you tapped a key on the keyboard. The device controller hardware picks up the data and activates an interrupt signal.
That wends its way - probably via various pieces of hardware outside the CPU, which are all different and not interesting right now - until it shows up as the activation of one of the CPU's interrupt signals, most often one of Int0-5*.
The CPU hardware polls those inputs on every clock cycle. The Linux kernel sometimes disables interrupts, but not often; most often, the CPU is receptive. The CPU responds by taking an interrupt exception the next time it's ready to fetch an instruction.
It's sometimes difficult to get across just how fast this happens. A 500-MHz MIPS CPU presented with an interrupt will fetch the first instruction of the exception handler (if in cache) within 3 to 4 clocks: That's 8 nanoseconds.
Seasoned low-level programmers know that you can't depend on an 8-ns interrupt latency in any system, so we'll discuss below what gets in the way. As the CPU fetches instructions from the exception entry point, it also flips on the exception state bit SR(EXL), which will make it insensitive to further interrupts and puts it in kernel-privilege mode. It will go to the general exception entry point, at 0x8000.0180.1
(Of course, it can't be that simple; some MIPS CPUs nowprovide a dedicated interrupt-exception entry point, and some will even go to a different entry point according to which interrupt signal was asserted. But we'll keep it simple - many OSs still do.)
The general exception handler inspects the Cause register and in particular the Cause(ExcCode) field: That has a zero value, showing that this was an interrupt exception. The Cause(IP7-2) field shows which interrupt input lines are active, and SR(IM7-2) shows which of these are currently sensitized.
There ought to be at least one active, enabled interrupt; the software calculates a number corresponding to the number of the active interrupt input that will become Linux's irq number.
(In many cases, the exception handler may consult system-dependent external registers to refine the information about the interrupt number.)
Before calling out into something more like generic code, the main exception handler saves all the integer register values - they belong to the thread that was just interrupted and they will need to be restored before that thread is allowed to continue, somewhere in the system's future.
(Some MIPS CPUs can avoid this overhead for very low level interrupt handlers using shadow Registers).
The values are saved on the kernel stack of the thread that was running when the interrupt happened, and the stack pointer is set accordingly. Some key CP0 register values are saved too; those include SR, EPC, and Cause.
With the SR(EXL) bit set, we're not really ready to call routines in the main kernel. In this state we can't take another exception, so we have to be careful to steer clear of any mapped addresses, for example.
(This is not quite inevitable. The MIPS architecture has some carefully designed corners that make it possible to nest exceptions in carefully controlled conditions. But Linux doesn't use them.)
Now that we have saved registers and established a stack we can change SR, clearing SR(EXL) but also clearing SR(IE) to avoid taking a second interrupt - after all, the interrupt signal we responded to is still active.
Now we're ready to call out to do IRQ(). It's a machine-dependent routine but written in C, with a fairly well standardized flow. do IRQ() is passed a structure full of register values as a parameter. Its job is to selectively disable this interrupt in particular and acknowledge any interrupt management hardware that needs a confirmation that the interrupt has been serviced.
Assuming there is a device ISR (interrupt service routine) registered on this irq number, do IRQ() calls on to handle IRQ event(). It's undesirable to run for long with all interrupts disabled; some other device out there may need fast response to its interrupt.