xv6, line by line
test yourself

Test yourself · category 5 of 20

Interrupts, the PLIC and the timer

How the Sstc timer and the PLIC raise interrupts, which hart takes them and when, how devintr tells them apart, and how claim, complete and stimecmp acknowledge them.

1warm-upChoose one

A supervisor timer interrupt is pending on hart 1. In this kernel, what makes it stop being pending?

kernel/trap.c
166void
169 if (cpuid() == 0) {
174 }
176 // ask for the next timer interrupt. this also clears
177 // the interrupt request. 1000000 is about a tenth
178 // of a second.
179 w_stimecmp(r_time() + 1000000);
2warm-upChoose one

In devintr, the external-interrupt branch asks the PLIC who interrupted with plic_claim, but the timer branch does not. Why?

kernel/trap.c
187int
192 if (scause == 0x8000000000000009L) {
193 // this is a supervisor external interrupt, via PLIC.
195 // irq indicates which device interrupted.
196 int irq = plic_claim();
198 if (irq == UART0_IRQ) {
200 } else if (irq == VIRTIO0_IRQ) {
202 } else if (irq) {
203 printk("unexpected interrupt irq=%d\n", irq);
204 }
206 // the PLIC allows each device to raise at most one
207 // interrupt at a time; tell the PLIC the device is
208 // now allowed to interrupt again.
209 if (irq)
212 return 1;
213 } else if (scause == 0x8000000000000005L) {
214 // timer interrupt.
216 return 2;
217 } else {
218 return 0;
219 }
3solidType a number

plicinithart writes one 32-bit value into this hart’s S-mode enable register. What is that value? (Hex like 0x10 or decimal are both accepted.)

kernel/plic.c
19void
22 int hart = cpuid();
24 // set enable bits for this hart's S-mode
25 // for the uart and virtio disk.
28 // set this hart's S-mode priority threshold to 0.
decimal, 0x hex or 0b binary
4solidType a number

What is the address of hart 2’s S-mode claim/complete register, the one plic_claim reads on hart 2? Give it in hex.

kernel/memlayout.h
32// qemu puts platform-level interrupt controller (PLIC) here.
33#define PLIC 0x0c000000L
34#define PLIC_PRIORITY (PLIC + 0x0)
35#define PLIC_PENDING (PLIC + 0x1000)
36#define PLIC_SENABLE(hart) (PLIC + 0x2080 + (hart) * 0x100)
37#define PLIC_SPRIORITY(hart) (PLIC + 0x201000 + (hart) * 0x2000)
38#define PLIC_SCLAIM(hart) (PLIC + 0x201004 + (hart) * 0x2000)
decimal, 0x hex or 0b binary
5warm-upType a number

The machine has three harts, and QEMU’s time counter runs at 10,000,000 per second. Approximately how many times per second does the kernel’s global ticks counter increase? (Give the nearest whole number.)

kernel/trap.c
166void
169 if (cpuid() == 0) {
174 }
176 // ask for the next timer interrupt. this also clears
177 // the interrupt request. 1000000 is about a tenth
178 // of a second.
179 w_stimecmp(r_time() + 1000000);
decimal, 0x hex or 0b binary
6solidChoose all that apply

A hart is running kernel code in supervisor mode. Which of these does the hardware check before it takes a supervisor timer interrupt?

7warm-upDecode the bits

kerneltrap reads scause = 0x8000000000000009. Decode it.

Value: 0x8000000000000009

8solidDecode the bits

Under gdb, hart 0 at the first instruction of scheduler shows sie = 0x220. Decode it.

Value: 0x220

9solidPut in order

Hart 2 is idle in its scheduler when the disk finishes a read. Put the events in order, from the device to the moment the PLIC may forward the disk’s interrupts again.

  1. Hart 2 executes intr_on() in its scheduler and takes the trap at stvec, which is kernelvec
  2. plic_claim reads hart 2’s claim register and gets 1
  3. virtio_disk_intr acknowledges the device, sets b->disk = 0 and calls wakeup
  4. kerneltrap calls devintr, which sees scause 0x8000000000000009
  5. plic_complete(1) writes 1 back to the claim register
  6. The disk raises interrupt source 1; the PLIC marks it pending and signals the harts (SEIP)
10warm-upClick the line

In clockintr, click the line that acknowledges the timer interrupt, so that it stops being pending.

kernel/trap.c
166void
169 if (cpuid() == 0) {
174 }
176 // ask for the next timer interrupt. this also clears
177 // the interrupt request. 1000000 is about a tenth
178 // of a second.
179 w_stimecmp(r_time() + 1000000);

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

11solidClick the line

Two harts trap for the same disk interrupt. Hart 2 claims it; hart 1’s claim returns 0. In devintr, click the line that keeps hart 1 from sending a completion to the PLIC.

kernel/trap.c
187int
192 if (scause == 0x8000000000000009L) {
193 // this is a supervisor external interrupt, via PLIC.
195 // irq indicates which device interrupted.
196 int irq = plic_claim();
198 if (irq == UART0_IRQ) {
200 } else if (irq == VIRTIO0_IRQ) {
202 } else if (irq) {
203 printk("unexpected interrupt irq=%d\n", irq);
204 }
206 // the PLIC allows each device to raise at most one
207 // interrupt at a time; tell the PLIC the device is
208 // now allowed to interrupt again.
209 if (irq)
212 return 1;
213 } else if (scause == 0x8000000000000005L) {
214 // timer interrupt.
216 return 2;
217 } else {
218 return 0;
219 }

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

