Interrupts
I'm still a bit unsure about what an interrupt is.
Okay, let's make it a little more concrete by looking at things from the CPU's perspective.
A CPU Without Interrupts
A simple CPU might execute instructions in a loop,
The behavior of the CPU is pretty simple: what it does next is determined by the instruction it just executed.
Adding Interrupts
Now, let's add interrupts to the mix. The CPU chip itself has a way for hardware to send it a notification that it needs attention, perhaps implemented via an “INT” pin on the chip itself (or possibly a more complex mechanism).
In more detail, when an interrupt occurs, if interrupts are enabled, the CPU
- Saves a little of its current state
- Enters kernel mode and disables interrupts
- Jumps to a special interrupt-handler routine
- Saves more of its state (via code in the interrupt handler)
- Switches stacks to a kernel stack
- Calls the appropriate interrupt-service routine
- Restores the state of the CPU
- Returns to the interrupted program, including returning to user mode and re-enabling interrupts
So that's what we saw when we read the OS/161 codebase? It ended up in some assembly code labeled
common_exceptionthat passed things along to the C code inmips_trap?
Yes, that's right. The
common_exceptioncode is the interrupt handler that the CPU jumps to when an interrupt occurs. It saves the state of the CPU and then calls the C code inmips_trapto handle the interrupt.
But how does the CPU know where to find
common_exception?
One approach is for the CPU to have a special register that holds the address of the interrupt handler. When an interrupt occurs, the CPU jumps to the address in that register.
In some CPUs, the interrupt handler is stored in a fixed location in memory, so we have to arrange to put our handler in a very specific place (or use a stub that jumps to the real handler). In others, there are multiple interrupts, and an area of memory is reserved for a table of interrupt handlers, known as an interrupt vector table; this table might be in a fixed location or in a location specified by a special CPU register.
There are lots of ways that handling interrupts can work, but we don't really need to worry about the details for OS/161; the code is already set up for us.
Stealing the Interrupt Mechanism for Software
Once chips had a hardware interrupt pin, it only required a tiny change to allow software to fake an interrupt, which we call a software interrupt or trap. Technically, it's not interrupting the program at all, since the program itself is asking for the interrupt to occur, which is why the term trap is often preferred.
So when we call
syscallin OS/161, we're actually causing a software interrupt?
Yes, that's right. The
syscallinstruction is a special instruction that causes a trap. The CPU then jumps to the interrupt handler, ending up inmips_trapin OS/161.
But why? Why go to all this trouble?
The main reason is to allow the kernel to run in kernel mode, where it has full access to the hardware and can do things like manage memory and devices. It's important to have a very controlled way to enter kernel mode, so that user programs can't just enter kernel mode themselves and start messing with things.
Disabling Interrupts
It's entirely possible that while we're in the middle of handling an interrupt, another interrupt could occur. This situation could lead to a nesting of interrupts, which can be very difficult to manage. To avoid this issue, we can disable interrupts while we're handling an interrupt. In essence, the CPU has its fingers in its ears and is saying, “La la la, I can't hear you,” to the hardware. The hardware will keep screaming for attention, and when the CPU finally re-enables interrupts, it will notice the pending interrupts and handle the next one.
The Old Way: Disabling Interrupts in All Kernel Code
Back in the early days of operating systems, one way to keep the kernel simple was to disable interrupts whenever the kernel was running. In a basic system (that didn't have to deal with multiple cores), this was a reasonable approach. But even then it was a bit of a problem, because it meant that if the kernel needed to perform a long operation, the system could be unresponsive to hardware interrupts for a long time.
A Better Way: Disabling Interrupts for Just Enough Time
In modern operating systems, for maximum responsiveness, we try to disable interrupts for as short a time as possible and run much of the kernel code with interrupts enabled. While this approach gives us a more responsive system, it also makes the kernel code more complex, because it has to deal with the possibility of interrupts occurring during its execution.
Thus, whenever the kernel is updating any global data structures, it needs to be far more careful than it would be if it were running with interrupts disabled.
Hay! Doesn't
sys161allow there to be multiple cores in our simulated system?
Yes, that's true. We're running a single-core configuration, but it would be nice if we could allow more cores. But then things get even more complicated, because disabling interrupts on one core doesn't disable them on the other cores. So we have to be even more careful about how we handle shared data structures.
!!!
On the next page, we'll dive deeper into synchronization and how to handle updating global state under these conditions.
(When logged in, completion status appears here.)