trapinit is one line:

void trapinit(void) {
  initlock(&tickslock, "time");
}

It initializes the lock that protects the ticks counter — a global that gets incremented every timer interrupt. Processes can sleep waiting on &ticks (that’s how sleep() the syscall works — it sleeps until enough ticks have passed). The lock prevents races between the timer interrupt incrementing ticks and processes reading it.

That’s all trapinit does. The actual trap handling setup happens in trapinithart.

trapinithart

Also one line:

void trapinithart(void) {
  w_stvec((uint64)kernelvec);
}

This writes the address of kernelvec (an assembly routine in kernelvec.S) into the stvec register. stvec is the register the CPU checks when a trap occurs — an interrupt, an exception, a syscall, anything. When a trap fires, the CPU jumps to whatever address is in stvec.

At this point during boot, the kernel sets it to kernelvec because we’re in kernel code. If a trap happens right now (say a timer interrupt), we want the kernel trap handler, not the user trap handler.

Later, when the kernel is about to return to a user process, prepare_return switches stvec to point to uservec in the trampoline. Then when that user process traps (syscall, page fault, interrupt), the CPU jumps to uservec. And the first thing usertrap does after handling the trap is switch stvec back to kernelvec — because now we’re in kernel code again.

So stvec bounces back and forth:

  • Kernel is running → stvec points to kernelvec
  • User is running → stvec points to uservec (in the trampoline)

trapinithart is the initial setting. It’s per-hart because stvec is a per-hart register — each hart has its own, just like satp. That’s why the other harts also call trapinithart after they wake up. They each need to tell their own hardware where to jump on a trap.