Operating
System Concepts
Most
operating Systems provide certain basic concepts and abstractions such as
Processes, address spaces, and files that are central to understanding them. In
the following sections, we will look at some of these basic concepts ever so
briefly, as an introduction. We will come to each of them in great detail later
in this tutorials. To illustrate these concepts will, from time to time, use
examples, generally drawn from UNIX.
{tocify} $title= {Table of Contents}
1. Processes
A key
concept in all operating Systems is the process.
A process is basically a program in Execution. Associated with each process
is its address space, a list of
memory locations from 0 to some maximum, which the process can read and write.
The address space contains the executable program, the program’s data, and its
stack. Also associated with each process is a set of resources, commonly
including registers (including the program counter and stack pointer), a list
of open files, out-standing alarms, list of related processes, and all the other
information needed to run the program. A process is fundamentally a container
that holds all the information needed to run a program.
For the
time being, the easiest way to get a good intuitive feel for a process is to
think about a multiprogramming system. The user may have started a video
editing program and instructed it to convert a one-hour video to a certain
format (something that can take hours) and then gone off to surf the Web.
Meanwhile, a background process that wakes up periodically to check for incoming
email may have started running. Thus we have (at least) three active processes:
the video editor, the Web browser, and the email receiver. Periodically, the operating
system decides to stop running one process and start running another, perhaps
because the first one has used up more than its share of CPU time in the past
second or two.
When a
process is suspended temporarily like this, it must later be restarted in exactly
the same state it had when it was stopped. This means that all information about
the process must be explicitly saved somewhere during the suspension. For example,
the process may have several files open for reading at once. Associated with
each of these files is a pointer giving the current position (i.e., the number
of the byte or record to be read next). When a process is temporarily
suspended, all these pointers must be saved so that a read call executed after
the process is restarted will read the proper data. In many operating systems,
all the information about each process, other than the contents of its own address
space, is stored in an operating system table called the process table, which is an array of structures, one for each
process currently in existence.
Thus, a
(suspended) process consists of its address space, usually called the core image (in honor of the magnetic
core memories used in days of yore), and its process table entry, which
contains the contents of its registers and many other items needed to restart
the process later.
The key process-management system calls are those
dealing with the creation and termination of processes. Consider a typical
example. A process called the command
interpreter or shell reads commands from a terminal. The user has just typed
a command requesting that a program be compiled. The shell must now create a
new process that will run the compiler. When that process has finished the compilation,
it executes a system call to terminate itself.
If a
process can create one or more other processes (referred to as child processes)
and these processes in turn can create child processes, we quickly arrive at the
process tree structure of Fig. 1. Related processes that are cooperating to get
some job done often need to communicate with one another and synchronize their
activities. This communication is called interprocess
communication.
Other
process system calls are available to request more memory (or release unused
memory), wait for a child process to terminate, and overlay its program with a
different one.
Occasionally,
there is a need to convey information to a running process that is not sitting
around waiting for this information. For example, a process that is
communicating with another process on a different computer does so by sending
messages to the remote process over a computer network. To guard against the
possibility that a message or its reply is lost, the sender may request that
its own operating system notify it after a specified number of seconds, so that
it can retransmit the message if no acknowledgement has been received yet.
After setting this timer, the program may continue doing other work.
When the
specified number of seconds has elapsed, the operating system sends an alarm signal to the process. The signal
causes the process to temporarily suspend whatever it was doing, save its
registers on the stack, and start running a special signal-handling procedure,
for example, to retransmit a presumably lost message. When the signal handler
is done, the running process is restarted in the state it was in just before the
signal. Signals are the software analog of hardware interrupts and can be
generated by a variety of causes in addition to timers expiring. Many traps
detected by hardware, such as executing an illegal instruction or using an
invalid address, are also converted into signals to the guilty process.
Each person
authorized to use a system is assigned a UID
(User Identification) by the system administrator. Every process started
has the UID of the person who started it. A child process has the same UID as
its parent. Users can be members of groups, each of which has a GID (Group Identification).
One UID,
called the superuser (in UNIX), or Administrator (in Windows), has special
power and may override many of the protection rules. In large installations,
only the system administrator knows the password needed to become superuser,
but many of the ordinary users (especially students) devote considerable effort
seeking flaws in the system that allow them to become superuser without the password.
1. Address Spaces
Every computer
has some main memory that it uses to hold executing programs. In a very simple
operating system, only one program at a time is in memory. To run a second
program, the first one has to be removed and the second one placed in memory. More
sophisticated operating systems allow multiple programs to be in memory at the
same time. To keep them from interfering with one another (and with the operating
system), some kind of protection mechanism is needed. While this mechanism has
to be in the hardware, it is controlled by the operating system.
The above
viewpoint is concerned with managing and protecting the computer’s main memory.
A different, but equally important, memory-related issue is managing the
address space of the processes. Normally, each process has some set of
addresses it can use, typically running from 0 up to some maximum. In the
simplest case, the maximum amount of address space a process has is less than
the main memory. In this way, a process can fill up its address space and there
will be enough room in main memory to hold it all.
However, on
many computers addresses are 32 or 64 bits, giving an address space of 232 or
264 bytes, respectively. What happens if a process has more address space than
the computer has main memory and the process wants to use it all? In the first
computers, such a process was just out of luck. Nowadays, a technique called
virtual memory exists, as mentioned earlier, in which the operating system keeps
part of the address space in main memory and part on disk and shuttles pieces
back and forth between them as needed. In essence, the operating system creates
the abstraction of an address space as the set of addresses a process may reference.
The address space is decoupled from the machine’s physical memory and may be
either larger or smaller than the physical memory. Management of address spaces
and physical memory form an important part of what an operating system does.
2. Files
Another key
concept supported by virtually all operating systems is the file system. As
noted before, a major function of the operating system is to hide the peculiarities
of the disks and other I/O devices and present the programmer with a nice,
clean abstract model of device-independent files. System calls are obviously needed
to create files, remove files, read files, and write files. Before a file can
be read, it must be located on the disk and opened, and after being read it
should be closed, so calls are provided to do these things. To provide a place
to keep files, most PC operating systems have the concept of a directory as a
way of grouping files together. A student, for example, might have one
directory for each course he is taking (for the programs needed for that course),
another directory for his electronic mail, and still another directory for his World
Wide Web home page. System calls are then needed to create and remove directories.
Calls are also provided to put an existing file in a directory and to remove a
file from a directory. Directory entries may be either files or other
directories. This model also gives rise to a hierarchy—the file system—as shown
in Fig. 2.
The process
and file hierarchies both are organized as trees, but the similarity stops
there. Process hierarchies usually are not very deep (more than three levels is
unusual), whereas file hierarchies are commonly four, five, or even more levels
deep. Process hierarchies are typically short-lived, generally minutes at most,
whereas the directory hierarchy may exist for years. Ownership and protection
also differ for processes and files. Typically, only a parent process may
control or even access a child process, but mechanisms nearly always exist to
allow files and directories to be read by a wider group than just the owner.
Every file
within the directory hierarchy can be specified by giving its path name from
the top of the directory hierarchy, the root directory. Such absolute path
names consist of the list of directories that must be traversed from the root
directory to get to the file, with slashes separating the components. In Fig.
2, the path for file CS101 is /Faculty/Prof.Brown/Courses/CS101. The leading
slash indicates that the path is absolute, that is, starting at the root
directory. As an aside, in Windows, the backslash (\) character is used as the
separator instead of the slash (/) character (for historical reasons), so the
file path given above would be written as
\Faculty\Prof.Brown\Courses\CS101. Throughout this book we will
generally use the UNIX convention for paths.
At every
instant, each process has a current working directory, in which path names not
beginning with a slash are looked for. For example, in Fig. 1-14, if /Faculty/Prof.Brown
were the working directory, use of the path Courses/CS101 would yield the same
file as the absolute path name given above. Processes can change their working
directory by issuing a system call specifying the new working directory.
Before a
file can be read or written, it must be opened, at which time the permissions
are checked. If the access is permitted, the system returns a small integer called
a file descriptor to use in subsequent operations. If the access is prohibited,
an error code is returned.
Another
important concept in UNIX is the mounted file system. Most desktop computers have
one or more optical drives into which CD-ROMs, DVDs, and Bluray discs can be
inserted. They almost always have USB ports, into which USB memory sticks
(really, solid state disk drives) can be plugged, and some computers have
floppy disks or external hard disks. To provide an elegant way to deal with these
removable media UNIX allows the file system on the optical disc to be attached
to the main tree. Consider the situation of Fig. 3. a. Before the mount call,
the root file system, on the hard disk, and a second file system, on a CDROM,
are separate and unrelated. However, the file system on the CD-ROM cannot be
used, because there is no way to specify path names on it. UNIX does not allow
path names to be prefixed by a drive name or number; that would be precisely
the kind of device dependence that operating systems ought to eliminate. Instead,
the mount system call allows the file system on the CD-ROM to be attached to the
root file system wherever the program wants it to be. In Fig. 1-15(b) the file
system on the CD-ROM has been mounted on directory b, thus allowing access to
files /b/x and /b/y. If directory b had contained any files they would not be accessible
while the CD-ROM was mounted, since /b would refer to the root directory of the
CD-ROM. (Not being able to access these files is not as serious as it at first
seems: file systems are nearly always mounted on empty directories.) If a system
contains multiple hard disks, they can all be mounted into a single tree as
well.
Another
important concept in UNIX is the special file. Special files are provided in
order to make I/O devices look like files. That way, they can be read and written
using the same system calls as are used for reading and writing files. Two kinds
of special files exist: block special files and character special files. Block special
files are used to model devices that consist of a collection of randomly
addressable blocks, such as disks. By opening a block special file and reading,
say, block 4, a program can directly access the fourth block on the device,
without regard to the structure of the file system contained on it. Similarly,
character special files are used to model printers, modems, and other devices
that accept or output a character stream. By convention, the special files are
kept in the /dev directory. For example, /dev/lp might be the printer (once
called the line printer).
The last
feature we will discuss in this overview relates to both processes and files:
pipes. A pipe is a sort of pseudo file that can be used to connect two
processes, as shown in Fig. 1-16. If processes A and B wish to talk using a
pipe, they must set it up in advance. When process A wants to send data to
process B, it writes on the pipe as though it were an output file. In fact, the
implementation of a pipe is very much like that of a file. Process B can read
the data by reading from the pipe as though it were an input file. Thus,
communication between processes in UNIX looks very much like ordinary file
reads and writes. Stronger yet, the only way a process can discover that the
output file it is writing on is not really a file, but a pipe, is by making a
special system call. File systems are very important.
1. Input/ Output
All
computers have physical devices for acquiring input and producing output. After
all, what good would a computer be if the users could not tell it what to do and
could not get the results after it did the work requested? Many kinds of input and
output devices exist, including keyboards, monitors, printers, and so on. It is
up to the operating system to manage these devices. Consequently, every
operating system has an I/O subsystem for managing its I/O devices. Some of the
I/O software is device independent, that is, applies to many or all I/O devices
equally well. Other parts of it, such as device drivers, are specific to
particular I/O devices.
2. Protection
Computers
contain large amounts of information that users often want to protect and keep
confidential. This information may include email, business plans, tax returns,
and much more. It is up to the operating system to manage the system security
so that files, for example, are accessible only to authorized users.
As a simple
example, just to get an idea of how security can work, consider UNIX. Files in
UNIX are protected by assigning each one a 9-bit binary protection code. The
protection code consists of three 3-bit fields, one for the owner, one for
other members of the owner’s group (users are divided into groups by the system
administrator), and one for everyone else. Each field has a bit for read
access, a bit for write access, and a bit for execute access. These 3 bits are
known as the rwx bits. For example, the protection code rwxr-x--x means that
the owner can read, write, or execute the file, other group members can read or
execute (but not write) the file, and everyone else can execute (but not read
or write) the file. For a directory, x indicates search permission. A dash
means that the corresponding permission is absent.
In addition
to file protection, there are many other security issues. Protecting the system
from unwanted intruders, both human and nonhuman (e.g., viruses) is one of
them.
3. The Shell
The
operating system is the code that carries out the system calls. Editors, compilers,
assemblers, linkers, utility programs, and command interpreters definitely are
not part of the operating system, even though they are important and useful. At
the risk of confusing things somewhat, in this section we will look briefly at
the UNIX command interpreter, the shell. Although it is not part of the
operating system, it makes heavy use of many operating system features and thus
serves as a good example of how the system calls are used. It is also the main
interface between a user sitting at his terminal and the operating system,
unless the user is using a graphical user interface. Many shells exist, including
sh, csh, ksh, and bash. All of them support the functionality described below,
which derives from the original shell (sh). When any user logs in, a shell is
started up. The shell has the terminal as standard input and standard output.
It starts out by typing the prompt, a character such as a dollar sign, which
tells the user that the shell is waiting to accept a command. If the user now
types
date
for
example, the shell creates a child process and runs the date program as the child.
While the child process is running, the shell waits for it to terminate. When the
child finishes, the shell types the prompt again and tries to read the next
input line.
The user
can specify that standard output be redirected to a file, for example,
date
>file
Similarly,
standard input can be redirected, as in
sort
<file1 >file2
which
invokes the sort program with input taken from file1 and output sent to file2. The
output of one program can be used as the input for another program by connecting
them with a pipe. Thus
cat file1
file2 file3 | sort >/dev/lp
invokes the
cat program to concatenate three files and send the output to sort to arrange all
the lines in alphabetical order. The output of sort is redirected to the file
/dev/lp,
typically the printer.
If a user
puts an ampersand after a command, the shell does not wait for it to complete.
Instead it just gives a prompt immediately. Consequently,
cat file1
file2 file3 | sort >/dev/lp &
starts up
the sort as a background job, allowing the user to continue working normally
while the sort is going on. The shell has a number of other interesting
features, which we do not have space to discuss here. Most books on UNIX discuss
the shell at some length (e.g., Kernighan and Pike, 1984; Quigley, 2004;
Robbins, 2005).
Most
personal computers these days use a GUI. In fact, the GUI is just a program
running on top of the operating system, like a shell. In Linux systems, this fact
is made obvious because the user has a choice of (at least) two GUIs: Gnome and
KDE or none at all (using a terminal window on X11). In Windows, it is also possible
to replace the standard GUI desktop (Windows Explorer) with a different program
by changing some values in the registry, although few people do this.
7. Ontogeny Recapitulates Phylogeny
After
Charles Darwin’s book On the Origin of the Species was published, the German
zoologist Ernst Haeckel stated that ‘‘ontogeny recapitulates phylogeny.’’ By
this he meant that the development of an embryo (ontogeny) repeats (i.e.,
recapitulates) the evolution of the species (phylogeny). In other words, after
fertilization, a human egg goes through stages of being a fish, a pig, and so
on before turning into a human baby. Modern biologists regard this as a gross
simplification, but it still has a kernel of truth in it.
Something
vaguely analogous has happened in the computer industry. Each new species
(mainframe, minicomputer, personal computer, handheld, embedded computer, smart
card, etc.) seems to go through the development that its ancestors did, both in
hardware and in software. We often forget that much of what happens in the
computer business and a lot of other fields is technology driven. The reason the
ancient Romans lacked cars is not that they liked walking so much. It is
because they did not know how to build cars. Personal computers exist not
because millions of people have a
centuries-old pent-up desire to own a computer, but because it is now possible
to manufacture them cheaply. We often forget how much technology affects our
view of systems and it is worth reflecting on this point from time to time.
In
particular, it frequently happens that a change in technology renders some idea
obsolete and it quickly vanishes. However, another change in technology could
revive it again. This is especially true when the change has to do with the relative
performance of different parts of the system. For instance, when CPUs became
much faster than memories, caches became important to speed up the ‘‘slow’’
memory. If new memory technology someday makes memories much faster than CPUs,
caches will vanish. And if a new CPU technology makes them faster than memories
again, caches will reappear. In biology, extinction is forever, but in computer
science, it is sometimes only for a few years.
As a
consequence of this impermanence, in this book we will from time to time look
at ‘‘obsolete’’ concepts, that is, ideas that are not optimal with current
technology. However, changes in the technology may bring back some of the so-called
‘‘obsolete concepts.’’ For this reason, it is important to understand why a concept
is obsolete and what changes in the environment might bring it back again.
To make
this point clearer, let us consider a simple example. Early computers had
hardwired instruction sets. The instructions were executed directly by hardware
and could not be changed. Then came microprogramming (first introduced on a
large scale with the IBM 360), in which an underlying interpreter carried out the
‘‘hardware instructions’’ in software. Hardwired execution became obsolete. It was
not flexible enough. Then RISC computers were invented, and microprogramming
(i.e., interpreted execution) became obsolete because direct execution was
faster. Now we are seeing the resurgence of interpretation in the form of Java applets
that are sent over the Internet and interpreted upon arrival. Execution speed is
not always crucial because network delays are so great that they tend to
dominate. Thus the pendulum has already swung several cycles between direct
execution and interpretation and may yet swing again in the future.
·
Large Memories
Let us now
examine some historical developments in hardware and how they have affected
software repeatedly. The first mainframes had limited memory. A fully loaded
IBM 7090 or 7094, which played king of the mountain from late 1959 until 1964,
had just over 128 KB of memory. It was mostly programmed in assembly language
and its operating system was written in assembly language to save precious
memory.
As time
went on, compilers for languages like FORTRAN and COBOL got good enough that
assembly language was pronounced dead. But when the first commercial
minicomputer (the PDP-1) was released, it had only 4096 18-bit words of memory,
and assembly language made a surprise comeback. Eventually, minicomputers
acquired more memory and high-level languages became prevalent on them.
When
microcomputers hit in the early 1980s, the first ones had 4-KB memories and
assembly-language programming rose from the dead. Embedded computers often used
the same CPU chips as the microcomputers (8080s, Z80s, and later 8086s) and
were also programmed in assembler initially. Now their descendants, the
personal computers, have lots of memory and are programmed in C, C++, Java, and
other high-level languages. Smart cards are undergoing a similar development,
although beyond a certain size, the smart cards often have a Java interpreter
and execute Java programs interpretively, rather than having Java being compiled
to the smart card’s machine language.
·
Protection Hardware
Early
mainframes, like the IBM 7090/7094, had no protection hardware, so they just
ran one program at a time. A buggy program could wipe out the operating system
and easily crash the machine. With the introduction of the IBM 360, a primitive
form of hardware protection became available. These machines could then hold
several programs in memory at the same time and let them take turns running
(multiprogramming). Monoprogramming was declared obsolete.
At least
until the first minicomputer showed up—without protection hardware—so
multiprogramming was not possible. Although the PDP-1 and PDP-8 had no
protection hardware, eventually the PDP-11 did, and this feature led to
multiprogramming and eventually to UNIX. When the first microcomputers were
built, they used the Intel 8080 CPU chip, which had no hardware protection, so
we were back to monoprogramming—one program in memory at a time. It was not
until the Intel 80286 chip that protection hardware was added and
multiprogramming became possible. Until this day, many embedded systems have no
protection hardware and run just a single program.
Now let us
look at operating systems. The first mainframes initially had no protection
hardware and no support for multiprogramming, so they ran simple operating
systems that handled one manually loaded program at a time. Later they acquired
the hardware and operating system support to handle multiple programs at once,
and then full timesharing capabilities.
When
minicomputers first appeared, they also had no protection hardware and ran one
manually loaded program at a time, even though multiprogramming was well
established in the mainframe world by then. Gradually, they acquired protection
hardware and the ability to run two or more programs at once. The first microcomputers
were also capable of running only one program at a time, but later acquired the
ability to multiprogram. Handheld computers and smart cards went the same
route.
In all
cases, the software development was dictated by technology. The first microcomputers,
for example, had something like 4 KB of memory and no protection hardware.
High-level languages and multiprogramming were simply too much for such a tiny
system to handle. As the microcomputers evolved into modern personal computers,
they acquired the necessary hardware and then the necessary software to handle
more advanced features. It is likely that this development will continue for
years to come. Other fields may also have this wheel of reincarnation, but in
the computer industry it seems to spin faster.
·
Disks
Early
mainframes were largely magnetic-tape based. They would read in a program from
tape, compile it, run it, and write the results back to another tape. There were
no disks and no concept of a file system. That began to change when IBM introduced
the first hard disk—the RAMAC (Random Access) in 1956. It occupied about 4
square meters of floor space and could store 5 million 7-bit characters, enough
for one medium-resolution digital photo. But with an annual rental fee of
$35,000, assembling enough of them to store the equivalent of a roll of film
got pricey quite fast. But eventually prices came down and primitive file
systems were developed.
Typical of
these new developments was the CDC 6600, introduced in 1964 and for years by
far the fastest computer in the world. Users could create so-called ‘‘permanent
files’’ by giving them names and hoping that no other user had also decided
that, say, ‘‘data’’ was a suitable name for a file. This was a single-level
directory. Eventually, mainframes developed complex hierarchical file systems,
perhaps culminating in the MULTICS file system.
As
minicomputers came into use, they eventually also had hard disks. The standard
disk on the PDP-11 when it was introduced in 1970 was the RK05 disk, with a
capacity of 2.5 MB, about half of the IBM RAMAC, but it was only about 40 cm in
diameter and 5 cm high. But it, too, had a single-level directory initially. When
microcomputers came out, CP/M was initially the dominant operating system, and
it, too, supported just one directory on the (floppy) disk.
·
Virtual Memory
Virtual
memory gives the ability to run programs larger than the machine’s physical
memory by rapidly moving pieces back and forth between RAM and disk. It
underwent a similar development, first appearing on mainframes, then moving to
the minis and the micros. Virtual memory also allowed having a program dynamically
link in a library at run time instead of having it compiled in. MULTICS was the
first system to allow this. Eventually, the idea propagated down the line and
is now widely used on most UNIX and Windows systems.
In all these developments, we see ideas invented in one context and later thrown out when the context changes (assembly-language programming, monoprogramming, single-level directories, etc.) only to reappear in a different context often a decade later. For this reason in this book we will sometimes look at ideas and algorithms that may seem dated on today’s gigabyte PCs, but which may soon come back on embedded com