xv6, line by line
kernel/spinlock.c

kernel/spinlock.c

C · 116 lines · annotated 100% · kernel · upstream

About this file

The implementation of the spinlock, the lock that every other piece of synchronization in xv6 is built on. It has three jobs, and each one is a classic source of bugs:

  1. Mutual exclusion. Only one CPU at a time may hold a lock. acquire uses an atomic swap instruction so that two CPUs can never both see the lock as free.
  2. Memory ordering. Reads and writes of the protected data must stay inside the critical section; neither the compiler nor the hardware may move them before the acquire or after the release. The __atomic built-ins add the needed barriers.
  3. No interrupts while holding a lock. If an interrupt handler on the same CPU tried to take a lock the interrupted code holds, the CPU would deadlock. So a CPU keeps interrupts off while it holds any spinlock, using the nesting counter in push_off and pop_off (interrupts and spinlocks (push_off / pop_off)).

The compiler output quoted below comes from kernel/kernel.asm of this build.

Read before: kernel/spinlock.h. Read next: kernel/sleeplock.c, and sched in kernel/proc.c for how a lock is carried across a context switch.

1// Mutual exclusion spin locks.
3#include "types.h"
4#include "param.h"
6#include "spinlock.h"
7#include "riscv.h"
8#include "proc.h"
9#include "defs.h"
11void
12initlock(struct spinlock *lk, char *name)
15 lk->locked = 0;
16 lk->cpu = 0;
19// Acquire the lock.
20// Loops (spins) until the lock is acquired.
21void
24 push_off(); // disable interrupts to avoid deadlock.
25 if (holding(lk))
26 panic("acquire");
28 // On RISC-V, __atomic_exchange_n turns into an atomic swap:
29 // a5 = 1
30 // s1 = &lk->locked
31 // amoswap.w.aq a5, a5, (s1)
32 //
33 // Passing __ATOMIC_ACQUIRE to __atomic_exchange_n tells
34 // the C compiler and the processor to not move loads or stores
35 // past this point, to ensure that the critical section's memory
36 // references happen strictly after the lock is acquired.
37 while (__atomic_exchange_n(&lk->locked, 1, __ATOMIC_ACQUIRE) != 0)
38 ;
40 // Record info about lock acquisition for holding() and debugging.
41 lk->cpu = mycpu();
44// Release the lock.
45void
48 if (!holding(lk))
49 panic("release");
51 lk->cpu = 0;
53 // Release the lock, equivalent to lk->locked = 0.
54 //
55 // This code doesn't use a C assignment, since the C standard
56 // implies that an assignment might be implemented with
57 // multiple store instructions.
58 //
59 // On RISC-V, __atomic_store_n turns into a single atomic store:
60 // s1 = &lk->locked
61 // fence rw,w
62 // sw zero,0(s1)
63 //
64 // The __ATOMIC_RELEASE argument to __atomic_store_n tells the
65 // the C compiler and the CPU to not move loads or stores past
66 // this point, to ensure that all the stores in the critical
67 // section are visible to other CPUs before the lock is released,
68 // and that loads in the critical section occur strictly before
69 // the lock is released.
70 //
71 // On RISC-V, this generates a fence instruction before the store:
72 // fence rw,w
73 __atomic_store_n(&lk->locked, 0, __ATOMIC_RELEASE);
78// Check whether this cpu is holding the lock.
79// Interrupts must be off.
80int
83 int r;
84 r = (lk->locked && lk->cpu == mycpu());
85 return r;
88// push_off/pop_off are like intr_off()/intr_on() except that they are matched:
89// it takes two pop_off()s to undo two push_off()s. Also, if interrupts
90// are initially off, then push_off, pop_off leaves them off.
92void
95 // disable interrupts to prevent an involuntary context
96 // switch while using mycpu().
98 int old = !!(flags & SSTATUS_SIE);
100 if (mycpu()->noff == 0)
102 mycpu()->noff += 1;
105void
108 struct cpu *c = mycpu();
109 if (intr_get())
110 panic("pop_off - interruptible");
111 if (c->noff < 1)
112 panic("pop_off");
113 c->noff -= 1;
114 if (c->noff == 0 && c->intena)