1Which clock?
Before writing anything, decide how the kernel will know how long a process ran. This
tree has two clocks you could build on: the timer interrupt, and the time CSR that
the timer is programmed from. For each, find out: how often does it tick, on which
harts, who can read it, and what would “charge this process” look like? Then pick one.
Read clockintr and the two places that call yield when which_dev == 2:
usertrap (kernel/trap.c:85) and kerneltrap (kernel/trap.c:157). Then
read timerinit in kernel/start.c and r_time in kernel/riscv.h:301.
Every hart programs its own stimecmp 1,000,000 ticks of time ahead, so every hart
takes a timer interrupt about every 0.1 s, but only hart 0 increments ticks
(kernel/trap.c:169). start sets bit 1 ™ of mcounteren
(kernel/start.c:62), which is what lets supervisor mode read time. Nothing in
the tree sets scounteren, the register that would let user mode read it.
Either count timer interrupts per process, on every hart, by the mode each one
interrupted (one sample per 0.1 s per hart), or read time each time a process
changes between user mode, the kernel and not running, and add the differences to
two counters.
The reference design
Sampling the timer interrupt is the classic Unix approach: at each timer interrupt, charge one period (0.1 s here) to whichever process the interrupt found running, as user or system time depending on the mode it interrupted.
- It has to happen on every hart. If you hang it on
ticks(only hart 0 increments it) you see one hart in three. Three processes spinning on three harts for 3 s were charged 2.5 to 2.8 s instead of about 8.7 s by the hart-0 ledger in the Measure section (clinic 1 shows the same mistake made for real). - Its resolution is 0.1 s.
echo hiruns for a few milliseconds (real 0.003andreal 0.004in clinic 3’s runs), so it would be charged 0 or 100 ms. - It is statistical, and the sample is not taken at a random moment: an interrupt that becomes pending while interrupts are off waits for the next moment they are on. The Measure section shows that this skews the user/system split of a system-call-heavy program.
Reading the time CSR at every transition is exact to one tick (100 ns at
QEMU’s 10 MHz) and costs one csrr per transition. Supervisor mode may read it here
because start set mcounteren.TM. All harts read the same clock: the RISC-V
unprivileged specification requires the real-time clocks of all harts to be
synchronized to within one tick. The per-process accounting will not even need that,
as you will see: every stretch turns out to start and end on the same hart. real,
which time compares across a fork and a wait that may run on different harts,
does need it.
The reference uses the CSR. The rest of the lab is about where to read it, which is the hard part.
Check yourself
clockintr asks for the next timer interrupt 1,000,000 ticks of time after it
handles one, and time counts at 10 MHz in QEMU’s virt machine. About how many
timer interrupts does each hart take per second?
A learner charges 0.1 s of user or system time to myproc() right next to
ticks++ in clockintr, inside the if (cpuid() == 0) block. Three processes
spin in user mode for 3 s, one on each hart. About how much user time is charged
to the three of them together?