xv6, line by line
test yourself

Test yourself · category 3 of 20

Traps and system calls

How a user program asks the kernel for a service, what ecall and the trap path do, how usertrap and kerneltrap sort traps by scause, and how a system call’s number, arguments and result travel through the trapframe.

1warm-upChoose one

echo calls write(1, "hi", 2). What is write in user space?

user/usys.S
28.global write
30 li a7, SYS_write
31 ecall
32 ret
2warm-upChoose one

How does the kernel know which system call the program asked for, and where does syscall read that information?

kernel/syscall.c
136void
139 int num;
140 struct proc *p = myproc();
143 if (num > 0 && num < NELEM(syscalls) && syscalls[num]) {
144 // Use num to lookup the system call function for num, call it,
145 // and store its return value in p->trapframe->a0
147 } else {
148 printk("%d %s: unknown sys call %d\n", p->pid, p->name, num);
149 p->trapframe->a0 = -1;
150 }
3warm-upType a number

In our gdb run, echo’s ecall for write sits at user address 0x354. When the system call finishes, at what address does echo continue? (Answer in hex.)

kernel/trap.c
49 struct proc *p = myproc();
51 // save user program counter.
54 if (r_scause() == 8) {
55 // system call
57 if (killed(p))
58 kexit(-1);
60 // sepc points to the ecall instruction,
61 // but we want to return to the next instruction.
62 p->trapframe->epc += 4;
decimal, 0x hex or 0b binary
4warm-upTrue or false, and why

True or false: when a user program executes ecall, the hardware switches sp to the process’s kernel stack.

Why?

5warm-upChoose one

Why does usertrap add 4 to p->trapframe->epc for a system call?

kernel/trap.c
49 struct proc *p = myproc();
51 // save user program counter.
54 if (r_scause() == 8) {
55 // system call
57 if (killed(p))
58 kexit(-1);
60 // sepc points to the ecall instruction,
61 // but we want to return to the next instruction.
62 p->trapframe->epc += 4;
64 // an interrupt will change sepc, scause, and sstatus,
65 // so enable only now that we're done with those registers.
69 } else if ((which_dev = devintr()) != 0) {
6warm-upChoose one

The first thing usertrap does after its SPP check is point stvec at kernelvec (line 47). Why?

kernel/trap.c
40 int which_dev = 0;
42 if ((r_sstatus() & SSTATUS_SPP) != 0)
43 panic("usertrap: not from user mode");
45 // send interrupts and exceptions to kerneltrap(),
46 // since we're now in the kernel.
47 w_stvec((uint64)kernelvec); //DOC: kernelvec
49 struct proc *p = myproc();
7solidMatch the pairs

Match each scause value with what it means. These are the values usertrap, kerneltrap and devintr compare against.

8solidChoose all that apply

Which of these does the hardware itself change when a user program executes ecall?

9solidChoose one

For a system call, usertrap turns interrupts on at line 66, but only after line 52 and the scause test. Why not earlier?

kernel/trap.c
49 struct proc *p = myproc();
51 // save user program counter.
54 if (r_scause() == 8) {
55 // system call
57 if (killed(p))
58 kexit(-1);
60 // sepc points to the ecall instruction,
61 // but we want to return to the next instruction.
62 p->trapframe->epc += 4;
64 // an interrupt will change sepc, scause, and sstatus,
65 // so enable only now that we're done with those registers.
69 } else if ((which_dev = devintr()) != 0) {
10warm-upClick the line

Click the line that delivers sys_write’s result to the user program.

kernel/syscall.c
136void
139 int num;
140 struct proc *p = myproc();
143 if (num > 0 && num < NELEM(syscalls) && syscalls[num]) {
144 // Use num to lookup the system call function for num, call it,
145 // and store its return value in p->trapframe->a0
147 } else {
148 printk("%d %s: unknown sys call %d\n", p->pid, p->name, num);
149 p->trapframe->a0 = -1;
150 }

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

11solidChoose one

A buggy program puts 99 in a7 and executes ecall. What happens in this kernel?

kernel/syscall.c
136void
139 int num;
140 struct proc *p = myproc();
143 if (num > 0 && num < NELEM(syscalls) && syscalls[num]) {
144 // Use num to lookup the system call function for num, call it,
145 // and store its return value in p->trapframe->a0
147 } else {
148 printk("%d %s: unknown sys call %d\n", p->pid, p->name, num);
149 p->trapframe->a0 = -1;
150 }
12solidType a number

What is NELEM(syscalls), the number of elements in the table?

kernel/syscall.c
107// An array mapping syscall numbers from syscall.h
108// to the function that handles the system call.
109static uint64 (*syscalls[])(void) = {
110 // clang-format off
133 // clang-format on
134};
decimal, 0x hex or 0b binary
13warm-upType a number

At most how many arguments can a system call handler fetch with argint, argaddr or argstr?

kernel/syscall.c
34static uint64
35argraw(int n)
37 struct proc *p = myproc();
38 switch (n) {
39 case 0:
40 return p->trapframe->a0;
41 case 1:
42 return p->trapframe->a1;
43 case 2:
44 return p->trapframe->a2;
45 case 3:
46 return p->trapframe->a3;
47 case 4:
48 return p->trapframe->a4;
49 case 5:
50 return p->trapframe->a5;
51 }
52 panic("argraw");
53 return -1;
decimal, 0x hex or 0b binary
14solidChoose all that apply

A timer interrupt arrives while cat runs in user mode (it has not been killed). Which of these does usertrap do for this trap?

kernel/trap.c
40 int which_dev = 0;
42 if ((r_sstatus() & SSTATUS_SPP) != 0)
43 panic("usertrap: not from user mode");
45 // send interrupts and exceptions to kerneltrap(),
46 // since we're now in the kernel.
47 w_stvec((uint64)kernelvec); //DOC: kernelvec
49 struct proc *p = myproc();
51 // save user program counter.
54 if (r_scause() == 8) {
55 // system call
57 if (killed(p))
58 kexit(-1);
60 // sepc points to the ecall instruction,
61 // but we want to return to the next instruction.
62 p->trapframe->epc += 4;
64 // an interrupt will change sepc, scause, and sstatus,
65 // so enable only now that we're done with those registers.
69 } else if ((which_dev = devintr()) != 0) {
70 // ok
71 } else if ((r_scause() == 15 || r_scause() == 13) &&
73 (r_scause() == 13) ? 1 : 0) != 0) {
74 // page fault on lazily-allocated page
75 } else {
76 printk("usertrap(): unexpected scause 0x%lx pid=%d\n", r_scause(), p->pid);
77 printk(" sepc=0x%lx stval=0x%lx\n", r_sepc(), r_stval());
79 }
81 if (killed(p))
82 kexit(-1);
84 // give up the CPU if this is a timer interrupt.
85 if (which_dev == 2)
90 // the user page table to switch to, for trampoline.S
93 // return to trampoline.S; satp value in a0.
94 return satp;
15deepFill in the machine state

A user program stores to a lazily allocated heap page and takes a store page fault (scause 15). usertrap calls vmfault. What is the state of the hart at line 463, before kalloc has been called?

kernel/vm.c
463 if (va >= psz)
464 return 0;
467 return 0;
468 }
470 if (mem == 0)
471 return 0;
472 memset((void *)mem, 0, PGSIZE);
474 kfree((void *)mem);
475 return 0;
476 }
477 return mem;
16solidDecode the bits

