7.5 Locks and interrupts
Some xv6 spinlocks protect data that is used by
both threads and interrupt handlers.
For example, the
clockintr
timer interrupt handler might increment
ticks
(3482)
at about the same time that a kernel
thread reads
ticks
in
sys_pause
(3836).
The lock
tickslock
serializes the two accesses.
The interaction of spinlocks and interrupts raises a potential danger.
Suppose
sys_pause
holds
tickslock,
and its CPU is interrupted by a timer interrupt.
clockintr
would try to acquire
tickslock,
see it was held, and wait for it to be released.
In this situation,
tickslock
will never be released: only
sys_pause
can release it, but
sys_pause
will not continue running until
clockintr
returns.
So the CPU will deadlock, and any code
that needs either lock will also freeze.
To avoid this situation, if a spinlock is used by an interrupt handler,
a CPU must never hold that lock with interrupts enabled.
Xv6 is more conservative: when a CPU acquires any
lock, xv6 always disables interrupts on that CPU.
Interrupts may still occur on other CPUs, so
an interrupt’s
acquire
can wait for a thread to release a spinlock; just not on the same CPU.
Xv6 re-enables interrupts when a CPU holds no spinlocks; it must
do a little book-keeping to cope with nested critical sections.
acquire
calls
push_off
(1355)
and
release
calls
pop_off
(1369)
to track the nesting level of locks on the current CPU.
When that count reaches zero,
pop_off
restores the interrupt enable state that existed
at the start of the outermost critical section.
The
intr_off
and
intr_on
functions execute RISC-V instructions to disable and enable
interrupts, respectively.
It is important that
acquire
call
push_off
strictly before setting
lk->locked
(1277).
If the two were reversed, there would be
a brief window when the lock
was held with interrupts enabled, and
an unfortunately timed interrupt would deadlock the system.
Similarly, it is important that
release
call
pop_off
only after
releasing the lock
(1321).