xv6, line by line
test yourself

Test yourself · category 9 of 20

Spinlocks and interrupt state

acquire and release at the instruction level, push_off/pop_off, noff and intena, and why interrupts must be off while a spinlock is held.

1warm-upChoose one

acquire calls push_off before it tries to take the lock. Why must interrupts be off before the lock is taken, rather than just after?

kernel/spinlock.c
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();
2solidChoose all that apply

Which of these locks are acquired inside an interrupt handler (from devintr or code it calls) in this kernel?

3solidType a number

A process is in kwait holding wait_lock and a zombie child’s p->lock. It calls freeproc, which calls kfree, which is now inside acquire(&kmem.lock) with the lock held. What is mycpu()->noff at that moment?

decimal, 0x hex or 0b binary
4solidPut in order

Put the steps of acquire in the order they happen.

kernel/spinlock.c
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();
  1. holding(lk) check, panic if this hart already holds the lock
  2. amoswap.w.aq in a loop until the old value is 0
  3. push_off(): interrupts off, noff + 1
  4. lk->cpu = mycpu()
6solidFill in the machine state

Hart 1 is running cat’s consoleread (a read system call). It has just executed line 95, acquire(&cons.lock), and the lock is now held. What is the state of hart 1?

kernel/console.c
87int
91 int c;
92 char cbuf;
96 while (n > 0) {
97 // wait until interrupt handler has put some
98 // input into cons.buffer.
99 while (cons.r == cons.w) {
100 if (killed(myproc())) {
102 return -1;
103 }
108 }
7deepDecode the bits

gdb shows sstatus = 0x200000122 on a hart running kernel code. Decode it.

Value: 0x200000122

8warm-upMatch the pairs

Match each panic message with the mistake that triggers it.

9deepTrue or false, and why

True or false: holding tells you whether the current process holds the lock.

Why?

10warm-upChoose one

Hart 0 holds a spinlock. Hart 1, inside acquire, executes the swap below with a5 = 1 and s1 pointing at the lock’s locked word. What happens?

80000c02:  mv     a5,a4
80000c04:  amoswap.w.aq  a5,a5,(s1)
80000c08:  sext.w a5,a5
80000c0a:  bnez   a5,80000c02
kernel/spinlock.c
37 while (__atomic_exchange_n(&lk->locked, 1, __ATOMIC_ACQUIRE) != 0)
38 ;
11warm-upChoose one

Hart 0 holds kmem.lock. A process on hart 1 calls acquire(&kmem.lock). Until hart 0 releases it, what is hart 1 doing?

12warm-upTrue or false, and why

True or false: push_off saves the current value of SIE into intena every time it is called.

kernel/spinlock.c
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;

Why?

13warm-upType a number

A spinlock is free (locked is 0). Three harts execute their amoswap.w.aq on its locked word at the same instant. How many of the three swaps return 0?

decimal, 0x hex or 0b binary
14warm-upChoose one

Why does xv6 hold spinlocks only for short stretches of code, never across a disk read or a wait for input?

15warm-upChoose one

Why does xv6 count interrupt-disabling with push_off/pop_off instead of having acquire call intr_off and release call intr_on?

16solidChoose one

release frees the lock with __atomic_store_n(&lk->locked, 0, __ATOMIC_RELEASE), which compiles to fence rw,w followed by sw zero,0(s1). What does the fence prevent?

kernel/spinlock.c
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);
17solidChoose one

release clears lk->cpu on line 51 before it stores 0 into lk->locked on line 73. What could go wrong if the two were the other way round?

kernel/spinlock.c
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);
18solidClick the line

Click the line of release from which another hart’s acquire can succeed.

kernel/spinlock.c
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);

Your pick: none yet (click a line in the code)

19solidPut in order

A system call on hart 0 is in sys_uptime, holding tickslock, when hart 0’s timer deadline passes. Put the events in order.

  1. the pending timer interrupt is taken, and clockintr acquires tickslock, which hart 0 no longer holds
  2. pop_off takes noff to 0, sees intena 1, and sets SIE
  3. release stores 0 into tickslock.locked
  4. the amoswap takes tickslock
  5. the timer deadline passes: the interrupt becomes pending but is not taken
  6. acquire’s push_off clears SIE on hart 0 and records intena = 1
21solidChoose one

The comment above holding says “Interrupts must be off.” What could go wrong if holding(lk) ran with interrupts on?

kernel/spinlock.c
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;
22deepChoose one

sh went to sleep in consoleread (a system call) on hart 0, so the p->lock it took in sleep was acquired with SIE on and hart 0’s intena is 1. Suppose line 456 (mycpu()->intena = 0) were deleted. What goes wrong when hart 0’s scheduler continues after its swtch?

kernel/proc.c
444 int found = 0;
445 for (p = proc; p < &proc[NPROC]; p++) {
447 if (p->state == RUNNABLE) {
448 // Switch to chosen process. It is the process's job
449 // to release its lock and then reacquire it
450 // before jumping back to us.
452 c->proc = p;
455 // Don't re-enable interrupts on release.
456 mycpu()->intena = 0;
458 // Process is done running for now.
459 // It should have changed its p->state before coming back.
460 c->proc = 0;
461 found = 1;
462 }
464 }
24deepFill in the machine state

A process called exit(0). kexit has just executed line 360, release(&wait_lock), while still holding its own p->lock (taken on line 355). What is the state of its hart?

kernel/proc.c
349 // Give any children to init.
352 // Parent might be sleeping in wait().
362 // Jump into the scheduler, never to return.
364 panic("zombie exit");
25deepType a number

iput drops the last reference to an unlinked file. Holding itable.lock, it calls acquiresleep(&ip->lock) on line 362. Inside acquiresleep, line 32 calls myproc. Right after myproc’s push_off, what is mycpu()->noff?

kernel/sleeplock.c
21void
25 while (lk->locked) {
30 }
31 lk->locked = 1;
32 lk->pid = myproc()->pid;
decimal, 0x hex or 0b binary