Inside usertrap, scause reads 0x8000000000000009. Decode it.

Value: 0x8000000000000009

17solidTrue or false, and why

True or false: if a user program sets sp to 0 just before its ecall, it can crash the kernel.

Why?

18solidPut in order

Put these steps of one system call (one that does not sleep or yield) in the order they happen.

  1. syscall runs the handler and stores its result in trapframe->a0
  2. uservec saves the user registers into the trapframe
  3. uservec loads the kernel stack and switches satp to the kernel page table
  4. userret switches to the user page table, restores the registers and executes sret
  5. usertrap turns interrupts on
  6. ecall: mode U to S, sepc = the ecall’s address, pc = stvec
  7. prepare_return turns interrupts off and points stvec at uservec
  8. usertrap copies sepc into p->trapframe->epc
19deepChoose one

Why must kerneltrap write sepc and sstatus back (lines 162–163) before returning to kernelvec?

kernel/trap.c
134// interrupts and exceptions from kernel code go here via kernelvec,
135// on whatever the current kernel stack is.
136void
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.
20deepChoose one

kernelvec restores every register it saved except tp; it does not even save it. Why?

kernel/kernelvec.S
40 # restore registers.
41 ld ra, 0(sp)
42 # ld sp, 8(sp)
43 ld gp, 16(sp)
44 # not tp (contains hartid), in case we moved CPUs
45 ld t0, 32(sp)
46 ld t1, 40(sp)
47 ld t2, 48(sp)
48 ld a0, 72(sp)
49 ld a1, 80(sp)
50 ld a2, 88(sp)
51 ld a3, 96(sp)
52 ld a4, 104(sp)
53 ld a5, 112(sp)
54 ld a6, 120(sp)
55 ld a7, 128(sp)
56 ld t3, 216(sp)
57 ld t4, 224(sp)
58 ld t5, 232(sp)
59 ld t6, 240(sp)
61 addi sp, sp, 256
63 # return to whatever we were doing in the kernel.
64 sret
21deepChoose one

