4.4 Code: System call arguments
System call implementations in the kernel need to find the arguments
passed by user code. Because user code calls system call wrapper
functions, the arguments are initially where the RISC-V C calling
convention places them: in registers.
The kernel trap code saves user registers to the current
process’s trap frame, where kernel code can find them.
The kernel functions
argint
,
argaddr
,
and
argfd
retrieve the
n ’th
system call argument
from the trap frame
as an integer, pointer, or a file descriptor.
They all call argraw to retrieve the appropriate saved
user register
(kernel/syscall.c:34).
Some system calls pass pointers as arguments, and the kernel must use those pointers to read or write user memory. The exec system call, for example, passes the kernel an array of pointers referring to string arguments in user space. These pointers pose two challenges. First, the user program may be buggy or malicious, and may pass the kernel an invalid pointer or a pointer intended to trick the kernel into accessing kernel memory instead of user memory. Second, the xv6 kernel page table mappings are not the same as the user page table mappings, so the kernel cannot use ordinary instructions to load or store from user-supplied addresses.
The kernel implements functions that safely transfer data to and
from user-supplied addresses.
fetchstr is an example (kernel/syscall.c:25).
File system calls such as
exec use fetchstr to retrieve string file-name arguments from user
space.
fetchstr
calls copyinstr
to do the hard work.
copyinstr
(kernel/vm.c:415) copies up to max
bytes to
dst
from virtual address srcva
in the user page
table pagetable
.
Since pagetable
is not the current page
table,
copyinstr
uses walkaddr
(which calls walk) to look up
srcva
in
pagetable
, yielding
physical address pa0
.
The kernel’s page table maps all of physical RAM
at virtual addresses that are equal to the RAM’s physical address.
This allows
copyinstr to directly copy string bytes from pa0 to dst.
walkaddr
(kernel/vm.c:109)
checks that the user-supplied virtual address is part of
the process’s user address space, so programs
cannot trick the kernel into reading other memory.
A similar function, copyout, copies data from the
kernel to a user-supplied address.