I was reading the implementation of Linux semaphores. Due to atomicity, signal and wait (up and down in the source code) use spin locks. Then I saw Linux disabled interrupt in spin_lock_irqsave and reenabled interrupt in spin_unlock. This confused me. In my opinion, there is really no point disabling interrupt within a critical section.
For example, proc A (currently active) acquired the lock, proc B (blocked) is waiting for the lock and proc C is doing some unrelated stuff. It makes perfect sense to context switch to C within the critical section between A and B. Even if C also tries to acquire the lock, since the lock is already locked by A, the result would be C being blocked and A resuming execution.
Therefore, I don't know why Linux decided to disable interrupt within critical sections guarded by spin locks. It probably won't cause any problems but seems like a redundant operation to me.
Allow me to start off with a disclaimer that I am not a Linux expert, so my answer may not be the most accurate. Please point out any flaws and problems that you may find.
Imagine if some shared data is used by various parts of the kernel, including operations such as interrupt handlers that need to be fast and cannot block. Let's say system call foo
is currently active and has acquired a lock to use/access shared data bar
, and interrupts are not disabled when/before acquiring said lock.
Now a (hardware) interrupt handler, e.g. the keyboard, kicks in and also needs access to bar
(hardware interrupts have higher priority than system calls). Since bar
is currently being locked by syscall foo
, the interrupt handler cannot do anything. Interrupt handlers do need to be fast & not be blocked though, so they just keep spinning while trying to acquire the lock, which would cause a deadlock (i.e. system freeze) since syscall foo
never gets a chance to finish and release its lock.
If you disable interrupts before trying to acquire the lock in foo
, though, then foo
will be able to finish up whatever it's doing and ultimately release the lock (and restore interrupts). Any interrupts trying to come in while foo
holds the spinlock will be left on the queue, and will be able to start when the lock is released. This way, you won't run into the problem described above. However, care must also be taken to ensure that the lock for bar
is held for as short as possible, so that other higher priority operations can take over whenever required.
Thank you for your answer. I did not consider this scenario indeed. Then how about on a multicore system? Is it sufficient to just disable the local core's interrupt? Since in that case there would be no deadlock.
Yes it's sufficient - your data would be protected from multicore access with a global spinlock. If the data can be concurrently accessed by a maximum of x number of processes, and if the data is not used by interrupt handlers, then you could use a semaphore to protect instead.
It's also worth mentioning that if the data is used by multiple interrupt handlers, you should disable interrupts before getting a spinlock (using
spin_lock_irqsave
) for all handlers & functions that can be interrupted by a higher priority interrupt that also uses the same data. Only the interrupt handler with the highest priority does not need to disable interrupts.That makes perfect sense. Thanks!
this is called priority inversion in short. To avoid priority inversion on same core. In case of multiprocessor system also process disables interrupt on its own core to ensure it gets the enough cpu time to run.