Suppose usertrap called intr_on() right after line 47 (so before line 52), and a timer interrupt was already pending when echo executed its ecall for write. What would happen?

kernel/trap.c
49 struct proc *p = myproc();
51 // save user program counter.
54 if (r_scause() == 8) {
55 // system call
57 if (killed(p))
58 kexit(-1);
60 // sepc points to the ecall instruction,
61 // but we want to return to the next instruction.
62 p->trapframe->epc += 4;
64 // an interrupt will change sepc, scause, and sstatus,
65 // so enable only now that we're done with those registers.
69 } else if ((which_dev = devintr()) != 0) {
22deepChoose all that apply

In this kernel, which of these traps from user mode end with usertrap killing the process?

kernel/trap.c
69 } else if ((which_dev = devintr()) != 0) {
70 // ok
71 } else if ((r_scause() == 15 || r_scause() == 13) &&
73 (r_scause() == 13) ? 1 : 0) != 0) {
74 // page fault on lazily-allocated page
75 } else {
76 printk("usertrap(): unexpected scause 0x%lx pid=%d\n", r_scause(), p->pid);
77 printk(" sepc=0x%lx stval=0x%lx\n", r_sepc(), r_stval());
79 }
81 if (killed(p))
82 kexit(-1);
23deepPut in order

cat is inside copyout (in a system call, interrupts on) when hart 2’s timer fires. Put the steps in order.

  1. kerneltrap copies sepc and sstatus into local variables
  2. The hardware sets sepc to the interrupted instruction, scause to the timer code, clears SIE, and jumps to stvec, which is kernelvec
  3. kernelvec pushes a 256-byte frame on cat’s kernel stack and saves the caller-saved registers
  4. devintr calls clockintr, which writes a new stimecmp
  5. kernelvec reloads the registers, pops its frame and executes sret
  6. yield switches away; later cat resumes, perhaps on another hart
  7. kerneltrap writes the saved sepc and sstatus back
24solidChoose one

Hart 1 has nothing to run. Its scheduler briefly enables interrupts at line 441, and a timer interrupt is taken there. What does kerneltrap do?

kernel/trap.c
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.
25solidType a number

During one system call that neither sleeps nor yields, how many times is stvec written, from the ecall to the sret?

decimal, 0x hex or 0b binary