12deepChoose one

Hart 1 is idle in its scheduler and traps for a disk interrupt that hart 2 has already claimed, so hart 1’s plic_claim returns 0. What happens next on hart 1?

kernel/trap.c
139 int which_dev = 0;
144 if ((sstatus & SSTATUS_SPP) == 0)
145 panic("kerneltrap: not from supervisor mode");
146 if (intr_get() != 0)
147 panic("kerneltrap: interrupts enabled");
149 if ((which_dev = devintr()) == 0) {
150 // interrupt or trap from an unknown source
151 printk("scause=0x%lx sepc=0x%lx stval=0x%lx\n", scause, r_sepc(),
153 panic("kerneltrap");
154 }
156 // give up the CPU if this is a timer interrupt.
157 if (which_dev == 2 && myproc() != 0)
160 // the yield() may have caused some traps to occur,
161 // so restore trap registers for use by kernelvec.S's sepc instruction.
13solidTrue or false, and why

True or false: while a hart is executing user code, a pending supervisor timer interrupt (with sie.STIE set) is taken even if sstatus.SIE happened to be 0.

Why?

14warm-upChoose one

clockintr increments ticks only when cpuid() == 0. What is the reason?

kernel/trap.c
166void
169 if (cpuid() == 0) {
174 }
176 // ask for the next timer interrupt. this also clears
177 // the interrupt request. 1000000 is about a tenth
178 // of a second.
179 w_stimecmp(r_time() + 1000000);
15deepChoose one

Suppose clockintr were changed so that only hart 0 executes line 179 (w_stimecmp(...)), on the theory that only hart 0’s ticks matter. What happens to hart 1 after its first timer interrupt?

kernel/trap.c
166void
169 if (cpuid() == 0) {
174 }
176 // ask for the next timer interrupt. this also clears
177 // the interrupt request. 1000000 is about a tenth
178 // of a second.
179 w_stimecmp(r_time() + 1000000);
16deepFill in the machine state

Hart 2 was idle in its scheduler when a disk interrupt arrived. It is now in virtio_disk_intr and has just executed line 303, acquire(&disk.vdisk_lock). What is the state of hart 2?

kernel/virtio_disk.c
300void
305 // the device won't raise another interrupt until we tell it
306 // we've seen this interrupt, which the following line does.
307 // this may race with the device writing new entries to
308 // the "used" ring, in which case we may process the new
309 // completion entries in this interrupt, and have nothing to do
310 // in the next interrupt, which is harmless.
17solidMatch the pairs

Match each register with its role in taking an interrupt.

18solidChoose one

When its scan finds nothing to run, the scheduler executes wfi at line 467 with interrupts off (line 442 turned them off). Why doesn’t the hart sleep through the interrupt that should wake it?

kernel/proc.c
435 for (;;) {
436 // The most recent process to run may have had interrupts
437 // turned off; enable them to avoid a deadlock if all
438 // processes are waiting. Then turn them back off
439 // to avoid a possible race between an interrupt
440 // and wfi.
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 }
465 if (found == 0) {
466 // nothing to run; stop running on this core until an interrupt.
467 asm volatile("wfi");
468 }
469 }
19deepChoose one

The scheduler turns interrupts on at line 441 and straight back off at line 442, and then scans the table and possibly executes wfi with interrupts off. What would go wrong if line 442 were removed, so interrupts stayed on during the scan and wfi?

kernel/proc.c
435 for (;;) {
436 // The most recent process to run may have had interrupts
437 // turned off; enable them to avoid a deadlock if all
438 // processes are waiting. Then turn them back off
439 // to avoid a possible race between an interrupt
440 // and wfi.
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 }
465 if (found == 0) {
466 // nothing to run; stop running on this core until an interrupt.
467 asm volatile("wfi");
468 }
469 }
20solidType a number

A process calls pause(5) just after a tick. Assume nothing else wakes or kills it, and that after each wakeup some scheduler runs it again before the next tick. How many times does sys_pause call sleep() (line 85)?

kernel/sysproc.c
70 int n;
73 argint(0, &n);
74 if (n < 0)
75 n = 0;
78 while (ticks - ticks0 < n) {
79 if (killed(myproc())) {
81 return -1;
82 }
87 }
89 return 0;
decimal, 0x hex or 0b binary
21warm-upChoose one

Hart 0 started a disk read and its process went to sleep. When the disk finishes, which hart runs virtio_disk_intr?

22solidChoose one

virtio_disk_intr writes the device’s INTERRUPT_ACK register (line 311), and later devintr calls plic_complete. What would happen if xv6 called plic_complete without acknowledging the device?

kernel/virtio_disk.c
300void
305 // the device won't raise another interrupt until we tell it
306 // we've seen this interrupt, which the following line does.
307 // this may race with the device writing new entries to
308 // the "used" ring, in which case we may process the new
309 // completion entries in this interrupt, and have nothing to do
310 // in the next interrupt, which is harmless.
23warm-upTrue or false, and why

True or false: if hart 1 has interrupts off when its timer fires, hart 0 may handle that timer interrupt instead.

Why?

24deepChoose all that apply

Which of these always run with interrupts off (sstatus.SIE = 0) on the hart executing them?

25deepChoose all that apply

Hypothetically, hart 0 runs for 0.35 seconds with interrupts off (say, a buggy driver spinning). Harts 1 and 2 run normally. Which statements are true?