Lecture: RISC-V privileged ISA
administrivia
today’s plan
base ISA review
privileged ISA
base ISA review
base ISA and extensions
general-purpose registers
example function call:
riscv64-unknown-elf-gcc -S -fno-inline -O2 -c t.c
on the following C code
what are call
and ret
? are they “real” instructions”?
what does ret
do in bar
?
what does foo
save ra
beore calling bar
?
int bar ( int x )
{
return x + 1 ;
}
int foo ( int x )
{
return bar ( x ) + 1 ;
}
bar:
addiw a0,a0,1
ret
.size bar, .-bar
.align 1
.globl foo
.type foo, @function
foo:
addi sp,sp,-16
sd ra,8(sp)
call bar
ld ra,8(sp)
addiw a0,a0,1
addi sp,sp,16
jr ra
privileged ISA
specification
RISC-V base spec : §9, Zicsr
RISC-V privileged spec :§4 (Supervisor-Level ISA) is most relevant to kernel programming;
may also need to consult §3 (Machine-Level ISA) for some registers
overview
interface
csrr*
instructions to transfer values between CSRs and general-purpose registers
ecall
: environment call to trap (usually to more privileged level)
mret
/sret
/uret
: return from trap handling to M/S/U-mode
S-mode provides virtual memory support
the satp
CSR holds the root of the page table
sfence.vma
: synchronize page-table changes (similar to TLB flush)
trap handling
int main ( void )
{
* ( volatile int * ) 0x4000000000 = 0 ;
return 0 ;
}
what happens if one run the above program in user space? why?
what does the CPU do?
what does the OS kernel do?
workflow
the kernel sets up the entry point in the stvec
register (kernel/trap.c
)
the cpu switches to kernel mode (if needed) and jumps to stvec
trap causes
exceptions (e.g., page faults)
system calls (e.g., ecall
)
interrupts (e.g., by timer/external devices)
example: page fault
demo: add printf("%d\n", *(volatile int *)0x4000000000);
to main
in user/call.c
window 1: make qemu-gdb
window 2: make gdb
window 2: type b *0x28 (the address from sepc
)
window 2: press c a few times to continue until window 1 runs to shell
window 1: run call
window 2: hit breakpoint 1 at the lw
instruction
window 2: type delete 1 to disable the breakpoint for now
window 2: single step in gdb using si
window 2: now in the trampoline code (kernel/trampoline.S
)
window 2: single step until to C code (usertrap
in kernel/trap.c
)
registers
scause
: 13 (0xd), “load access fault”
sepc
: pc of the faulted instruction
stval
: the virtual memory address
questions
how to change the kernel (usertrap
in kernel/trap.c
) to continue instead of killing the process?
or even print 42?
$ call
usertrap(): unexpected scause 0x000000000000000d pid=3
sepc=0x0000000000000028 stval=0x0000004000000000
virtual memory
451 review questions
why use multiple levels for page table? why not just one level?
it makes sense for user processes to use virtual addresses,
but why does the kernel also use virtual addresses?
satp
: Figure 4.12 of the RISC-V privileged spec
MODE: xv6 uses 8 (sv39, three-level page table), see Table 4.3
ASID: ignore (set to 0)
PPN: physical page number of the page-table root
example: #define MAKE_SATP(pagetable) ((8L << 60) | (((uint64)pagetable) >> 12))
in xv6’s kernel/riscv.h
page table (sv39): Figure 3.2 of the xv6 book
three-level page table
64-bit VA (→ 56-bit PA)
top 25 VA bits unused
next 27 VA bits used to index into page table (9 bits for each level)
bottom 12 bits untranslated and copied to PA
each level (one page) contains 512 page-table entries (PTEs)
PTE = 44-bit PPN (for the next level) + 10-bit permission flags
V/R/W/X/U: valid/read/write/execute/user
A/D: accessed/dirty
see PTE*
macros in xv6’s kernel/riscv.h
implement vmprint
to print page table
see the first part of lab lazy
use it to show kernel and process address spaces
example
insert a call to vmprint
in kvminit()
, right after the first kvmmap
use it to translate VA 0x1000_0000
- what’s the resulting PA?
page table 0x0000000087fff000
..0: pte 0x0000000021fff801 pa 0x0000000087ffe000
.. ..128: pte 0x0000000021fff401 pa 0x0000000087ffd000
.. .. ..0: pte 0x0000000004000007 pa 0x0000000010000000