CS312 Lec 2
CS312 Lec 2
PROGRAMMING
LECTURE # 2
FUNDAMENTAL CONCEPTS
1
AGENDA FOR TODAY
2
SYSTEM CALLS
A system call is a controlled entry point into the kernel, allowing a process to
request that the kernel perform some action on the process’s behalf. The kernel
makes a range of services accessible to programs via the system call application
programming interface (API). These services include, for example, creating a new
process, performing I/O, and creating a pipe for interprocess communication.
Before going into the details of how a system call works, we note some general
points:
A system call changes the processor state from user mode to kernel mode, so
that the CPU can access protected kernel memory.
The set of system calls is fixed. Each system call is identified by a unique
number. (This numbering scheme is not normally visible to programs, which
identify system calls by name.)
Each system call may have a set of arguments that specify information to be
transferred from user space to kernel space and vice versa.
3
INVOKING A SYSTEM CALL
4
INVOKING A SYSTEM CALL
From a programming point of view, invoking a system call looks much like calling a C
function. To illustrate this, we consider the steps in the order that they occur on a
specific hardware implementation, the x86-32. The steps are as follows:
1. The application program makes a system call by invoking a wrapper function in the
C library.
2. The wrapper function must make all of the system call arguments available to the
system call trap-handling routine. These arguments are passed to the wrapper via
the stack, but the kernel expects them in specific registers. The wrapper function
copies the arguments to these registers.
3. Since all system calls enter the kernel in the same way, the kernel needs some
method of identifying the system call. To permit this, the wrapper function copies
the system call number into a specific CPU register.
4. The wrapper function executes a trap machine instruction, which causes the
processor to switch from user mode to kernel mode and execute code pointed to
by location 0x80 of the system’s trap vector.
5
INVOKING A SYSTEM CALL…
5. In response to the trap to location 0x80, the kernel invokes its system_call() routine to
handle the trap. This handler:
a) Saves register values onto the kernel stack .
b) Checks the validity of the system call number.
c) Invokes the appropriate system call service routine, which is found by using the system
call number to index a table of all system call service routines (the kernel variable
sys_call_table). If the system call service routine has any arguments, it first checks their
validity; for example, it checks that addresses point to valid locations in user memory. Then
the service routine performs the required task, which may involve modifying values at
addresses specified in the given arguments and transferring data between user memory and
kernel memory. Finally, the service routine returns a result status to the system_call() routine.
d) Restores register values from the kernel stack and places the system call return value
on the stack.
e) Returns to the wrapper function, simultaneously returning the processor to user mode.
6. If the return value of the system call service routine indicated an error, the wrapper
function sets the global variable errno using this value. The wrapper function then returns
to the caller, providing an integer return value indicating the success or failure of the
6
system call.
LIBRARY FUNCTIONS
A library function is simply one of the multitude of functions that constitutes the
standard C library.
The purposes of these functions are very diverse, including tasks as opening a file,
converting a time to a human-readable format, and comparing two character
strings.
Many library functions don’t make any use of system calls (e.g., the string
manipulation functions).
On the other hand, some library functions are layered on top of system calls. For
example, the fopen() library function uses the open() system call to actually open a
file.
Often, library functions are designed to provide a more caller-friendly interface
than the underlying system call. For example, the printf() function provides output
formatting and data buffering, whereas the write() system call just outputs a block
of bytes.
Similarly, the malloc() and free() functions perform various bookkeeping tasks that
make them a much easier way to allocate and free memory than the underlying
7
brk() system call.
THE STANDARD C LIBRARY;
THE GNU C LIBRARY (GLIBC)
8
HANDLING ERRORS FROM SYSTEM CALLS
AND LIBRARY FUNCTIONS
Almost every system call and library function returns some type of status value
indicating whether the call succeeded or failed.
This status value should always be checked to see whether the call succeeded. If
it did not, then appropriate action should be taken—at the very least, the
program should display an error message warning that something unexpected
occurred.
Although it is tempting to save typing time by excluding these checks, it is a false
economy. Many hours of debugging time can be wasted because a check was
not made on the status return of a system call or library function that “couldn’t
possibly fail.”
9
HANDLING SYSTEM CALL ERRORS
The manual page for each system call documents the possible return values of the call, showing which value(s)
indicate an error. Usually, an error is indicated by a return of –1.
When a system call fails, it sets the global integer variable errno to a positive value that identifies the specific
error.
Including the <errno.h> header file provides a declaration of errno, as well as a set of constants for the various
error numbers. The section headed ERRORS in each manual page provides a list of possible errno values that
can be returned by each system call.
Successful system calls and library functions never reset errno to 0, so this variable may have a nonzero value as
a consequence of an error from a previous call.
Therefore, when checking for an error, we should always first check if the function return value indicates an error,
and only then examine errno to determine the cause of the error.
A few system calls (e.g., getpriority()) can legitimately return –1 on success. To determine whether an error
occurs in such calls, we set errno to 0 before the call, and then check it afterward.
If the call returns –1 and errno is nonzero, an error occurred.
A common course of action after a failed system call is to print an error message based on the errno value. The
10
perror() and strerror() library functions are provided for this purpose. The perror() function prints the string pointed
to by its msg argument, followed by a message corresponding to the current value of errno.
HANDLING ERRORS FROM LIBRARY FUNCTIONS
The various library functions return different data types and different values to
indicate failure. For our purposes, library functions can be divided into the following
categories:
Some library functions return error information in exactly the same way as system
calls: a –1 return value, with errno indicating the specific error. An example of such
a function is remove(), which removes a file (using the unlink() system call) or a
directory (using the rmdir() system call). Errors from these functions can be
diagnosed in the same way as errors from system calls.
Some library functions return a value other than –1 on error, but nevertheless set
errno to indicate the specific error condition. For example, fopen() returns a NULL
pointer on error, and the setting of errno depends on which underlying system call
failed. The perror() and strerror() functions can be used to diagnose these errors.
Other library functions don’t use errno at all. The method for determining the
existence and cause of errors depends on the particular function and is
documented in the function’s manual page. For these functions, it is a mistake to
use errno, perror(), or strerror() to diagnose errors. 11
THE CORE OPERATING SYSTEM: THE KERNEL
By contrast, a running system has one kernel that knows and controls everything. The kernel facilitates
the running of all processes on the system. The kernel decides which process will next obtain access to
the CPU, when it will do so, and for how long.
The kernel maintains data structures containing information about all running processes and updates
these structures as processes are created, change state, and terminate.
The kernel maintains all of the low-level data structures that enable the filenames used by programs to
be translated into physical locations on the disk.
The kernel also maintains data structures that map the virtual memory of each process into the physical
memory of the computer and the swap area(s) on disk.
All communication between processes is done via mechanisms provided by the kernel.
In response to requests from processes, the kernel creates new processes and terminates existing
processes.
Lastly, the kernel performs all direct communication with input and output devices, transferring
information to and from user processes as required.
14
SINGLE DIRECTORY HIERARCHY, DIRECTORIES,
LINKS, AND FILES
15
References:
16
THE END
LECTURE # 2
17