Editor’s note: Sometimes we can forget how important it is to truly understand how a system works, and how the nuts and bolts affect our everyday activities. Marty Kalin asks us to dive a little deeper and strengthen our computational thinking abilities.
It’s clear that applications need system resources to execute: a processor, memory, and usually I/O devices such as the keyboard and screen. It’s less clear how applications gain access to these shared resources, which are under operating system (OS) control. The OS, like any good manager, is efficient and unobtrusive as it handles resource requests from applications. Let’s take a look at how applications interact with the OS, in both routine and dramatic fashion.
Consider what happens when a print statement executes. Here’s a Ruby example:
puts 'Hello, world!'
The Ruby puts
statement wraps a call to a high-level I/O function in the standard C library (in this case, printf
), which acts as the interface between resource-requesting applications and resource-granting OS routines. In this example, the screen is the requested resource. The standard library interacts seamlessly with the OS, which also is written in C with some assembly language. The library function printf
is high-level because, as the f
in the name indicates, the function can format the bytes to be written as integers, floating-point values, and character strings such as Hello, world!. In systems-speak, the Ruby application and the C library function execute in user space, which does not bestow the rights and privileges needed to control system resources such as the screen.
The printf
call only starts the request process, which progresses to a low-level, byte-oriented library function named write
:
write(1, "Hello, world!\n", 14);
The first argument to write
is a file descriptor, an integer value that identifies a file: the 1
in this example identifies the standard output, which defaults to the screen. In a modern OS, any I/O device counts as a file. The second argument is a string, in C an array of 1-byte char
elements. An 8-bit char
accommodates 7-bit ASCII character codes, which are in play here. The last argument, 14
, is the number of bytes to be written. The write
call, in turn, invokes an OS routine that executes in kernel space, which bestows the very rights and privileges needed to control system resources such as the screen. If the write
call succeeds, it returns 14 in this example — the number of bytes written to the screen. Should the request fail, write
would return -1, a distinctive value often used to signal an error: -1 in binary is all 1s.

