Thrashing the interrupt code
SMP changed all that, of course. The cli() call remained, however, for the few places that really needed it - and to avoid having to change a great deal of code which relied on cli() for mutual exclusion. The cli() call became global, in that it disabled the handling of interrupts on all processors in the system. Note that it did not disable the interrupts themselves, just the processing of those interrupts. This was accomplished by way of the "big IRQ lock" (global_irq_lock); once cli() was called, any processor attempting to handle an interrupt would spin on that lock until things were released with sti(). Needless to say, spending a lot of time with interrupts globally disabled in this way is not good for performance; thus the use of cli() and sti() has been discouraged for a long time.
As of 2.5.28, these functions are no longer discouraged - they are gone. Ingo Molnar sent out a patch (since revised an unbelievable number of times) which removes the global_irq_lock, the cli() and sti() primitives, and more. The result is the removal of a bunch of old legacy code, a faster IRQ handling subsystem, and a great many broken drivers. Said drivers are being fixed, but building Linux kernels for SMP systems could be a bit challenging for the next release or two.
This patch also merges three different counters that the kernel used to maintain:
- The hard IRQ counter (__local_irq_count), which tracked the number of hardware interrupts currently being serviced by each processor;
- The soft IRQ counter (__local_bh_count), which tracked software interrupts (bottom halves, tasklets, etc.); and
- The preemption counter (preempt_count, in the task structure) which noted whether the process had been preempted in kernel space.
The soft IRQ and preemption counters could also be used to disable software IRQs and kernel preemption by setting them to a nonzero value. The two IRQ counters, taken together, indicate whether the processor is currently responding to an interrupt. In other words, all of these counters are related to each other - they describe what kind of code is running at the moment and what sorts of diversions the processor is allowed to take. So, with Ingo's patch, all three have been merged into the per-process preemption counter. This change results in some simplified code; it should be mostly transparent to the rest of the kernel.
The cli() change is not transparent, though. People maintaining
or writing drivers will now need to bear in mind that there is no longer
any way to globally disable interrupts. You can still disable interrupts
for the current processor (with local_irq_save() and friends), but
other processors will still accept and handle interrupts. The only really
safe way of protecting resources is most situations is with
spin_lock_irq(); a number of drivers will need to be (finally)
converted over to real locking before they will work again. Ingo has
included a document (cli-sti-removal.txt) in
the kernel source to help driver maintainers who are wondering how to
handle this change.