Operating systems offer processes running in User Mode a set of interfaces to interact with hardware devices such as the CPU, disks, and printers. Putting an extra layer between the application and the hardware has several advantages. First, it makes programming easier by freeing users from studying low-level programming characteristics of hardware devices. Second, it greatly increases system security, since the kernel can check the accuracy of the request at the interface level before attempting to satisfy it. Last but not least, these interfaces make programs more portable since they can be compiled and executed correctly on any kernel that offers the same set of interfaces.
Unix systems implement most interfaces between User Mode processes and hardware devices by means of system calls issued to the kernel. This chapter examines in detail how Linux implements system calls that User Mode programs issue to the kernel.
Let’s start by stressing the difference between an application programmer interface (API) and a system call. The former is a function definition that specifies how to obtain a given service, while the latter is an explicit request to the kernel made via a software interrupt.
Unix systems include several libraries of functions that provide APIs to programmers. Some of the APIs defined by the libc standard C library refer to wrapper routines (routines whose only purpose is to issue a system call). Usually, each system call has a corresponding wrapper routine, which defines the API that application programs should employ.
The converse is not true, by the way—an API does not
necessarily correspond to a specific system call. First of all, the
API could offer its services directly in User Mode. (For something
abstract like math functions, there may be no reason to make system
calls.) Second, a single API function could make several system
calls. Moreover, several API functions could make the same system
call, but wrap extra functionality around it. For instance, in Linux,
the malloc( )
, calloc( )
, and
free( )
APIs are implemented in the
libc library. The code in this library keeps
track of the allocation and deallocation requests and uses the
brk( )
system call to enlarge or shrink the
process heap (see Section 8.6).
The POSIX standard refers to APIs and not to system calls. A system can be certified as POSIX-compliant if it offers the proper set of APIs to the application programs, no matter how the corresponding functions are implemented. As a matter of fact, several non-Unix systems have been certified as POSIX-compliant, since they offer all traditional Unix services in User Mode libraries.
From the programmer’s point of view, the distinction between an API and a system call is irrelevant — the only things that matter are the function name, the parameter types, and the meaning of the return code. From the kernel designer’s point of view, however, the distinction does matter since system calls belong to the kernel, while User Mode libraries don’t.
Most wrapper routines return an integer value, whose meaning depends
on the corresponding system call. A return value of -1 usually
indicates that the kernel was unable to satisfy the process request.
A failure in the system call handler may be caused by invalid
parameters, a lack of available resources, hardware problems, and so
on. The specific error code is contained in the
errno
variable, which is defined in the
libc library.
Each error code is defined as a macro constant, which yields a
corresponding positive integer value. The POSIX standard specifies
the macro names of several error codes. In Linux, on 80 ×
86 systems, these macros are defined in the header file
include/asm-i386/errno.h
. To allow portability
of C programs among Unix systems, the
include/asm-i386/errno.h
header file is
included, in turn, in the standard
/usr/include/errno.h
C library header file.
Other systems have their own specialized subdirectories of header
files.