user/user.h
Included by 23 files
user/cat.c, user/dorphan.c, user/echo.c, user/forktest.c, user/forphan.c, user/grep.c, user/grind.c, user/init.c, user/kill.c, user/ln.c, user/logstress.c, user/ls.c, user/mkdir.c, user/printf.c, user/rm.c, user/sh.c, user/stressfs.c, user/sync.c, user/ulib.c, user/umalloc.c, user/usertests.c, user/wc.c, user/zombie.cAbout this file
The one header every xv6 user program includes: the declarations of everything a program
can call. It plays the role that <unistd.h>, <string.h>, <stdio.h> and <stdlib.h>
play on a Unix system, in 50 lines.
It has four groups, each matching the file that defines it:
| Lines | Group | Defined in |
|---|---|---|
| 6–27 | system calls | user/usys.S (generated stubs) |
| 30–42 | string and helper functions | user/ulib.c |
| 45–46 | formatted output | user/printf.c |
| 49–50 | memory allocation | user/umalloc.c |
For a system call, the declaration is all the C compiler sees; the definition is a
three-instruction system call stub that traps into the kernel. Each line note below
names the kernel function that does the real work, so you can follow any call down, for
example fork() → stub → ecall → syscall → sys_fork →
kfork.
The header is not self-contained: it uses the type uint without defining it, so
programs include kernel/types.h first. A program that reads the fields of a
struct stat also includes kernel/stat.h; for the prototypes, the declaration on
line 3 is enough.
Read next: user/usys.pl and user/usys.S.
The error value of sbrk
sbrk returns an address, so it cannot report failure as an ordinary -1 integer;
it returns the pointer whose bits are all ones, (char *)-1, which can never be the
start of newly added memory. The kernel’s sys_sbrk returns -1 on failure,
and the stub’s char * return type turns that into exactly this value. Unix systems
use the same convention, often spelled (void *)-1.
Declare struct stat without defining it
An incomplete declaration: it tells the compiler that a struct called stat exists,
without listing its fields. That is enough for the prototypes of fstat and stat,
which only pass a pointer to it. A program that wants to read the fields includes
kernel/stat.h, which has the full definition.
The system calls
The 22 system calls of xv6. Their numbers are defined in kernel/syscall.h (the
order here is different, and does not matter). Each name is a label in
user/usys.S; sys_sbrk on line 24 is named differently on purpose.
The types are the user’s view. In the kernel every handler has the signature
uint64 sys_xxx(void): it fetches its arguments itself from the saved registers
(system call arguments) and returns a 64-bit value, which syscall puts
in a0. On return the C caller reads a0 as whatever type this header promises: the
low 32 bits for an int, all 64 for a pointer. Nothing checks that the two sides
agree; this file and the handlers are kept consistent by hand.
Almost every call returns -1 on failure, with no further detail (there is no
errno). Pointers passed in are untrusted by the kernel and copied with
copyin and copyout, so a bad pointer usually produces -1
instead of a crash.
fork(): create a copy of the calling process. Returns the child’s PID (process ID) in the
parent and 0 in the child, or -1. Kernel: sys_fork → kfork.
exit(status): end the calling process; status is passed to the parent’s wait
(exit status). Kernel: sys_exit → kexit.
It never returns, and __attribute__((noreturn)) (attribute) tells the compiler
so. The compiler can then omit code after a call to exit and does not warn about a
function that “falls off the end” after calling it. The int return type is
meaningless for a function that never returns.
wait(&status): wait for a child to exit; return its pid and store its exit status
at the address given (which may be 0 to ignore it). Returns -1 if the caller has no
children. Kernel: sys_wait → kwait.
pipe(fds): create a pipe; store the read end’s descriptor in fds[0] and the
write end’s in fds[1]. Kernel: sys_pipe → pipealloc.
write(fd, buf, n): write n bytes from buf; returns the number written, or -1.
Kernel: sys_write → filewrite.
read(fd, buf, n): read up to n bytes into buf; returns the number read, 0 at end
of file, or -1. Kernel: sys_read → fileread.
close(fd): release a file descriptor. Kernel: sys_close →
fileclose.
kill(pid): mark process pid as killed, and wake it if it is sleeping. The process
exits when the kernel next checks the mark, at the latest before it returns to user
mode. Kernel: sys_kill →
kkill. Unlike Unix kill, there is no signal number.
exec(path, argv): replace the calling process’s program with the one in file
path, passing it the null-terminated argument array argv (exec). Returns
only on failure. Kernel: sys_exec → kexec.
open(path, flags): open or create a file; returns a new descriptor, or -1. The flags
are in kernel/fcntl.h. Kernel: sys_open.
mknod(path, major, minor): create a device file; reading or writing it goes to the
driver selected by major. user/init.c:20 uses it to create console. Kernel:
sys_mknod.
unlink(path): remove a name from a directory; the file itself is deleted when no
name and no open descriptor refers to it any more (link count (nlink)). Kernel:
sys_unlink.
fstat(fd, &st): fill st with information about an open file. Kernel:
sys_fstat → filestat.
link(old, new): give the file old a second name new (hard link). Kernel:
sys_link.
chdir(path): change the calling process’s current directory, the starting point for
relative path names. Kernel: sys_chdir.
dup(fd): return a new descriptor, the lowest free one, that refers to the same
open file (struct file) as fd. Kernel: sys_dup.
getpid(): the calling process’s PID (process ID). Kernel: sys_getpid.
The raw sbrk system call: grow (or shrink) the process’s memory by the first
argument, in the mode given by the second (SBRK_EAGER or SBRK_LAZY from
kernel/vm.h); returns the old end of memory. Kernel: sys_sbrk.
Its stub is called sys_sbrk instead of sbrk (user/usys.pl:12) so that the name
sbrk can be the one-argument wrapper in user/ulib.c. Programs call sbrk or
sbrklazy rather than this. The user-space sys_sbrk and the kernel’s
sys_sbrk share only a name; they are in different programs.
pause(n): sleep for n clock ticks (about a tenth of a second each on QEMU; see
timer interrupt). Returns -1 if the process is killed while waiting. Unix calls
this sleep. Kernel: sys_pause.
uptime(): the number of clock ticks since boot. Kernel: sys_uptime.
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, see write-ahead log). Always returns 0. Kernel:
sys_sync.
Helpers from ulib.c
The functions in user/ulib.c: the C string and memory functions in small
versions, and three helpers built on system calls (stat, gets, and the sbrk
wrappers). The parameter types differ in places from standard C: lengths are uint
or int rather than size_t, and memmove takes an int count while memcpy
takes a uint.
Standard C names are used on purpose, so programs read like ordinary C. Because the
Makefile compiles with -ffreestanding (Makefile:72), which implies
-fno-builtin, GCC does not assume these functions behave like the standard
library’s (it will not, for example, replace a call with its own inline code). The
individual -fno-builtin-... flags in Makefile:74 repeat this for some of the
names and are redundant.
stat(path, &st): like fstat, but by name; implemented in user space with open,
fstat and close.
gets(buf, max): read one line from standard input. The C standard library’s gets
had no size limit and was removed from the language for that reason; this one takes
the buffer size.
sbrk(n) and sbrklazy(n): the two modes of the sys_sbrk system call, as
one-argument functions. sbrk allocates the memory immediately; sbrklazy only
reserves it, and pages are allocated when first touched.
Formatted output, checked by the compiler
printf writes to standard output, fprintf to a chosen
file descriptor.
__attribute__((format(printf, 2, 3))) (attribute) tells GCC that argument 2
is a printf-style format string and the values to format start at argument 3
(for printf, 1 and 2). GCC then checks each call: printf("%d", "hello") produces
a warning, which xv6’s -Werror turns into an error. Without the attribute, a
mismatch would only show up as garbage output or a crash at run time.
fprintf(fd, fmt, ...): formatted output to descriptor fd; the format starts at
argument 2, the values at 3.
printf(fmt, ...): formatted output to descriptor 1, standard output.
Memory allocation
malloc and free from user/umalloc.c, a free-list allocator on top
of sbrk. The size is a uint where standard C uses size_t.