Figure 1. Routine system calls
In summary, a routine print statement in a high-level language such as Ruby goes through a standard C library function such as printf
, which leads to a system-level function such as write
, which in turn invokes an OS routine charged with managing a shared resource such as the screen (see Figure 1). The Ruby call to puts
is, in effect, an OS request that terminates in a system call, which either grants or denies the request.
Explicit System Calls
The special library function syscall
allows C code to fine-tune a system call in a way that an ordinary library function either disallows outright or makes cumbersome. The aptly named syscall
function takes a variable number of arguments, from 0 to 5 depending on the particular call.
Example 1.1 An explicit system call
#include <unistd.h>
#include <sys/syscall.h>
#include <errno.h>
#include <stdio.h>
void main() {
/* 755 means: owner has read/write/execute permissions
others have read/execute permissions */
int perms = 0755;
int status = syscall(SYS_chmod, "/usr/local/website", perms);
if (-1 == status) printf("chmod failed: errno = %i\n", errno);
else printf("chmod succeeded\n");
}
The callExplicit program (see Example 1.1) shows the syntax for syscall
. The call targets the OS routine chmod
, which has a wrapper with the same name in the standard library. The example is contrived in that the wrapper function chmod
could be invoked directly. In this example, the arguments to the variadic syscall
are:
-
SYS_chmod
: This is a symbolic constant (in C, a macro) for an integer value that identifies the system function to be called, in this casechmod
(change mode). Thechmod
function can change the access permissions for files, including directories. -
/usr/local/website
: This is a directory, a file that can contain other files; the directory’s access permissions are to be changed through syscall. The name website is meant to suggest that the directory holds executable scripts of the kind often used in websites to deliver dynamic content. The directory name is arbitrary, but the directory must exist to avoid an error. -
perms
: This is an integer whose value,0755
, represents read/write/execute permissions for the owner of the directory and read/execute permissions for others. The value is in octal, as signaled by the leading0
.
The syscall
function returns an integer value as a status code. By convention, a returned value of 0
represents a successful call, and a returned negative value signals an error. The syscall
function always returns -1
to signal an error; the particular error code then is stored in the errno
variable. There are symbolic constants for such error codes. For example, an errno
value of EPERM
, which is 1, means no permission for the operation; an errno
value of ENOENT
, which is 2, means no such entity.
Application/system interaction can be more dramatic than the examples shown so far. A user might terminate an application by entering Control-C from the keyboard or by executing a kill action from the command-line or another program. An executing program that generates an out-of-bounds address (e.g., by falling off the end of an array) generates a general-protection fault, which usually causes the OS to terminate the program at once. Let’s take a look at system calls that change a program’s normal flow of control.
System Calls and Exceptional Flow of Control
High-level languages such as C++, Java, and C# have a programming construct built around the concept of an exception, which abruptly changes the normal flow of program control. In these languages, an executing statement throws an exception either explicitly (by using, for example, a throw statement) or implicitly (by trying, for example, to open a non-existent file for reading).
Figure 2.1 Exceptional flow of control
try {
s1 ;; throws an exception //line 1
s2
s3
}
catch(Exception e) {print(e)} //line 2
The try
code segment (see Figure 2.1) is a pseudo-code depiction of the exception-handling construct. Any statement in the try
block might throw an exception, which alters the usual flow of control. In this example, the normal flow of control is from statement s1
to s2
to s3
. Suppose that statement s1
tries to open a non-existent disk file, which generates an I/O exception (line 1). The exception halts the normal flow of control: statement s2
does not execute because control switches instead to the print
statement in the body of the catch
block, which is at the end of the try
block (line 2). In this example, the exception can be described as synchronous because the change in the flow of control results from — is synchronized with — a particular executing statement in the program, in this case s1
.
Not every abrupt change in the control flow is synchronous. For example, if program P is started at the command line, then entering control-C from the keyboard usually terminates P‘s execution: P aborts at once, which is another example of an abrupt change in control flow. An abort might be described as asynchronous to underscore that something external to the program — and, therefore, something
unpredictable — alters control flow.
The terms used to describe abrupt change in the flow of control do not have fixed, standard meanings. Nonetheless, these traditional terms are worth clarifying:
-
Interrupt
An interrupt results from a signal that an I/O device generates. Recall the example of entering control-C from the keyboard, which is an I/O device. This generates a signal to abort an executing program. An OS interrupt handler, a system routine, responds by terminating the program. An interrupt in this sense is asynchronous: the interrupt results from an event external to the executing program.
-
Trap
A typical system call starts with a call to an ordinary library function such as
printf
, which eventually results in the execution of OS code. The situation can be expressed in a different way, using the language of traps. The call toprintf
results in an event called a trap, which an OS trap handler processes by mapping the library call to the appropriate OS code. A trap in this sense is synchronous: the trap results from the execution of a specific instruction in the application code.The next two types can seen either as trap subtypes or as types in their own right. In either case, the distinction can be introduced with an similar one in Java. The Java
Throwable
type has two subtypes. AnException
is generally recoverable; hence, an application should try to catch an exception, such as aFileNotFoundException
, and handle the exception some some appropriate way, for example, by reverting to a default file. By contrast, anError
is serious enough that an application typically should not try to catch and recover. For example, given anOutOfMemoryError
, there is no way for an application on its own to recover from this condition; hence, there is no point in trying to catch and handle such an error. The difference between a fault and an abort resembles the one between anException
and anError
, although the correspondence is not exact.-
Fault
A fault is condition from which an application typically can recover, but recovery is not a certainty. Suppose, for example, that an executing application references data or instructions that are not currently in memory. This results in a page fault, where a page is a fixed-length block of bytes moved between main memory and disk. (On an Intel box, the standard page is about 4K bytes.) The fault is recoverable in that the OS loads the required page into memory, thereby allowing the application to resume execution.
In some cases, however, faults are not recoverable. For example, an OS generates a general protection fault if an executing application tries to reference a memory location outside of the application’s address space. An application cannot recover from this fault.
-
Abort
An abort is a serious condition from which an application cannot recover. The general protection fault noted above might be renamed a general protection abort because the offending application is terminated when it generates an abort.
-
Aborts in particular underscore that not every request from an application to the OS is honored. If an application requests access to an out-of-bounds memory location, the OS normally responds with an emphatic no in the form of an abort. The possibility of failure is baked into every system call, although system calls may become so routine that they are expected to succeed.
It’s time to summarize. In routine application/system interaction, an application calls a standard library function using the language’s ordinary syntax; the Ruby call to puts is a case in point. The application and the library function execute in user space, that is, with no OS rights and privileges. The library call results in a system call, which invokes OS code that runs in kernel space. Once the OS call completes successfully, the application continues as usual. The application/system interaction turns dramatic when such interaction alters the application’s normal control flow, with an application abort as the most dramatic outcome.