Global Interrupt Disabling

Some critical kernel functions can execute on a CPU only if no interrupt handler or deferrable function is running on any other CPU. This synchronization requirement is satisfied by global interrupt disabling. A typical scenario consists of a driver that needs to reset the hardware device. Before fiddling with I/O ports, the driver performs global interrupt disabling, ensuring that no other driver will access the same ports.

As we shall see in this section, global interrupt disabling significantly lowers the system concurrency level; it is deprecated because it can be replaced by more efficient synchronization techniques.

Global interrupt disabling is performed by the cli( ) macro. On uniprocessor system, the macro just expands into__cli( ), disabling local interrupts. On multiprocessor systems, the macro waits until all interrupt handlers and all deferrable functions in the other CPUs terminate, and then acquires the global_irq_lock spin lock. The key activities required for multiprocessor systems occur inside the _ _global_cli( ) function, which is called by cli( ):

_save_flags(flags); if (!(flags & 0x00000200)) /* testing IF flag */ {

First of all, _ _global_cli( ) checks the value of the IF flag of the eflags register because it refuses to "promote" the disabling of a local interrupt to a global one. Deadlock conditions can easily occur if this constraint is removed and global interrupts are disabled inside a critical region protected by a spin lock. For instance, consider a spin lock that is also accessed by interrupt handlers. Before acquiring the spin lock, the kernel must disable local interrupts, otherwise an interrupt handler could freeze waiting until the interrupted program released the spin lock. Now, suppose that a kernel control path disables local interrupts, acquires the spin lock, and then invokes cli( ). The latter macro waits until all interrupt handlers on the other CPUs terminate; however, an interrupt handler could be stuck waiting for the spin lock to be released. To avoid this kind of deadlock, _ _global_cli( ) refuses to run if local interrupts are already disabled before its invocation.

If cli( ) is invoked with local interrupts enabled, _ _global_cli( ) disables them. If cli( ) is invoked inside an interrupt service routine (i.e., local_irq_count macro returns a value different than 0), _ _global_cli( ) returns without performing any further action. [6] Otherwise, _ _global_irq( ) invokes the get_irqlock( ) function, which acquires the global_irq_lock spin lock and waits for the termination of all interrupt handlers running on the other CPUs. Moreover, if cli( ) is not invoked by a deferrable function, get_irqlock( ) waits for the termination of all bottom halves running on the other CPUs.

[6] This case should never occur because protection against concurrent execution of interrupt handlers should be based on spin locks rather than on global interrupt disabling. In short, an interrupt service routine should never execute the cli( ) macro.

global_irq_lock differs from normal spin locks because invoking get_irqlock( ) does not freeze the CPU if it already owns the lock. In fact, the global_irq _holder variable contains the logical identifier of the CPU that is holding the lock; this value is checked by get_irqlock( ) before starting the tight loop of the spin lock.

Once cli( ) returns, no other interrupt handler on other CPUs starts running until interrupts are re-enabled by invoking the sti( ) macro. On multiprocessor systems, sti( ) invokes the__global_sti( ) function:

cpu = smp_processor_id( ); if (!local_irq_count[cpu])

The release_irqlock( ) function releases the global_irq_lock spin lock. Notice that similar to cli( ), the sti( ) macro invoked inside an interrupt service routine is equivalent to__sti( ) because it doesn't release the spin lock.

Linux also provides global versions of the _ _save_flags and _ _restore_flags macros, which are also called save_flags and restore_flags. They save and reload, respectively, information controlling the interrupt handling for the executing CPU. As illustrated in Figure 5-3, save_flags yields an integer value that depends on three conditions; restore_flags performs actions based on the value yielded by save_flags.

Figure 5-3. Actions performed by save_ flags( ) and restore_ flags( )

Figure 5-3. Actions performed by save_ flags( ) and restore_ flags( )

+5 -1

Post a comment