Next: Reentrant Syscalls, Up: Syscalls
This is the complete set of system definitions (primarily subroutines)
required; the examples shown implement the minimal functionality
required to allow libc to link, and fail gracefully where OS
services are not available.
Graceful failure is permitted by returning an error code. A minor
complication arises here: the C library must be compatible with
development environments that supply fully functional versions of these
subroutines. Such environments usually return error codes in a global
errno. However, the Red Hat newlib C library provides a macro
definition for errno in the header file errno.h, as part
of its support for reentrant routines (see Reentrancy).
The bridge between these two interpretations of errno is
straightforward: the C library routines with OS interface calls
capture the errno values returned globally, and record them in
the appropriate field of the reentrancy structure (so that you can query
them using the errno macro from errno.h).
This mechanism becomes visible when you write stub routines for OS interfaces. You must include errno.h, then disable the macro, like this:
#include <errno.h>
#undef errno
extern int errno;
The examples in this chapter include this treatment of errno.
_exitexit, system).
close int close(int file) {
return -1;
}
environ char *__env[1] = { 0 };
char **environ = __env;
execve #include <errno.h>
#undef errno
extern int errno;
int execve(char *name, char **argv, char **env) {
errno = ENOMEM;
return -1;
}
fork #include <errno.h>
#undef errno
extern int errno;
int fork(void) {
errno = EAGAIN;
return -1;
}
fstat #include <sys/stat.h>
int fstat(int file, struct stat *st) {
st->st_mode = S_IFCHR;
return 0;
}
getpid int getpid(void) {
return 1;
}
isattystdout, this minimal implementation is suggested:
int isatty(int file) {
return 1;
}
kill #include <errno.h>
#undef errno
extern int errno;
int kill(int pid, int sig) {
errno = EINVAL;
return -1;
}
link #include <errno.h>
#undef errno
extern int errno;
int link(char *old, char *new) {
errno = EMLINK;
return -1;
}
lseek int lseek(int file, int ptr, int dir) {
return 0;
}
open int open(const char *name, int flags, int mode) {
return -1;
}
read int read(int file, char *ptr, int len) {
return 0;
}
sbrkmalloc and related functions
depend on this, it is useful to have a working implementation. The
following suffices for a standalone system; it exploits the symbol
_end automatically defined by the GNU linker.
caddr_t sbrk(int incr) {
extern char _end; /* Defined by the linker */
static char *heap_end;
char *prev_heap_end;
if (heap_end == 0) {
heap_end = &_end;
}
prev_heap_end = heap_end;
if (heap_end + incr > stack_ptr) {
write (1, "Heap and stack collision\n", 25);
abort ();
}
heap_end += incr;
return (caddr_t) prev_heap_end;
}
stat int stat(char *file, struct stat *st) {
st->st_mode = S_IFCHR;
return 0;
}
times int times(struct tms *buf) {
return -1;
}
unlink #include <errno.h>
#undef errno
extern int errno;
int unlink(char *name) {
errno = ENOENT;
return -1;
}
wait #include <errno.h>
#undef errno
extern int errno;
int wait(int *status) {
errno = ECHILD;
return -1;
}
writestdout—so if you need to generate any output, for example to a
serial port for debugging, you should make your minimal write
capable of doing this. The following minimal implementation is an
incomplete example; it relies on a outbyte subroutine (not
shown; typically, you must write this in assembler from examples
provided by your hardware manufacturer) to actually perform the output.
int write(int file, char *ptr, int len) {
int todo;
for (todo = 0; todo < len; todo++) {
outbyte (*ptr++);
}
return len;
}