user/usys.pl
About this file
A small Perl program that writes the assembly file user/usys.S: one
system call stub for each of xv6’s 22 system calls. The stubs are the only place where
a user program makes a system call (the only ecall instructions in user code); everything in user/user.h that is a system
call is defined by one of them.
The stubs are identical apart from two names, so writing them by hand would be 110 lines
of copy and paste in which a typo (a stub loading the wrong number) would be easy to miss.
Generating them from a list keeps each call to one line. The Makefile runs the script
with perl user/usys.pl > user/usys.S and assembles the result (Makefile:111).
Perl is used here only as a text printer: each print writes one line of the output.
You can see the result, with notes, at user/usys.S.
Read next: user/usys.S, then kernel/syscall.c for the kernel side.
The top of the generated file
Line 1 lets the script be run as a command (./usys.pl) on Unix; -w turns on
Perl’s warnings. The Makefile does not rely on it: it runs perl user/usys.pl.
Lines 5 and 7 print the first two lines of usys.S: a comment warning that the file
is generated (edit this script instead), and an #include of
kernel/syscall.h. The output file’s name ends in capital .S, which tells GCC to
run the C C preprocessor over it before assembling, so the #include works and
names like SYS_fork are replaced by their numbers. This is how user programs and
the kernel share one definition of every system call number.
The “shebang” line: if the file is run directly, the system uses /usr/bin/perl to
run it. -w enables warnings.
A Perl comment saying what the script is for.
Print the first line of the output, an assembler comment (# starts a comment in
RISC-V assembly). \n is a newline.
Print #include "kernel/syscall.h". The inner quotes are escaped with \ so they
become part of the output instead of ending the Perl string. The path is relative to
the top of the source tree, which the Makefile passes as -I..
entry(): print one stub
A Perl subroutine (function) that prints the stub for the system call named by its argument:
.global NAME
NAME:
li a7, SYS_NAME
ecall
ret
.global makes the label visible to the linker (.globl / .global), so a C call to
fork() in any program is connected to the label fork: here. The three
instructions are explained at user/usys.S:5.
The one special case is sbrk, whose label gets a sys_ prefix: the C library
wants the name sbrk for a friendlier wrapper (sbrk), which calls
the stub sys_sbrk with an extra argument. The SYS_ constant keeps the plain name,
SYS_sbrk, because that is what kernel/syscall.h defines.
my declares local variables. shift takes the first argument passed to the
subroutine, the system call’s name, for example "fork".
For sbrk only, print the label as sys_sbrk ($prefix$name joins the two
strings). Perl replaces variables written inside double-quoted strings by their
values.
For every other call the label is the call’s own name, so the C function read() is
the assembly label read:.
Print the instruction that loads the system call number. ${name} is the same as
$name; the braces separate the variable from the text around it. For fork this
prints li a7, SYS_fork, and the preprocessor later turns SYS_fork into 1.
Print ecall, the instruction that traps into the kernel.
Print ret, returning to the C caller with the kernel’s result in a0.
The list of system calls
One call to entry per system call (line 23 contains only a tab). Adding a system
call to xv6 means adding a line here, a declaration in user/user.h, a
SYS_ number in kernel/syscall.h, an entry in the table syscalls, and
the handler itself.
The order of the lines only decides the order of the stubs in the file; the numbers
come from syscall.h. Each line’s kernel handler is listed in the notes on
user/usys.S.
sbrk produces the label sys_sbrk (line 12), the raw system call behind
sbrk and sbrklazy.