user/usys.S
This file is generated by the build (it is not in the xv6 repository). It is shown here as the build produces it. See user/usys.pl.
About this file
The system call stubs: the code that connects a C call such as write(1, "x", 1) in a
user program to the kernel. This file is generated by user/usys.pl during the build;
it is not part of the xv6 repository.
Every stub has the same three instructions (see system call stub):
li a7, SYS_nameputs the system call number in registera7;ecalltraps into the kernel, which performs the call;retreturns to the C caller, with the kernel’s result ina0.
The arguments need no code at all. The caller is ordinary compiled C, which by the
calling convention has already placed them in a0, a1, … before calling the
stub, and the kernel reads them from exactly those registers
(system call arguments).
The whole round trip for write, as an example:
user: write(fd, buf, n) C call, args in a0..a2
write: li a7, 16 this file
ecall ── trap ──┐
kernel: uservec save registers in the trapframe (trampoline.S)
usertrap scause 8: system call; epc += 4 (trap.c)
syscall a7 = 16 → syscalls[16] = sys_write (syscall.c)
sys_write → filewrite the work (sysfile.c, file.c)
syscall trapframe->a0 = result
prepare_return, userret restore registers, sret (trap.c, trampoline.S)
user: ret ◄─────────┘ back in C, a0 = result
Each stub is 8 bytes of machine code: li with a small constant assembles into a 2-byte
compressed instruction, ecall is 4 bytes, ret 2 bytes (see user/cat.asm).
Read before: user/usys.pl, user/user.h. Read next: kernel/trampoline.S,
kernel/trap.c, kernel/syscall.c.
Generated file; get the numbers from the kernel
Line 1 is an assembler comment printed by user/usys.pl:5: change the script, not
this file, because the build regenerates it.
Line 2 is a C C preprocessor directive. Because the file name ends in .S
(capital S), GCC preprocesses it before assembling (Makefile:115), so every
SYS_ name below is replaced by its number from kernel/syscall.h. The kernel’s
table syscalls is indexed by the same names, so both sides always agree
on which number means which call.
fork: the stub, instruction by instruction
fork is the stub for system call 1, SYS_fork: create a copy of the calling
process. Kernel: sys_fork → kfork.
fork returns twice. The parent returns through this ret with the child’s
PID (process ID) in a0. The child also starts by “returning” from this same ecall:
kfork copies the parent’s trapframe, including the saved program
counter, and sets the child’s saved a0 to 0. When the child first runs, it
returns to user mode right after the ecall, executes this ret, and sees 0.
The line notes explain the three instructions; every other stub in this file works the same way.
Make the label fork visible outside this file (.globl / .global), so the
linker (ld) can connect a C call to fork() in any program to the code below.
The label: the address of the stub’s first instruction. To C, this is the function
fork declared in user/user.h:6.
Load the system call number into a7 (li); the preprocessor has
replaced SYS_fork by 1. a7 is used because it is the last argument register:
up to six real arguments (a0–a5) stay where the C caller put them. The calling
convention lets a called function overwrite a7 freely, so the stub does not need to
save it.
Trap into the kernel (ecall). In user mode, ecall raises an
environment-call exception (cause 8); the hardware switches to supervisor mode, saves
this instruction’s address in sepc, and jumps to the trap handler in stvec, which
is uservec on the trampoline page page. The kernel saves all user
registers, handles the call in usertrap and syscall, writes the
result into the saved a0, and returns to the instruction after ecall: before
handling the call, usertrap adds 4 to the saved program counter
(kernel/trap.c:62), the length of ecall. All other registers come back as they
were.
Return to the C caller (ret); ra still holds the caller’s return
address, since nothing here changed it. The kernel’s result is in a0, where C
expects a function’s return value.
exit: end the calling process
exit is the stub for system call 2, SYS_exit: end the calling process. Kernel:
sys_exit → kexit. This stub’s ecall never returns, so its
ret is never executed. start calls it with main’s return value.
wait: wait for a child process to exit
wait is the stub for system call 3, SYS_wait: wait for a child process to exit.
Kernel: sys_wait → kwait, which may sleep until a child
becomes a zombie.
pipe: create a pipe
pipe is the stub for system call 4, SYS_pipe: create a pipe. Kernel:
sys_pipe → pipealloc. The two new descriptors are written into
the caller’s array with copyout.
read: read bytes from a file descriptor
read is the stub for system call 5, SYS_read: read bytes from a
file descriptor. Kernel: sys_read → fileread, which
dispatches to a pipe, a device such as the console (consoleread), or an
inode.
write: write bytes to a file descriptor
write is the stub for system call 16, SYS_write: write bytes to a
file descriptor. Kernel: sys_write → filewrite. Every
character printed by printf passes through here.
close: release a file descriptor
close is the stub for system call 21, SYS_close: release a file descriptor.
Kernel: sys_close → fileclose.
kill: mark a process as killed
kill is the stub for system call 6, SYS_kill: mark a process as killed. Kernel:
sys_kill → kkill.
exec: replace the program running in this process
exec is the stub for system call 7, SYS_exec: replace the program running in
this process. Kernel: sys_exec → kexec. On success the ecall
does not come back here: the return to user mode lands at the new program’s entry
point, start, and this stub’s ret is never executed. Only a failed exec
returns, with -1.
open: open or create a file by name
open is the stub for system call 15, SYS_open: open or create a file by name.
Kernel: sys_open.
mknod: create a device file
mknod is the stub for system call 17, SYS_mknod: create a device file. Kernel:
sys_mknod.
unlink: remove a name from a directory
unlink is the stub for system call 18, SYS_unlink: remove a name from a
directory. Kernel: sys_unlink.
fstat: get information about an open file
fstat is the stub for system call 8, SYS_fstat: get information about an open
file. Kernel: sys_fstat → filestat. stat in
user/ulib.c builds on it.
link: give a file another name
link is the stub for system call 19, SYS_link: give a file another name. Kernel:
sys_link.
mkdir: create a directory
mkdir is the stub for system call 20, SYS_mkdir: create a directory. Kernel:
sys_mkdir.
chdir: change the current directory
chdir is the stub for system call 9, SYS_chdir: change the current directory.
Kernel: sys_chdir.
dup: duplicate a file descriptor
dup is the stub for system call 10, SYS_dup: duplicate a file descriptor.
Kernel: sys_dup.
getpid: return the caller's process ID
getpid is the stub for system call 11, SYS_getpid: return the caller’s process
ID. Kernel: sys_getpid.
sys_sbrk: grow or shrink the process's memory
sys_sbrk is the stub for system call 12, SYS_sbrk: grow or shrink the process’s
memory. Kernel: sys_sbrk. The label is sys_sbrk, not sbrk
(user/usys.pl:12): the C functions sbrk and sbrklazy in user/ulib.c
call it with a second argument, SBRK_EAGER or SBRK_LAZY, in a1. The kernel
handler happens to have the same name; the two are in different programs and are
never linked together.
pause: sleep for a number of clock ticks
pause is the stub for system call 13, SYS_pause: sleep for a number of clock
ticks. Kernel: sys_pause.
uptime: return the number of clock ticks since boot
uptime is the stub for system call 14, SYS_uptime: return the number of clock
ticks since boot. Kernel: sys_uptime.
sync: wait for an open file-system transaction to commit
sync is the stub for system call 22, SYS_sync: if a file-system
transaction is open or being committed, wait until it has been committed to
disk; otherwise return at once (every finished operation is already on disk). Always
returns 0. Kernel: sys_sync.