Cosc411 Summary
Cosc411 Summary
0 Main Content
2.1. What is an Operating System
You are once again welcome to this class, where we shall discuss Operating System (OS). You
can simply define Operating System (OS) as a set of
computer programs that manage the hardware and software resources of a
computer. It is the core of computer programming that primarily deals with computer
architecture. It is basically an application program that serves as an interface to coordinate
different resources
of computer. It processes raw system and user input and responds by allocating and managing
tasks and internal system resources as a service to you as a user and programs of the system. You
can also say that an OS can be defined as a suite (set) of
firmware (hardwired instructions on chips usually in ROM) or both that makes the hardware
usable.
Note that at the foundation of all system software, an operating system performs
basic tasks such as controlling and allocating memory, prioritizing system
requests, controlling input and output devices, facilitating networking and
managing file systems. Most operating systems come with an application that provides you an
interface to the OS managed resources. These applications have had command line interpreters
as a basic user interface, but more recently have been implemented as a graphical user interface
(GUI) for ease of operation. Operating Systems themselves have no user interfaces, and the user
of an OS is an application, not a person. The operating system forms a platform for other system
software and for application software. Windows, Linux, and Mac OS are some of the most popular
OSs.
2.3. Views of OS
OS is viewed from the perspective of what they are. These views are diverse depending on the
particular view point of a user. But some of these views are discussed below.
a) OS as a User/Computer Interface
A computer system is viewed as a layered or hierarchical structure consisting of the hardware,
operating system, utilities, application programs and users.
The users of application programs are called the end-users and are generally not concerned with
the computer‘s architecture. The end-user views the computer system in terms of an application.
The application is developed by the application programmer who uses a programming language
and a language translator. A set of programs called the utilities is provided to assist the
programmer in program creation, file management and the control of Input
/Output (I/O) devices.
The most important system program; operating system masks the details of the hardware from
the programmer and provides a convenient interface for using the system. It acts as a mediator,
making it easier for the programmer and for application programs to access and use the available
services and facilities.
b) OS as a Resource Manager
A computer system has a set of resources for the movement, storage and processing of data as
shown in Figure 2.1 above. The OS is responsible for managing these resources. Note that
resources include CPU, file storage space, data, programs, memory space, I/O devices, etc. The
OS is like any other computer program in that it provides instructions for the processor(s). The key
difference is in the purpose of the program. The OS directs the processor(s) in the use of the
other system resources and in the timing of its execution of other programs. The processor(s), in
order to do any of these things, must cease execution of the OS program to execute other
programs. Thus, the OS relinquishes control long enough to prepare the processor(s) to do the
next piece of work. The portion of the OS that is always in main memory is called the
kernel or nucleus and it contains the most frequently used functions in the OS. The remainder of
the main memory contains other user programs and data. The allocation of this resource (i.e. main
memory) is controlled jointly by the OS and the memory management hardware in the
processor(s).
iii. Access to I/O devices: Each I/O device requires its own set of instructions or control signal
for operation. The OS takes care of the details so that the programmer can think in terms of reads
and writes.
iv. Controlled Access: In the case of files, control includes an understanding of the nature of
the I/O device (e.g. diskette drive, CDROM drive, etc.) as well as the file format of the storage
medium. The OS deals with these details. In the case of the multi-user system, the OS must
provide protection mechanisms to control access to the files.
v. Communications: There are many instances in which a process needs to exchange
information with another process. There are two major ways in which communication can occur:
a. It can take place between processes executing on the same computer.
b. It can take place between processes executing on different computer systems that are
linked by a computer network. Communications may be implemented via a shared memory or by
a technique of message passing in which packets of information are moved between processes
by the OS.
vi. Error Detection: A variety of errors can occur while a computer system is running. These
errors include:
a. CPU and memory hardware error: This encompasses memory error, power failure, a device
failure such as connection failure on a network, lack of paper in printer.
b. Software errors: Arithmetic overflow, attempt to access forbidden memory locations,
inability of the OS to grant the request of an application.
In each case, the OS must make a response that makes the less impact on running applications.
The response may range from ending the program that
caused the error, retrying the operation or simply reporting the error to the application.
b) Efficiency of System: Single and Multi-User
In the area of system efficiency, the OS offers the following services:
i. System Access or Protection: In the case of a shared or public system, the OS controls
access to the system and to specific system resources by ensuring that each user authenticates
him/herself to the system, usually by means of passwords to be allowed access to system
resources. It extends to defending external I/O devices including modems, network adapters
from invalid access attempts and to recording all such connections for detection of break-ins.
ii. Resources Allocation: In an environment where there multiple users or multiple jobs
running at the same time, resources must be allocated to each of them. Many different types of
resources are managed by the OS. Some (such as CPU cycles, main memory and file storage) may
have general request and release codes. For instances, in determining how best to use the CPU,
the OS have CPU-scheduling routines that take into account the speed of the CPU, the jobs that
must be executed, the number of registers available and other factors. These routines may also
be used to allocate plotters, modems and other peripheral devices.
iii. Accounting: This helps to keep track of how much of and what types of computer
resources are used by each user. Today, this record keeping is not for billing purposes but for
simply accumulating usage statistics. This statistics may be available tool for researchers who want
to reconfigure the system to improve computing services.
iv. Ease of Evolution of OS: A major OS will evolve over time for a number of reasons such as
hardware upgrades and new types of hardware e.g.: The use of graphics terminals may affect OS
design.
This is because such a terminal may allow the user to view several applications at the same time
through ‗windows‘ on the screen. This requires more sophisticated support in the OS.
v. New Services: In response to user demands or the need of system managers, the OS may
expand to offer new services.
vi. Error correction: The OS may have faults which may be discovered over the course of time
and fixes will need to be made.
Other features provided by the OS include:
1. Defining the user interface
2. Sharing hardware among users
3. Allowing users to share data
4. Scheduling resources among users
5. Facilitating I/O
6. Recovering from errors. Etc.
The OS interfaces with, programs, hardware, users such as administrative personnel, computer
operators, application programmers, system programmers, etc.
STUDY SESSION 3
The Kernel
maintainability and modularity of the codebase. A range of possibilities exists between these two
extremes.
Most operating systems rely on the kernel concept. The existence of a kernel is a natural
consequence of designing a computer system as a series of abstraction layers, each relying on the
functions of layers beneath itself. The kernel, from this viewpoint, is simply the name given to the
lowest level of abstraction that is implemented in software. In order to avoid having a kernel, one
would have to design all the software on the system not to use abstraction layers; this would
increase the complexity of the design to such a point that only the simplest systems could feasibly
be implemented.
While it is today mostly called the kernel, the same part of the operating system has also in the
past been known as the nucleus or core. (You should note, however, that the term core has also
been used to refer to the primary memory of a computer system, typically because some early
computers used a form of memory called Core memory.) In most cases, the boot loader starts
executing the kernel in supervisor mode. The kernel then initializes itself and starts the first
process. After this, the kernel does not typically execute directly, only in response to external
events (e.g. via system calls used by applications to request services from the kernel, or via
interrupts used by the hardware to notify the kernel of events). Additionally, the kernel typically
provides a loop that is executed whenever no processes are available to run; this is often called
the idle process.
Kernel development is considered one of the most complex and difficult tasks in programming.
Its central position in an operating system implies the necessity for good performance, which
defines the kernel as a critical piece of software and makes its correct design and implementation
difficult. For various reasons, a kernel might not even be able to use the abstraction mechanisms
it provides to other software. Such reasons include memory management concerns (for example,
a user-mode function might rely on memory being subject to demand paging, but as the kernel
itself provides that facility, it cannot use it because then it might not remain in memory to provide
that facility) and lack of reentrancy, thus making its development even more difficult for software
engineers.
A kernel will usually provide features for low-level scheduling of processes (dispatching), Inter-
process communication, process synchronization, context
switch, manipulation of process control blocks, interrupt handling, process creation and
destruction, process suspension and resumption (see process states in the next module).
the point where the application does not need to know implementation details of the device)
Kernels also usually provide methods for synchronization and communication between processes
(called inter-process communication or IPC). This is discussed in module 3. A kernel may
implement these features itself, or rely on some of the processes it runs to provide the facilities to
other processes, although in this case it must provide some means of IPC to allow processes to
access the facilities provided by each other. Finally, a kernel must provide running programs with
a method to make requests to access these facilities.
2.2.1 Process Management
The main task of a kernel is to allow the execution of applications and support them with features
such as hardware abstractions. To run an application, a kernel typically sets up an address space
for the application, loads the file containing the application's code
into memory (perhaps via demand paging), sets up a stack for the program and branches to a
given location inside the program, thus
starting its execution. Fig 1.3.3: Process Management
Multi-tasking kernels are able to give the user the illusion that the number of processes being run
simultaneously on the computer is higher than the maximum number of processes the computer
is physically able to run simultaneously. Typically, the number of processes a system may run
simultaneously is equal to the number of CPUs installed (however this may not be the case if the
processors support simultaneous multithreading).
In a pre-emptive multitasking system, the kernel will give every program a slice of time and switch
from process to process so quickly that it will appear to the user as if these processes were being
executed simultaneously. The kernel uses scheduling algorithms to determine which process is
running next and how
much time it will be given. The algorithm chosen may allow for some processes to have higher
priority than others. The kernel generally also provides these processes a way to communicate;
this is known as inter-process communication (IPC) and the main approaches are shared memory,
message passing and remote procedure calls (see module 3).
Other systems (particularly on smaller, less powerful computers) may provide cooperative
multitasking; where each process is allowed to run uninterrupted until it makes a special request
that tells the kernel it may switch to another process. Such requests are known as "yielding", and
typically occur in response to requests for inter process communication, or for waiting for an
event to occur. Older versions of Windows and Mac OS both used co-operative multitasking but
switched to pre-emptive schemes as the power of the computers to which they were targeted
grew.
The operating system might also support multiprocessing (SMP or Non- Uniform Memory Access);
in that case, different programs and threads may run on different processors. A kernel for such a
system must be designed to be re- entrant, meaning that it may safely run two different parts of
its code simultaneously. This typically means providing synchronization mechanisms (such as
spinlocks) to ensure that no two processors attempt to modify the same data at the same time.
2.2.2 Memory Management
The kernel has full access to the system's memory and must allow processes to access this
memory safely as they require it. Often the first step in doing this is virtual addressing, usually
achieved by paging and/or segmentation. Virtual addressing allows the kernel to make a given
physical address appear to be another address, the virtual address. Virtual address spaces may be
different for different processes; the memory that one process accesses at a particular (virtual)
address may be different memory from what another process accesses at the same address. This
allows every program to behave as if it is the only one
(apart from the kernel) running and thus prevents applications from crashing each other.
On many systems, a program's virtual address may refer to data which is not currently in memory.
The layer of indirection provided by virtual addressing allows the operating system to use other
data stores, like a hard drive, to store what would otherwise have to remain in main memory
(RAM). As a result, operating systems can allow programs to use more memory than the system
has physically available. When a program needs data which is not currently in RAM, the CPU
signals to the kernel that this has happened, and the kernel responds by writing the contents of
an inactive memory block to disk (if necessary) and replacing it with the data requested by the
program. The program can then be resumed from the point where it was stopped. This scheme is
generally known as demand paging.
Virtual addressing also allows creation of virtual partitions of memory in two disjointed areas, one
being reserved for the kernel (kernel space) and the other for the applications (user space). The
applications are not permitted by the processor to address kernel memory, thus preventing an
application from damaging the running kernel. This fundamental partition of memory space has
contributed much to current designs of actual general-purpose kernels and is almost universal in
such systems, although some research kernels (e.g. Singularity) take other approaches.
2.2.3 Device Management
To perform useful functions, processes need access to the peripherals connected to the
computer, which are controlled by the kernel through device drivers. For example, to show the
user something on the screen, an application would make a request to the kernel, which would
forward the request to its display driver, which is then responsible for actually plotting the
character/pixel. A kernel must maintain a list of available devices. This list may be known in
advance (e.g. on an embedded system where the kernel will be rewritten if the available
hardware changes), configured by the user (typical on older PCs and on systems that are not
designed for personal use) or detected by the operating system at run time (normally called Plug
and Play).
In a plug and play system, a device manager first performs a scan on different hardware buses,
such as Peripheral Component Interconnect (PCI) or Universal Serial Bus (USB), to detect installed
devices, then searches for the appropriate drivers.
As device management is a very OS-specific topic, these drivers are handled differently by each
kind of kernel design, but in every case, the kernel has to provide the I/O to allow drivers to
physically access their devices through some port or memory location. Very important decisions
have to be made when designing the device management system, as in some designs accesses
may involve context switches, making the operation very CPU-intensive and easily causing a
significant performance overhead.
2.2.4 System Calls
To actually perform useful work, a process must be able to access the services provided by the
kernel. This is implemented differently by each kernel, but most provide a C library or an API,
which in turn invoke the related kernel functions. The method of invoking the kernel function
varies from kernel to kernel. If memory isolation is in use, it is impossible for a user process to call
the kernel directly, because that would be a violation of the processor's access control rules. A
few possibilities are:
1. Using a software-simulated interrupt: This method is available on most hardware and is
therefore very common.
2. Using a call gate: A call gate is a special address which the kernel has added to a list stored
in kernel memory and which the processor knows the location of when the processor detects a
call to that location, it instead redirects to the target location without causing an access violation.
Requires hardware support, but the hardware for it is quite common.
3. Using a special system call instruction: This technique requires special hardware support,
which common architectures (not ably, x86) may lack. System call instructions have been added to
recent models of x86 processors, however, and some (but not all) operating systems for PCs make
use of them when available.
4. Using a memory-based queue: An application that makes large numbers of requests but
does not need to wait for the result of each may add details of requests to an area of memory
that the kernel periodically scans to find requests.
value). A kernel based on capabilities, however, is more flexible in assigning privileges, can satisfy
Denning's fault tolerance principles, and typically does not suffer from the performance issues of
copy by value. Both approaches typically require some hardware or firmware support to be
operable and efficient.
The hardware support for hierarchical protection domains is typically that of "CPU modes." An
efficient and simple way to provide hardware support of capabilities is to delegate the MMU, the
responsibility of checking access-rights for every memory access, a mechanism called capability-
based addressing. Most commercial computer architectures lack MMU support for capabilities.
An alternative approach is to simulate capabilities using commonly-support hierarchical domains;
in this approach, each protected object must reside in an address space that the application does
not have access to; the kernel also maintains a list of capabilities in such memory. When an
application needs to access an object protected by a capability, it performs a system call and the
kernel performs the access for it. The performance cost of address space switching limits the
practicality of this approach in systems with complex interactions between objects, but it is used
in current operating systems for objects that are not accessed frequently or which are not
expected to perform quickly.
Approaches where protection mechanisms are not firmware supported but are instead simulated
at higher levels (e.g. simulating capabilities by manipulating page tables on hardware that does
not have direct support), are possible, but there are performance implications. Lack of hardware
support may not be an issue, however, for systems that choose to use language-based
protection.
ii. Security
An important kernel design decision is the choice of the abstraction levels where the security
mechanisms and policies should be implemented. One approach is to use firmware and kernel
support for fault tolerance (see above),
and build the security policy for malicious behavior on top of that (adding features such as
cryptography mechanisms where necessary), delegating some responsibility to the compiler.
Approaches that delegate enforcement of security policy to the compiler and/or the application
level are often called language- based security.
iii. Hardware-Based Protection or Language-Based Protection
Typical computer systems today use hardware-enforced rules about what programs are allowed
to access what data. The processor monitors the execution and stops a program that violates a
rule (e.g., a user process that is about to read or write to kernel memory, and so on). In systems
that lack support for capabilities, processes are isolated from each other by using separate
address spaces. Calls from user processes into the kernel are regulated by requiring them to use
one of the above-described system call methods. An alternative approach is to use language-
based protection. In a language-based protection
system, the kernel will only allow code to execute that has been produced by a
trusted language compiler. The language may then be designed such that it is impossible for
the programmer to instruct it to do something that will violate a security requirement.
Advantages of this approach include:
1. Lack of need for separate address spaces: Switching between address spaces is a slow
operation that causes a great deal of overhead, and a lot of optimization work is currently
performed in order to prevent unnecessary switches in current operating systems. Switching is
completely unnecessary in a language based protection system, as all code can safely operate in
the same address space.
2. Flexibility: Any protection scheme that can be designed to be expressed via a
programming language can be implemented using this method. Changes to the protection
scheme (e.g. from a hierarchical system to a capability-based one) do not require new hardware.
Disadvantages include:
1. Longer application start up time: Applications must be verified when they are started to
ensure they have been compiled by the correct compiler, or may need recompiling either from
source code or from byte code.
2. Inflexible type systems: On traditional systems, applications frequently perform operations
that are not type safe. Such operations cannot be permitted in a language-based protection
system, which means that applications may need to be rewritten and may, in some cases, lose
performance. Examples of systems with language-based protection include JX and Microsoft's
Singularity.
iv. Process cooperation
Edsger Dijkstra proved that from a logical point of view, atomic lock and unlock operations
operating on binary semaphores are sufficient primitives to express any functionality of process
cooperation. However this approach is generally held to be lacking in terms of safety and
efficiency, whereas a message passing approach is more flexible.
v. I/O devices management
The idea of a kernel where I/O devices are handled uniformly with other processes, as parallel co-
operating processes, was first proposed and implemented by Brinch Hansen (although similar
ideas were suggested in 1967). In Hansen's description of this, the "common" processes are
called internal processes, while the I/O devices are called external processes.
In a monolithic kernel, all OS services run along with the main kernel thread, thus also residing in
the same memory area. This approach provides rich and powerful hardware access. Some
developers maintain that monolithic systems are easier to design and implement than other
solutions, and are extremely efficient if well-written. The main disadvantages of monolithic kernels
are the dependencies between system components – a bug in a device driver might crash the
entire system - and the fact that large kernels can become very difficult to maintain.
Microkernels
microkernels, optimized for performance, such as L4 and K42 have addressed these problems. A
microkernel allows the implementation of the remaining part of the operating system as a normal
application program written in a high-level language, and the use of different operating systems
on top of the same unchanged kernel. It is also possible to dynamically switch among operating
systems and to have more than one active simultaneously.
Monolithic kernels Vs. Microkernels
As the computer kernel grows, a number of problems become evident. One of the most obvious
is that the memory footprint increases. This is mitigated to some degree by perfecting the virtual
memory system, but not all computer architectures have virtual memory support. To reduce the
kernel's footprint, extensive editing has to be performed to carefully remove unneeded code,
which can be very difficult with non-obvious interdependencies between parts of a kernel with
millions of lines of code. Due to the problems that monolithic kernels pose, they were considered
obsolete by the early 1990s. As a result, the design of Linux using a monolithic kernel rather than
a microkernel was the topic of a famous flame war between Linus Torvalds and Andrew
Tanenbaum. There is merit on both sides of the argument presented in the Tanenbaum/Torvalds
debate.
Some, including early UNIX developer Ken Thompson, argued that while microkernel designs
were more aesthetically appealing, monolithic kernels were easier to implement.
However, a bug in a monolithic system usually crashes the entire system, while this does not
happen in a microkernel with servers running apart from the main thread. Monolithic kernel
proponents reason that incorrect code does not belong in a kernel, and that microkernels offer
little advantage over correct code. Microkernels are often used in embedded robotic or medical
computers where crash tolerance is important and most of the OS components reside in their
own private, protected memory space. This is impossible with monolithic kernels,
even with modern module-loading ones. However, the monolithic model tends to be more
efficient through the use of shared kernel memory, rather than the slower IPC system of
microkernel designs, which is typically based on message passing.
Hybrid kernels
Is a kernel architecture based on combining aspects of microkernel and monolithic kernel
architectures used in computer operating systems. The category is controversial due to the
similarity to monolithic kernel; the term has been dismissed by some as just marketing. The
usually accepted categories are monolithic kernels and microkernels (with nano kernels and exo
kernels seen as more extreme versions of microkernels).
The hybrid kernel approach tries to combine the speed and simpler design of a monolithic kernel
with the modularity and execution safety of a microkernel. Hybrid kernels are essentially a
compromise between the monolithic kernel approach and the microkernel system. This implies
running some services (such as the network stack or the file system) in kernel space to reduce the
performance overhead of a traditional microkernel, but still running kernel code (such as device
drivers) as servers in user space.
The idea behind this quasi-category is to have a kernel structure similar to a microkernel, but
implemented as a monolithic kernel. In contrast to a microkernel, all (or nearly all) services are in
kernel space. As in a monolithic kernel, there is no performance overhead associated with
microkernel message passing and context switching between kernel and user mode. Also, as with
monolithic kernels, there are none of the benefits of having services in user space.
Nano kernels
Note that the term is sometimes used informally to refer to a very light-weight microkernel, such
as L4.
A Nano kernel or Pico kernel is a very minimalist operating system kernel. The nano kernel
represents the closest hardware abstraction layer of the operating system by interfacing the CPU,
managing interrupts and interacting with the MMU. The interrupt management and MMU
interface are not necessarily part of a nano kernel; however, on most architecture, these
components are directly connected to the CPU, therefore, it often makes sense to integrate these
interfaces into the kernel.
A Nano kernel delegates virtually all services – including even the most basic ones like interrupt
controllers or the timer – to device drivers to make the kernel memory requirement even smaller
than a traditional microkernel.
Advantages and Disadvantages
Nano kernels Versus Monolithic kernels
A Nano kernel is considered to be slower than a typical monolithic kernel due to the management
and communication complexity caused by the separation of its components. Contrariwise, this
abstraction potentiates considerably faster development, simpler modules and higher code
quality. Additionally the management effort of such code is not ably decreased because
monolithic implementations tend to be more complex and intra dependent. As a result of its
lower module complexity, Nano kernel modules tend to be more accurate and maintainable.
Furthermore APIs of monolithic kernels (as present in for example the Linux kernel) are often
considered to be very unstable and quite mutable. It is often argued that this applies only to
some implementations, but in reality monolithic drivers use more internal structures than
separated modules.
Another key aspect is the isolation of the Nano kernel modules by architecture. Monolithic kernels
generally suffer from considerably bad security architecture because an inaccurate and insecure
part directly affects the whole operating system.
Exo kernels
STUDY SESSION 4
Types of Operating Systems
not move at all because the system is busy. RTOS can be hard or soft. A hard RTOS guarantees
that critical tasks are performed on time. However, soft RTOS is less restrictive. Here, a critical
real-time task gets priority over other tasks and retains that priority until it completes.
ii. Single-User, Single-Tasking Operating System:
As the name implies, this operating system is designed to manage the computer so that one user
can effectively do one thing at a time. The Palm OS for Palm handheld computers is a good
example of a modern single-user, single-task operating system.
iii. Single-User, Multi-Tasking Operating System:
This is the type of operating system most of us use on our desktop and laptop computers today.
Windows 98 and the Mac O.S. are both examples of an operating system that will let a single user
have several programs in operation at the same time. For example, it is entirely possible for you
as a Windows user to be writing a note in a word processor while downloading a file from the
Internet and at the same time, is printing the text of an e-mail message.
iv. Multi-User Operating Systems:
A multi-user operating system allows many different users to take advantage of the computer's
resources simultaneously. The operating system must make sure that the requirements of the
various users are balanced, and that each of the programs they are using has sufficient and
separate resources so that a problem with one user does not affect the entire community of users.
Unix, VMS, and mainframe operating systems, such as MVS, are examples of multi-user operating
systems. It's important to differentiate here between multi-user operating systems and single-user
operating systems that support networking.
Windows 2000 and Novell Netware can each support hundreds or thousands of networked users,
but the operating systems themselves are not true multi-user operating systems. The system
administrator is the only user for Windows 2000 or Netware. The network support and the entire
remote user logins the network
enables are in the overall plan of the operating system, a program being run by the administrative
user.
2.2. Types of OS based on the Nature of Interaction that takes place between the
Computer User and His /Her Program during its Processing
Modern computer operating systems may be classified into three groups, which are distinguished
by the nature of interaction that takes place between the computer user and his or her program
during its processing. The three groups are called batch, time-shared and real time operating
systems. We shall further explain these three groups below.
i. Batch Processing OS
In a batch processing operating system environment, users submit jobs to a central place where
these jobs are collected into a batch, and subsequently placed on an input queue at the
computer where they will be run. In this case, the user has no interaction with the job during its
processing, and the computer‘s response time is the turnaround time (i.e. the time from
submission
of the job until execution is complete, and the results are ready for return to the person who
submitted the job).
ii. Time Sharing OS
Another mode for delivering computing services is provided by time sharing operating systems.
In this environment, a computer provides computing services to several or many users
concurrently on-line. Here, the various users are sharing the central processor, the memory, and
other resources of the computer system in a manner facilitated, controlled, and monitored by the
operating system. The user in this environment has nearly full interaction with the program during
its execution, and the computer‘s response time may be expected to be no more than a few
second.
iii. Real Time OS
The third class of operating systems, real time operating systems, is designed to service those
applications where response time is of the essence in order to prevent error, misrepresentation or
even disaster. Examples of real time operating systems are those which handle airlines
reservations, machine tool control, and monitoring of a nuclear power station. The systems, in this
case, are designed to be interrupted by external signal that require the immediate attention of
the computer system.
In fact, many computer operating systems are hybrids, providing for more than one of these types
of computing service simultaneously. It is especially common to have a background batch system
running in conjunction with one of the other two on the same computer.
2.3. Other Types of OS based on the Definition of the System/Environment A number of other
definitions are important, for you to gain a better understanding and subsequently classifying
operating systems:
i. Multiprogramming Operating System
A multiprogramming operating system is a system that allows more than one active user program
(or part of user program) to be stored in main memory simultaneously. Thus, it is evident that a
time-sharing system is a multiprogramming system, but note that a multiprogramming system is
not necessarily a time-sharing system. A batch or real time operating system could, and indeed
usually does, have more than one active user program simultaneously in main storage. Another
important, and all too similar, term is
‗multiprocessing‘. A multiprocessing system is a computer hardware configuration that includes
more than one independent processing unit. The term multiprocessing is generally used to refer
to large computer hardware complexes found in major scientific or commercial applications.
ii. Network Operating Systems
A networked operating system is a collection of physical interconnected computers. The
operating system of each of the interconnected computers must contain, in addition to its own
stand-alone functionality, provisions for handling communication and transfer of programs and
data among the other computers with which it is connected. In a network operating system, the
users are aware of the existence of multiple computers, and can log in to remote machines and
copy files from one machine to
another. Each machine runs its own local operating system and has its own user (or users).
Network operating systems are designed with more complex
functional capabilities. Fig 1.4.2: Network Operating Systems
Network operating systems are not fundamentally different from single processor operating
systems. They obviously need a network interface controller and some low-level software to drive
it, as well as programs to achieve remote login and remote files access, but these additions do
not change the essential structure of the operating systems.
iii. Distributed Operating Systems
A distributed computing system consists of a number of computers that are connected and
managed so that they automatically share the job processing load among the constituent
computers, or separate the job load as appropriate particularly configured processors. Such a
system requires an operating system which, in addition to the typical stand-alone functionality,
provides coordination of the operations and information flow among the component computers.
The distributed computing environment and its operating systems, like networking environment,
are designed with more complex functional capabilities. However, a distributed operating system,
in contrast to a network operating system, is one that appears to its users as a traditional
uniprocessor system, even though it is actually composed of multiple processors. In a true
distributed system, users should not be aware of where their programs are being run or where
their files are located; that should all be handled automatically and efficiently by the operating
system. True distributed operating systems require more than just adding a little code to a
uniprocessor operating system, because distributed and centralized systems differ in critical ways.
Distributed systems, for example, often allow program to run on several processors at the same
time, thus requiring more complex processor scheduling algorithms in order to optimize the
amount of parallelism achieved.
STUDY SESSION 5
Disk operating system
Introduction
The previous study session, you were introduced to the various types of OS based on different
criteria. In this study session, you shall be taken through the disk operating system and its various
characteristics and examples.
platters (such as hard disks or floppy disks). In the early days of micro computing, memory space
was often limited, so the disk operating system was an extension of the operating system. This
component was only loaded if needed. Otherwise, disk-access would be limited to low-level
operations such as reading and writing disks at the sector-level.
In some cases, the disk operating system component (or even the operating system) was known
as DOS. Sometimes, a disk operating system can refer to the entire operating system if it is
loaded off a disk and supports the abstraction and management of disk devices. Examples
include DOS/360 and Free DOS. On the PC compatible platform, an entire family of operating
systems was called DOS.
You should note that in the early days of computers, there were no disk drives; delay lines,
punched cards, paper tape, magnetic tape, magnetic drums, were
used instead. And in the early days of microcomputers, paper tape or audio cassette tape (see
Kansas City standard) or nothing were used instead. In the latter case, program and data entry
was done at front panel switches directly into memory or through a computer terminal/keyboard,
sometimes controlled by a ROM BASIC interpreter; when power was turned off after running the
program, the information so entered vanished. Both hard disks and floppy disk drives require
software to manage rapid access to block storage of sequential and other data.
When microcomputers rarely had expensive disk drives of any kind, the necessity to have software
to manage such devices (i.e. the 'disks‘) carried much status. To have one or the other was a mark
of distinction and prestige, and so was having the Disk sort of an Operating System. As prices for
both disk hardware and operating system software decreased, there were many such
microcomputer systems. Mature versions of the Commodore, SWTPC, Atari and Apple home
computer systems all featured a disk operating system (actually called 'DOS' in the case of the
Commodore 64 (CBM DOS), Atari 800 (Atari DOS), and Apple II machines (Apple DOS)), as did
(at the other end of the hardware spectrum, and much earlier) IBM's System/360, 370 and (later)
390 series of mainframes (e.g., DOS/360: Disk Operating System / 360 and DOS/VSE: Disk
Operating System / Virtual Storage Extended). Most home computer DOS'es were stored on a
floppy disk always to be booted at start-up, with the notable exception of Commodore, whose
DOS resided on ROM chips in the disk drives themselves, available at power-on. In large
machines there were other disk operating systems, such as IBM's VM, DEC's RSTS / RT-11 / VMS /
TOPS-10 / TWENEX, MIT's ITS / CTSS, Control Data's assorted NOS variants, Harris's Vulcan, Bell
Labs' Unix, and so on.
In microcomputers, SWTPC's 6800 and 6809 machines used TSC's FLEX disk operating system,
Radio Shack's TRS- 80 machines used TRS-DOS, their Color Computer used OS-9, and most of
the Intel 8080 based machines from IMSAI,
MITS (makers of the legendary Altair 8800), Cromemco, North Star, etc used the CP/M-80 disk
operating system. See list of operating systems.
Usually, a disk operating system was loaded from a disk. Only a very few comparable DOS were
stored elsewhere than floppy disks; among these exceptions were the British BBC Micro's
optional Disc Filing System, DFS, offered as a kit with a disk controller chip, a ROM chip, and a
handful of logic chips, to be installed inside the computer; and Commodore's CBM DOS, located
in a ROM chip in each disk drive.
STUDY SESSION 6
Real-time Operating System
Design philosophies
Two basic designs exist:
1. Event-driven (priority scheduling) designs: switch tasks only when an event of higher
priority needs service, called preemptive priority.
2. Time-sharing designs: switch tasks on a clock interrupt, and on events, called round-robin.
Time-sharing designs switch tasks more often than is strictly needed, but give smoother, more
deterministic multitasking, the illusion that a process or user has sole use of a machine. Early CPU
designs needed many cycles to switch tasks, during which the CPU could do nothing useful. So,
early OSes tried to minimize wasting CPU time by maximally avoiding unnecessary task-switches.
More recent CPUs take far less time to switch from one task to another; the extreme case is barrel
processors that switch from one task to the next in zero cycles. Newer RTOSes almost invariably
implement time-sharing scheduling with priority driven pre-emptive scheduling.
2.2. Scheduling
In typical designs, a task has three states:
1) running
2) ready
3) blocked
Most tasks are blocked, most of the time. Only one task per CPU is running. In simpler systems,
the ready list is usually short, two or three tasks at most. The real key is designing the scheduler.
Usually the data structure of the ready list in
the scheduler is designed to minimize the worst-case length of time spent in the scheduler's
critical section, during which preemption is inhibited, and, in
some cases, all interrupts are disabled. But, the choice of data
structure depends also on the maximum number of tasks that can be on the ready list (or ready
queue).
If there are never more than a few tasks on the ready list, then a simple unsorted bidirectional
linked list of ready tasks is likely optimal. If the ready list usually contains only a few tasks but
occasionally contains more, then the list should be sorted by priority, so that finding the highest
priority task to run does not require iterating through the entire list. Inserting a task then requires
walking the ready list until reaching either the end of the list, or a task of lower priority than that
of the task being inserted. Care must be taken not to inhibit preemption during this entire search;
the otherwise-long critical section should probably be divided into small pieces, so that if, during
the insertion of a low priority task, an interrupt occurs that makes a high priority task ready, that
high priority task can be inserted and run immediately (before the low priority task is inserted).
The critical response time, sometimes called the fly back time, is the time it
takes to queue a new ready task and restore the state of the highest priority task. In a well-
designed RTOS, readying a new task will take 3-20 instructions per ready queue entry, and
restoration of the highest-priority ready task will take 5- 30 instructions. On a 20MHz m68000
processor, task switch times run about 20 microseconds with two tasks ready. 100 MHz ARM
CPUs switch in a few microseconds.
In more advanced real-time systems, real-time tasks share computing resources with many non-
real-time tasks, and the ready list can be arbitrarily long. In such systems, a scheduler ready list
implemented as a linked list would be inadequate.
2.3 Inter Task Communication and Resource Sharing
A significant problem that multitasking systems must address is sharing data and hardware
resources among multiple tasks. It is usually "unsafe" for two tasks to access the same specific
data or hardware resource simultaneously. ("Unsafe" means the results are inconsistent or
unpredictable, particularly when one task is in the midst of changing a data collection. The view
by another task is best done either before any change begins, or after changes are completely
finished.) There are three common approaches to resolve this problem, they are:
1. Temporarily masking/disabling interrupts
2. Binary semaphores
3. Message passing
While interrupts are masked, the current task has exclusive use of the CPU; no other task or
interrupt can take control, so the critical section is effectively protected. When the task exits its
critical section, it must unmask interrupts; pending interrupts, if any, will then execute.
Temporarily masking interrupts should only be done when the longest path through the critical
section is shorter than the desired maximum interrupt latency, or else this method will increase
the system's maximum interrupt latency. Typically, this method of protection is used only when
the critical section is just a few source code lines long and contains no loops. This method is ideal
for protecting hardware bitmapped registers when the bits are controlled by different tasks.
When the critical section is longer than a few source code lines or involves lengthy looping, an
embedded/real-time programmer must resort to using mechanisms identical or similar to those
available on general-purpose operating systems, such as semaphores and OS-supervised inter
process messaging. Such mechanisms involve system calls, and usually invoke the OS's dispatcher
code on exit, so they can take many hundreds of CPU instructions to execute, while masking
interrupts may take as few as three instructions on some processors. But for longer critical
sections, there may be no choice; interrupts cannot be masked for long periods without
increasing the system's interrupt latency.
2. Binary semaphore
This is either locked or unlocked. When it is locked, a queue of tasks can wait for the semaphore.
Typically a task can set a timeout on its wait for a semaphore. Problems with semaphore based
designs are well known: priority inversion and deadlocks.
i. Priority inversion: a high priority task waits because a low priority task has a semaphore. A
typical solution is to have the task that has a semaphore run at (inherit) the priority of the highest
waiting task. But this simplistic approach fails when there are multiple levels of waiting (A waits for
a binary semaphore locked by B, which waits for a binary
semaphore locked by C). Handling multiple levels of inheritance without introducing instability in
cycles is not straightforward.
ii. Deadlock: when two or more tasks lock a number of binary semaphores and then wait
forever (no timeout) for other binary semaphores, creating a cyclic dependency graph. The
simplest deadlock scenario occurs when two tasks lock two semaphores in lockstep, but in the
opposite order. Deadlock is usually prevented by careful design, or by having floored
semaphores (which pass control of a semaphore to the higher priority task on defined conditions).
3. Message passing
In this paradigm, the resource is managed directly by only one task; when another task wants to
interrogate or manipulate the resource, it sends a message to the managing task. This paradigm
suffers from similar problems as binary semaphores: Priority inversion occurs when a task is
working on a low-priority message, and ignores a higher-priority message (or a message
originating indirectly from a high priority task) in its in-box. Protocol deadlocks occur when two or
more tasks wait for each other to send response messages. Although their real-time behavior is
less crisp than semaphore systems, simple message based systems usually do not have protocol
deadlock hazards, and are generally better behaved than semaphore systems.
driver task (through releasing a semaphore or sending a message). The scheduler often provides
the ability to unblock a task from interrupt handler.
STUDY SESSION 7
Time-Sharing and Object-Oriented Operating System
2.0 Main Content
2.1. Object-oriented operating system
An object-oriented operating system is an operating system which internally uses object-oriented
methodologies. An object-oriented operating system is in contrast to an object-
oriented user interface or programming framework, which can be placed above a non-object-
oriented operating system like DOS, Microsoft Windows
or Unix. Fig 1.7.1: Object-oriented operating system
It can be argued, however, that there are already object-oriented concepts involved in the design
of a more typical operating system such as Unix. While a more traditional language like C does
not support object orientation as fluidly as more recent languages, the notion, for example, of a
file, stream, or device driver (in Unix, each represented as a file descriptor) can be considered a
good example of object orientation: they are, after all, abstract data types, with various methods
in the form of system calls, whose behavior varies based on the type of object, whose
implementation details are hidden from the caller, and might even use inheritance in their
underlying code.
Examples NeXTSTEP
During the late 1980s, Steve Jobs formed the computer company NeXT. One of NeXT's first tasks
was to design an object-oriented operating system, NEXTSTEP. They did this by adding an
object-oriented framework on top of Mach and BSD using the Objective-C language as a basis.
NEXTSTEP's basis, Mach and BSD, are not object-oriented. Instead, the object- oriented portions
of the system live in user land. Thus, NEXTSTEP cannot be considered an object-oriented
operating system in the strictest terms.
The NeXT hardware and operating system were not successful, and, in search of a new strategy,
the company re-branded its object-oriented technology as a cross-platform development
platform.
Though NeXT's efforts were innovative and novel, they gained only a relatively small acceptance
in the marketplace. NeXT was later acquired by Apple Computer and its operating system
became the basis for Mac OS X most visibly in the form of the "Cocoa" frameworks.
Choices
Choices is an object-oriented operating system that was developed at the University of Illinois at
Urbana-Champaign. It is written in C++ and uses objects to represent core kernel components
like the CPU, Process and so on. Inheritance is used to separate the kernel into portable machine
independent classes and small non-portable dependent classes. Choices has been ported to and
runs on SPARC, x86 and ARM.
Athene
Athene is an object based operating system first released in 2000 by Rocklyte Systems. The user
environment is constructed entirely from objects that are linked together at runtime. Applications
for Athene can also be created using
this methodology and is commonly scripted using the object scripting language 'DML' (Dynamic
Markup Language). Objects can be shared between processes by creating them in shared
memory and locking them as required for access. Athene's object framework is multi-platform,
allowing it to be used in Windows and Linux environments for the development of object
oriented programs.
BeOS
One attempt at creating a truly object-oriented operating system was the BeOS of the mid 1990s,
which used objects and the C++ language for the application programming interface (API). But
the kernel itself was written in C with C++ wrappers in user space. The system did not become
main stream though even today it has its fans and benefits from ongoing development.
Syllable
Syllable makes heavy use of C++ and for that reason is often compared to BeOS.
TAJ
TAJ is India's first object oriented operating system. It is made in C++ with some part in
assembly. The source code of TAJ OS is highly modularized and is divided into different modules.
Each module is implemented as class. Many object oriented features like inheritance,
polymorphism, virtual functions etc. are extensively used in developing TAJ Operating System.
TAJ OS is a multitasking, multithreading and multiuser operating system.
The kernel of TAJ Operating System is of monolithic type. I.e. all the device drivers and other
important OS modules are embedded into kernel itself. This increases the speed of execution by
reducing context switching time (time taken to execute a system call). TAJ OS is developed by
Viral Patel. You can download the image file for TAJ OS at http://www.viralpatel.net or
http://www.geocities.com/taj_os
service to be used by many website customers at once, and none of them, notice any delays in
communications until the servers start to get very busy.
The Time-Sharing Business
In the 1960s, several companies started providing time-sharing services as service bureaus. Early
systems used Teletype K/ASR-33s or K/ASR-35s in ASCII environments, and an IBM tele printer in
EBCDIC environments. They would connect to the central computer by dial-up acoustically
coupled modems operating at 10-15 characters per second. Later terminals and modems
supported 30-120 characters per second. The timesharing system would provide a complete
operating environment, including a variety of programming language processors, various
software packages, file storage, bulk printing, and off-line storage. Users were charged rent for
the terminal, a charge for hours of connect time, a charge for seconds of CPU time, and a charge
for kilobyte- months of disk storage.
Common systems used for time-sharing included the SDS 940, the PDP-10, and the IBM 360.
Companies providing this service included Tymshare (founded in 1966), Dial Data, (bought by
Tymshare in 1968), and Bolt, Beranek, and Newman. By 1968, there were 32 such service bureaus
serving the NIH alone.
History
The concept was first described publicly in early 1957 by Bob Bemer as part of an article in
Automatic Control Magazine. The first project to implement a time- sharing system was initiated
by John McCarthy in late 1957, on a modified IBM 704, and later an additionally modified IBM
7090 computer. Although he left to work on Project MAC and other projects, one of the results of
the project, known as the Compatible Time Sharing System or CTSS, was demonstrated in
November, 1961. CTSS has a good claim to be the first time-sharing system and remained in use
until 1973. The first commercially successful time-sharing system was the Dartmouth Time-Sharing
System (DTSS) which was first implemented at Dartmouth College in 1964 and subsequently
formed the basis
of General Electric's computer bureau services. DTSS influenced the design of other early
timesharing systems developed by Hewlett Packard, Control Data Corporation, UNIVAC and
others (in addition to introducing the BASIC programming language).
Other historical timesharing systems, some of them still in widespread use, include:
1. IBM CMS (part of VM/CMS)
2. IBM TSS/360 (never finished; see OS/360)
3. IBM Time Sharing Option (TSO)
4. KRONOS (and later NOS) on the CDC 6000 series
5. Michigan Terminal System
6. Multics
7. MUSIC/SP
8. ORVYL
9. RSTS/E
10. RSX-11
11. TENEX
12. TOPS-10
13. TOPS-20
Contents:
MODULE 2
Process Management and Process Synchronization
Introduction:
Early computer systems allowed one program to be executed at a time. This program has
complete control of the system, and had access to all the system‘s resources. Current-day
computer systems allow multiple programs to be loaded into memory and to be executed
concurrently. This evolution requires firmer control and more compartmentalization of the various
programs. These needs resulted in the notion of a process, which is a program in execution. A
process is the unit of work in a modern time-sharing system. Although, the main concern of the
OS is the execution of user programs, it also needs to take care of various system tasks that are
better left outside the kernel itself.
A system therefore consists of a collection of processes: Operating system processes executing
system code, and user processes executing user code. All these processes can potentially
execute concurrently, with the CPU (or CPUs) multiplexed among them. By switching the CPU
between processes, the operating system can make the computer more productive.
6. Accounting information: this information includes the amount of CPU and real time used
time limits, account numbers, job or process numbers, etc.
7. I/O status information: the information includes the list of I/O devices allocated to this
process, a list of open files, etc.
The PCB simply serves as the repository for any information that may vary from process to
process.
Figure 2.1.3: Diagram showing CPU switch from process to process
2.4 Process Scheduling
The objective of multiprogramming is to have some process running at all times so as to
maximize CPU utilization. The objective of time-sharing is to switch the CPU among processes so
frequently that users can interact with each program while it is running. A uniprocessor system can
have only one running process. If more processes exist, the rest must wait until the CPU is free
and can be rescheduled.
Scheduling Queues
As processes enter the system, they are put into a job queue. This queue consists of all processes
in the system. The processes that are residing in main memory and are ready and waiting to
execute are kept on a list called the ready queue. This queue is generally stored as a linked list. A
ready-queue header contains pointers to the first and final PCB in the list. We extend each PCB to
include a pointer field that points to the next PCB in the ready queue.
The operating system also has other queues. When a process is allocated to the CPU, it executes
for a while and eventually quits, interrupted, or waits for the occurrence of a particular event, such
as the completion of an I/O request. In the case of I/O request, such a request may be to a
dedicated tape drive, or to a shared device, such as a disk. Since the system has many processes,
the disk may be busy with the I/O request of some other process. The process therefore may
have to wait for the disk. The list of processes waiting for a particular I/O device is called a device
queue. Each device has its own queue.
memory-management information. When a context switch occurs, the kernel saves the context of
the old process in its PCB and loads the saved context of the new process scheduled to run.
Context-switch time is pure overhead,
because the system does useful work while switching. Its speed varies from
machine to machine, depending on the memory speed, the number of registers
that must be copied, and the existence of special instructions. Typical speeds range from 1 to
1000 microseconds.
various physical and logical resources. For instance, consider a process whose function is to
display the status of a file, say F1, on the screen of a terminal. When it is created, it will get as an
input from its parent process, the name of the file F1, and it will execute using that datum to
obtain the desired information. It may also get the name of the output device. Some operating
systems pass resources to child processes. On such a system, the new process may get two open
files, F1 and the terminal device, and may just need to transfer the datum between the two.
When a process creates a new process, two possibilities exist in terms of execution:
1. The parent continues to execute concurrently with its children.
2. The parent waits until some or all of its children have terminated.
There are also two possibilities in terms of the address space of the new process:
1. The child process is a duplicate of the parent process.
2. The child process has a program loaded into it.
In UNIX, every process except process 0 (the swapper) is created when another process executes
the fork system call. The process that invoked fork is the parent process and the newly-created
process is the child process. Every process (except process 0) has one parent process, but can
have many child processes.
In UNIX, a child process is in fact created (using fork) as a copy of the parent. The child process
can then overlay itself with a different program (using exec) as required. Each process may create
many child processes but will have only one parent process, except for the very first process
which has no parent. The first process, called init in UNIX, is started by the kernel at booting time
and never terminates. The kernel identifies each process by its process identifier (PID).
Process 0 is a special process that is created when the system boots; after forking a child process
(process 1), process 0 becomes the swapper process. Process 0, known as init, is the ancestor of
every other process in the system.
When a child process terminates execution, either by calling the exit system call, causing a fatal
execution error, or receiving a terminating signal, an exit status is returned to the operating
system. The parent process is informed of its child's termination through a SIGCHLD signal. A
parent will typically retrieve its child's exit status by calling the wait system call. However, if a
parent does not do so, the child process becomes a zombie process.
process via an appropriate system call e.g. abort. Usually, only the parent of the process that is to
be terminated can invoke such a system call. Otherwise, users could arbitrarily kill each other‘s
jobs. A parent, therefore, need to know the identities of its children. Thus, when one process
creates a new process, the identity of the newly created process is passed to the parent.
A parent may terminate the execution of one of its children for a variety of reasons, such as these:
1. The child has exceeded its usage of some of the resources that it has been allocated. This
requires the parent to have a mechanism to inspect the state of its children.
2. The task assigned to the child is no longer required.
3. The parent is exiting, and the operating system does allow a child to continue if its parent
terminate. On such systems, if a process terminates (either normally or abnormally), then all its
children must also be terminated. This phenomenon, referred to as cascading termination, is
normally initiated by the operating system.
To illustrate process execution and termination, consider that in UNIX, we can terminate a process
by using the exit system call; its parent process may wait for the termination of a child process by
using wait system call. The wait system call returns the process identifier of a terminated child, so
that the parent can tell which of its possibly many children has terminated. If the parent
terminates, however, all its children have assigned as their new parent the init process. Thus, the
children still have a parent to collect their status.
STUDY SESSION 2
Co-operating Processes
Introduction:
The previous study session has introduced you to the concept of processes and the various
operations that can be carried out on processes. Sometimes, when you have more than one
process running on the computer system, there may be need for them to interact with one
another. This study session takes you through the different ways that these various processes may
be running on the computer system at the same time interact with one another.
135
2. Computation speedup: if we want a particular task to run faster, we must break it into sub-
tasks, each of which will be executing in parallel with the others. Such a speedup can be achieved
only if the computer has multiple processing elements (such as CPUs or I/O channels).
3. Modularity: We may want to construct the system in a modular fashion, dividing the system
functions into separate processes or threads.
4. Convenience: even an individual user may have many tasks on which to work at one time.
For instance, a user may be editing, printing, and compiling in parallel.
Concurrent execution of co-operating processes requires mechanisms that allow processes to
communicate with one another and to synchronize their actions. To illustrate the concept of co-
operating processes, let us consider the producer- consumer problem, which is a common
paradigm for co-operating processes. A producer process produces information that is consumed
by a consumer process. For example, a print program produces characters that are consumed by
the printer driver. To allow producer and consumer processes to run concurrently, we must have a
buffer available for items that can be filled by the producer and emptied by the consumer. A
producer can produce one item while the consumer consuming another item. The producer and
consumer must be synchronized, so that it does not try to consume an item that has not yet been
produced. In this situation, the consumer must wait until an item is produced. The unbounded-
buffer producer-consumer places no practical limit on the size of the buffer. The consumer may
have to wait for new items, but the producer can always produce new items. The bounded-buffer
producer-consumer problem assumes a fixed buffer size, in this case, the consumer must wait if
the buffer is empty and the producer must wait if the buffer is full. The buffer may either be
provided by the operating system through the use of an inter process- communication (IPC)
facility (this will be discussed fully in the next section),
or explicitly coded by the application programmer with the use of shared memory.
message-passing systems.
Message-Passing system
The function of a message system is to allow processes to communicate with one another without
the need to resort to shared data. An IPC facility provides at least the two operations:
i. send (message) and
ii. receive (message).
Messages sent by a process can be of either fixed or variable size. If only fixed- sized messages
can be sent, the system-level implementation is straightforward. This restriction, however, makes
the task of programming more difficult. On the other hand, variable-sized messages require a
more complex system-level implementation, but the programming task becomes simpler.
If processes P and Q want to communicate, they must send messages to and receive message
from each other, a communication link must exist between them. This link can be implemented in
a variety of ways. We are concerned here not with the link‘s physical implementation (such as
shared memory, hardware bus, or network), but rather with its logical implementation. Here are
several methods for logically implementing a link and the send/receive operations:
1. direct or indirect communication
2. symmetric or asymmetric communication
3. automatic or explicit buffering
4. send by copy or send by reference
5. fixed-sized or variable-sized messages
We are going to look at each of these types of messages in the following section.
Naming
Processes that want to communicate must have a way to refer to each other. They can use either
direct or indirect communication.
Direct Communication
With direct communication, each process that wants to communicate must explicitly name the
recipient or sender of the communication. In this scheme, the send and receive primitives are
defined as follows:
1. send (P, message) – Send message to process P
2. receive (Q, message) – Receive a message from process Q A communication link in this
scheme has the following properties:
1. A link is established automatically between every pair of processes that want to
communicate. The processes need to know only each other‘s identity to communicate.
2. A link is associated with exactly two processes.
3. Exactly one link exists between each pair of processes.
This scheme exhibits symmetry in addressing; that is, both the sender and the receiver processes
must name the other to communicate. A variant of this scheme employs asymmetry in addressing.
Only the sender names the recipient, the recipient is not required to name the sender. In this
scheme, send and receive primitives are defined as follows:
1. send (P, message) – Send message to process P
2. receive (id, message) – Receive a message from any process, the variable id is set to the
name of the process with which communication has taken place.
The disadvantage in both symmetric and asymmetric schemes is the limited modularity of the
resulting process definitions. Changing the name of a process may necessitate examining all
other process definitions. All references to the old name must be found, so that they can be
modified to the new name, this situation is not desirable from the viewpoint of separate
compilation.
Indirect Communication
With indirect communication, the messages are sent to and received from mailboxes or ports. A
mailbox can be viewed abstractly as an object into which
messages can be placed by processes and from which messages can be removed. Each mailbox
has a unique identification. In this scheme, a process can communicate with some other process
via a number of different mailboxes. Two processes can communicate only if they share a
mailbox. The send and receive primitives are defined as follows:
1. send (A, message) – Send a message to mailbox A
2. receive (A, message) – Receive a message from mailbox A
In this scheme, a communication link has the following properties:
1. A link is established between a pair of processes only if both members of the pair have a
shared mailbox.
2. A link may be associated with more than two processes.
3. A number of different links may exist between each pair of
communicating processes, with each link corresponding to one mailbox.
Now suppose that processes P1, P2 and P3 all share mailbox A. Process P1 sends a message to
A, while P2 and P3 each a receive from A. Which process will receive the message sent by P1?
The answer depends on the scheme that we choose:
1. Allow a link to be associated with at most two processes
2. Allow at most one process at a time to execute a receive operation.
3. Allow the system to select arbitrarily which process will receive the message (that is, either
P2 or P3, but not both, will receive the message). The system may identify the receiver to the
sender.
A mailbox may be owned by either a process or by the operating system. If the mailbox is owned
by a process (i.e. that mailbox is part of the address space of the process), then we distinguish
between the owner (who can only receive messages through this mailbox) and the user (who can
only send messages to the mailbox). Since each mailbox has a unique owner, there can be no
confusion about who should receive a message sent to this mailbox. When a process that owns a
mailbox terminates, the mailbox disappears. Any process that
subsequently sends a message to this mailbox must be notified that the mailbox no longer exists.
On the other hand, a mailbox owned by the operating system is independent and is not attached
to any particular process. The operating system then must provide a mechanism that allows a
process to do the following:
1. create a new mailbox
2. send and receive messages through the mailbox
3. delete a mailbox
The process that creates a new mailbox is that mailbox‘s owner by default. Initially, the owner is
the only process that can receive messages through the mailbox. However, the ownership and
receive privilege may be passed to other processes through appropriate system calls. Of course,
the provision would result in multiple receivers for each mailbox.
Synchronization
Communication between processes takes place by calls to send and receive primitives. There are
different design options for implementing each primitive. Message passing may be blocking or
non-blocking – also known as synchronous and
asynchronous. Fig 2.2.3: Synchronization
1. Blocking send: the sending process is blocked until the message is received by the
receiving process or by the mailbox.
2. Non-blocking send: the sending process sends the message and resumes operation.
3. Blocking receive: the receiver blocks until a message is available.
4. Non-blocking receive: the receiver retrieves either a valid message or a null.
Different combinations of send and receive are possible. When both the send and receive are
blocking, we have a rendezvous between the sender and the receiver.
Buffering
Whether the communication is direct or indirect, messages exchanged by communicating
processes reside in a temporary queue. Basically, such a queue can be implemented in three
ways:
1. Zero capacity: The queue has maximum length 0; thus, the link cannot have any messages
waiting in it. In this case, the sender must block until the recipient receives the message.
2. Bounded capacity: The queue has finite length n; thus, at most n messages can reside in it.
If the queue is not full when a new message is sent, the latter is placed in the queue (either the
message is copied or a pointer to the message is kept), and the sender can continue the
execution without waiting. The link has a finite capacity, however. If the link is full, the sender
must block until space is available in the queue.
3. Unbounded capacity: The queue has potentially infinite length; thus, any number of
messages can wait in it. The sender never blocks. The zero capacity case is sometimes referred to
as a message system with no buffering; the other cases are referred to as automatic buffering.
Send by copy and send by reference
Whenever there is communication between sender and receiver, some content of the message
can be alter and some can‘t. Send by copy does not allow the receiver to alter/edit the state of
the parameter while send by reference does allow the receiver to alter/edit the state of the
parameter.
Fixed-sized and variable-sized messages
The implications of these are mostly related to buffering issues; with fixed-size messages, a buffer
with a specific size can hold a known number of messages. The number of variable-sized
messages that can be held by such a buffer is
unknown. Consider how Windows 2000 handles this situation: with fixed-sized messages
(anything < 256 bytes), the messages are copied from the address space of the sender to the
address space of the receiving process. Larger messages (i.e. variable-sized messages) use shared
memory to pass the message.
STUDY SESSION 3
Threads
Introduction
A thread, sometimes called a lightweight process (LWP), is a basic unit of CPU utilization; it
comprises a thread ID, a program counter, a register set, and a stack. It shares with other threads
belonging to the same process its code section, data section, and other operating system
resources, such as open files and signals. A traditional (or heavyweight) process has a single
thread of control. If the process has multiple threads of control, it can do more than one task at a
time. Figure 2.1 will illustrate to you the difference between a traditional single-threaded process
and a multithreaded process.
concurrently without the extra overhead needed to create a new process and handle
synchronized communication between these processes.
For example, a word processor could perform a spell check as you (the user) type, without
freezing the application - one thread could handle user input, while another runs the spell
checking utility.
Fig 2.3.1: Threads
2.1.1 Motivation
Many software packages that run on modern desktop PCs are multithread. An application
typically is implemented as a separate process with several threads of control. A web browser
might have one thread display images or text while another thread retrieves data from the
network. A word processor may have a thread for displaying graphics, another thread for reading
keystrokes from the user, and a third thread for performing spelling and grammar checking in the
background. In certain situations a single application may be required to perform several similar
tasks. For instance, a web server accepts client requests for web pages, images, sound, and so
on. A bus web server may have several (perhaps hundreds of) clients concurrently accessing it. If
the web server ran as
a traditional single-threaded process, it would be able to service only one client at a time. The
amount of time that a client might have to wait for its request to be serviced could be
enormous.
One solution is to have the server run as a single process that accepts requests. When the server
receives a request, it creates a separate process to service that request. In fact, this process-
creation method was in common use before threads became popular. Process creation, as you
have seen in the previous unit is very heavy weight. If the new process will perform the same tasks
as the existing process, why incur all that overhead?
It is generally more efficient for one process that contains multiple threads to serve the same
purpose. This approach would multithread the web-server process. The server would create a
separate thread that would listen for client requests; when a request was made; rather than
creating another process, it would create another thread to service the request.
2.1.2 Benefits
The benefits of multithreaded programming can be broken down into four major categories:
1. Responsiveness: Multithreading an interactive application may allow program to continue
running even if part of it is blocked or is performing a lengthy operation, thereby increasing
responsiveness to the user. For instance, a multithreaded web browser could still allow user
interaction in one thread while an image is being loaded in another thread.
process can only run on one CPU, no matter how many are available. Multithreading on a multi-
CPU machine increases concurrency. In single- processor architecture, the CPU generally moves
between each thread so quickly as to create an illusion of parallelism, but in reality only one
thread is running at a time.
2.1.3 Types of Threads
Threads can be classified into two different types viz: user threads and kernel threads, depending
on the level and which support is provided for threads. Support for threads may be provided at
either the user level in which case the thread is referred to as user threads or fibers, or by the
kernel, in which case it is referred to kernel threads.
I. User threads (Fibers): These are supported above the kernel and are implemented by a
thread library at the user level. The library provides support for thread creation, scheduling, and
management with no support from the kernel. Since the kernel is not aware of user-level threads,
fiber creation and scheduling are done in user space without the need for kernel intervention.
Therefore, fibers are generally fast to create and manage. However, the use of blocking system
calls in fibers can be problematic. If a fiber performs a system call that blocks, the other fibers in
the process are unable to run until the system call returns. A typical example of this problem is
when performing I/O: most programs are written to perform I/O synchronously.
When an I/O operation is initiated, a system call is made, and does not return until the I/O
operation has been completed. In the intervening period, the entire process is "blocked" by the
kernel and cannot run, which starves other fibers in the same process from executing. As
mentioned earlier, fibers are implemented entirely in user space. As a result, context switching
between fibers in a process does not require any interaction with the kernel at all and is therefore
extremely efficient: a
context switch can be performed by locally saving the CPU registers used by the currently
executing fiber and loading the registers required by the fiber to be executed. Since scheduling
occurs in user space, the scheduling policy can be more easily tailored to the requirements of the
program's workload. User-thread libraries include POSIX Pthreads, Mach C-threads, and Solaris 2
UI-threads.
II. Kernel threads: These are supported directly by the operating system. The kernel performs
thread creation, scheduling and management in kernel space. Due to the fact that thread
management is done by the operating system, kernel threads are generally slower to create and
manage than are user threads. However, since the kernel is managing the threads, if a thread
performs a blocking system call, the kernel can schedule another thread in the application for
execution. Also in multiprocessor environment, the kernel can schedule threads on different
processors. Most contemporary operating systems – including Windows NT, Windows 2000,
Solaris 2, BeOS, and Tru64 UNIX support kernel threads.
The use of kernel threads simplifies user code by moving some of the most complex aspects of
threading into the kernel. The program does not need to schedule threads or explicitly yield the
processor. User code can be written in a familiar procedural style, including calls to blocking APIs,
without starving other threads. However, since the kernel may switch between threads at any
time, kernel threading usually requires locking that would not be necessary otherwise. Bugs
caused by incorrect locking can be very subtle and hard to reproduce. Kernel threading also has
performance limits. Each time a thread starts, blocks, or exits, the process must switch into kernel
mode, and then back into user mode. This context switch is fairly quick, but programs that create
many short-lived threads
can suffer a performance hit. Hybrid threading schemes are available which provide a balance
between kernel threads and fibers.
The second issue is more problematic: if we allow all concurrent requests to be serviced in a new
thread, we have not placed a bound on the number of threads concurrently active in the system.
Unlimited threads could exhaust system resources, such as CPU time or memory. One solution to
this issue is to thread pools.
The general idea behind a thread pool is to create a number of threads at process startup and
place them into a pool, where they sit and wait for work. When a server receives a request, it
awakens a thread from this pool (if one is available) passing it the request to service. Once the
thread completes its service, it returns to the pool awaiting more work. If the pool contains no
available thread, the server waits until one becomes free.
STUDY SESSION 4
CPU Scheduling
Introduction:
CPU scheduling is the basis of multi-programmed operating systems. By switching the CPU
among processes, the operating system can make the computer more productive. In this unit, you
are going to be introduced to the basic scheduling concepts and be presented with several
different CPU- scheduling algorithms. The problem of selecting an algorithm for a particular
system will also be considered.
computer system, the CPU would then sit idle. All this waiting time is wasted. With
multiprogramming, we try to use this time productively. Several processes are kept in memory at
one time. When one process has to wait, the operating system takes the CPU away from that
process and gives the CPU to another process. This pattern continues. Scheduling is fundamental
to operating system function. Almost all computer resources are scheduled before use. The CPU
is, of course, one of the primary computer resources. Thus, its scheduling is central to operating
system design.
Figure 2.4.1: Alternating Sequence of CPU and I/O Bursts of Two Processes.
either by terminating or by switching to the waiting state. This scheduling method is used by
Microsoft Windows 3.1 operating system. It is the only method that can be used on certain
hardware platforms, because it does not require the special hardware needed for preemptive
scheduling. Some of the disadvantages of preemptive scheduling are that:
1. It incurs a cost
2. It also has an effect on the design of the operating system kernel
2.1.4 Dispatcher
Another component involved in the CPU scheduling function is the dispatcher. The dispatcher is
the module that gives control of the CPU to the process selected by the short-term scheduler.
This function includes:
1. switching context
2. switching to user mode
3. jumping to the proper location in the user program to restart that program
4. the dispatcher should be as fast as possible, given that it is invoked during every process
switch. The time it takes for the dispatcher to stop one process and start another running is
known as the dispatch latency
The characteristics used for comparison can make a substantial difference in the determination of
the best algorithm. The criteria include the following:
CPU Utilization: We want to keep the CPU as busy as possible. CPU utilization may range from 0
to 100 percent. In a real
system, it should range from 40 percent (for a lightly loaded system) to 90 percent (for a
Throughput: if the CPU is busy executing processes, then work is being done. One measure of
work is the number of processes completed per time unit, called throughput. For long processes,
this rate may be 1 (one) process per hour or 10 processes per second for short transactions.
Turnaround Time: This is the interval from the time of submission of a process to the time of
completion. It is the sum of the periods spent waiting to get into memory, waiting in the ready
queue, executing on the CPU and doing I/O.
Waiting Time: The CPU scheduling algorithm does not affect the amount of time during which a
process executes or does I/O. It affects only the amount of time that a process spends waiting in
the ready queue. Waiting time is,
therefore, the sum of the periods spent waiting in the ready queue.
Response Time: This is the amount of time it takes to start responding but not the time it takes to
output the response. I.e. the time from the submission of a request until the first response is
produced.
We usually want to maximize CPU utilization and throughput, and to minimize turnaround time,
waiting time, and response time. In most cases, we optimize the average measure. However, in
some circumstances we want to optimize the minimum or maximum values, rather than the
average. For instance, to
guarantee that all users get good service, we may want to minimize the response time.
implemented as fixed priority preemptive scheduling. For example foreground queue may have
absolute priority over background queue. Therefore no process in the background queue could
run except the foreground queues are empty. If a process entered the foreground queue while a
process from the background queue is running, the background queue process will be
preempted.
This will lead to possible starvation for the background queue process. To address this problem,
time slice can be used between the queues. Each queue gets a certain portion of the CPU time,
which it can then schedule among the various processes in its queue. For instance, in the
background – foreground queue example, the foreground queue can be given 80% of the CPU
time for RR scheduling among its processes, whereas the background queue receives 20% of the
CPU to give to its processes in FCFS manner.
Figure 2.4.4: Multilevel Queue Scheduling
2.2.2 Multilevel Feedback Queue (MLFQ) Scheduling
Similar to MLQ, but here processes can move between the queues. The idea is to separate
processes with different CPU-burst characteristics (i.e., based on their ―behaviorǁ). A process that
uses too much CPU time is degraded to a
lower-priority queue; a process that waits too long is upgraded to a higher- priority queue. This is
a kind of aging that prevents starvation.
In general, MLFQ scheduler is defined by the following parameters:
1. Number of queues
2. Scheduling algorithms for each queue
3. Criteria for determining when to upgrade a process to a higher-priority queue
4. Criteria for determining when to demote a process to a lower-priority queue
5. The criteria for determining which queue a process will enter when that process needs
service. MLFQ is the most general scheme, and also the most complex.
Example 2.6: Consider a MLFQ scheduler with three queues, Q0 with time quantum 8
milliseconds, Q1 with time quantum 16 milliseconds and Q2 on FCFS basis only when queues Q0
and Q1 are empty.
In this scheduling algorithm a new job enters queue Q0 served by FCFS. Then job receives 8
milliseconds. If not finished in 8 milliseconds, it is moved to Q1. At Q1 job served by FCFS. It
then receives 16 milliseconds. If not completed, it is preempted and moved to Q2 where it is
served in FCFS order with any CPU cycles left over from queues Q0 and Q1.
ensure that no two processors choose the same process and that processes are not lost from the
queue.
The second approach avoids this problem by appointing one processor as scheduler for the other
processors, thereby creating a master-slave structure. Some systems go a step further by having
all scheduling decisions, I/O processing, and other system activities handled by one single
processor – the master server. The other processors only execute user codes.
STUDY SESSION 5
Algorithm Evaluation
Introduction
How do we select a CPU-scheduling algorithm for a particular system? As you have seen in the
previous study session, there are many scheduling algorithms, each with its own parameters. As a
result, selecting an algorithm can be difficult. The first problem is defining the criteria to be used
in selecting an
algorithm. As you saw in the previous unit, criteria are often defined in terms of
CPU utilization, response time, or throughput. To select an algorithm, you must first define the
relative importance of these measures. Your criteria may include several measures, such as:
i. Maximize CPU utilization under the constraint that the maximum response time is 1
second.
ii. Maximize throughput such that turnaround is (on average) linearly proportional to total
execution time.
Once the selection criteria have been defined, we are then going to evaluate the various
algorithms under consideration. We describe the different evaluation methods in the rest of this
study session.
Consider the FCFS, SJF, and RR (quantum = 10milliseconds) scheduling algorithms for this set of
processes. Which algorithm would give the minimum average waiting time? For the FCFS
algorithm, we would execute the processes as:
The waiting time is 0 milliseconds for process P1, 10 milliseconds for process P2, 39 milliseconds
for P3, 42 milliseconds for process P4 and 49 milliseconds for process P5. Therefore, the average
waiting time is (0 + 10 + 39 + 42 + 49)/5
= 28 milliseconds. With non-pre-emptive SJF scheduling, we execute the processes as:
The waiting time is 10 milliseconds for process P1, 32 milliseconds for process P2, 0 milliseconds
for P3, 3 milliseconds for process P4 and 20 milliseconds for process P5. Therefore, the average
waiting time is (10 + 32 + 0 + 3 + 20)/5 = 13 milliseconds. With RR algorithm, we execute the
processes as:
The waiting time is 0 milliseconds for process P1, 32 milliseconds for process P2, 20 milliseconds
for P3, 23 milliseconds for process P4 and 40 milliseconds for process P5. Therefore, the average
waiting time is (0 + 32 + 20 + 23 + 40)/5
= 13 milliseconds. You can see that in this case, the SJF results in less than one- half the average
waiting time obtained with FCFS scheduling; the RR algorithm gives us an intermediate value.
for new processes in the queue (such as three processes per second). Then we
expect that during the time W that a process waits, λ × W new processes will arrive in the queue
q. If the system is in a steady state, then the number of processes leaving the queue must be
equal to the number of processes that arrive. Therefore,
n = λ × W.
This equation is known as Little‘s formula. The formula is particularly useful because it is valid for
any scheduling algorithm and arrival distribution. It can be used to compute one of the three
variables once the other two are known.
Advantages of Queuing Analysis:
It can be useful in comparing scheduling algorithms.
Limitations:
i. The classes of algorithms and distribution that can be handled is presently limited
ii. It is hard to express a system of complex algorithms and distributions.
iii. Queuing models are often an approximation of a real system. As a result, the accuracy of
the computed results may be questionable.
2.3 Simulations
This is used to get a more accurate evaluation of scheduling algorithms. Simulations involve
programming a model of the computer system. Software data structures represent the major
components of the system. The simulator has a variable representing a clock; as this variable‘s
value is increased, the simulator modifies the system state to reflect the activities of the devices,
the processes and the scheduler.
As the simulation executes, statistics that indicate algorithm performance are gathered and
printed.
Advantages:
It produces accurate results for its inputs.
Disadvantages:
i. It can be expensive
ii. Trace tapes can require large amounts of storage space.
iii. The design, coding and debugging of the simulator can be a major task.
2.4 Implementation
Even a simulator is of limited accuracy. The only completely accurate way to evaluate a
scheduling algorithm is to code it, put it in the operating system, and see how it works. This
approach puts the actual algorithm in the real system for evaluation under real operating
conditions.
Limitations:
This approach is very expensive. The expense is incurred not only in coding the algorithm and
modifying the operating system to support it as well as its
required data structure, but also in the reaction of the users to a constantly changing operating
system.
STUDY SESSION 6
Race Condition Section and Subsection Headings:
Introduction
You are welcome to this very interesting session, where we shall discuss race condition like you
have been told in your previous study. A race condition or race hazard is a flaw in a system or
process whereby the output of the process is unexpectedly and critically dependent on the
sequence or timing of other events. The term originates with the idea of two signals racing each
other to influence the output first. Race conditions arise in software when separate processes or
threads of execution depend on some shared state.
the system in such a way that one unique process (running a daemon or the like) has exclusive
access to the file, and all other processes that need to access the data in that file do so only via
inter process communication with that one process (which of course requires synchronization at
the process level).
A different form of race condition exists in file systems where unrelated programs may affect each
other by suddenly using up available resources such as disk space (or memory, or processor
cycles). Software not carefully designed to anticipate and handle this rare situation may then
become quite fragile and unpredictable. Such a risk may be overlooked for a long time in a
system that
seems very reliable. But eventually enough data may accumulate or enough other software may
be added to critically destabilize many parts of a system.
Probably the best known example of this occurred with the near loss of the
Mars Rover "Spirit" not long after landing, but this is a commonly overlooked hazard in many
computer systems. A solution is for software to request and reserve all the resources it will need
before beginning a task; if this request fails then the task is postponed, avoiding the many points
where failure could have occurred. (Alternately, each of those points can be equipped with error
handling, or the success of the entire task can be verified afterwards, before continuing on). A
more common but incorrect approach is to simply verify that enough disk space (for example) is
available before starting a task; this is not adequate because in complex systems the actions of
other running programs can be unpredictable.
2.2.2 Networking
In networking, I will like you to consider a distributed chat network like Internet relay chat (IRC),
where a user acquires channel-operator privileges in any
channel he starts. If two users on different servers, on different ends of the same network, try to
start the same-named channel at the same time, each user's respective server will grant channel-
operator privileges to each user, since neither server will yet have received the other server's
signal that it has allocated that channel. (Note that this problem has been largely solved by
various IRC server implementations.)
In this case of a race condition, the concept of the "shared resource" covers the state of the
network (what channels exist, as well as what users started them and therefore have what
privileges), which each server can freely change as long as it signals the other servers on the
network about the changes so that they can update their conception of the state of the network.
However, the latency across the network makes possible the kind of race condition described. In
this case,
heading off race conditions by imposing a form of control over access to the shared resource—
say, appointing one server to control who holds what privileges—would mean turning the
distributed network into a centralized one (at least for that one part of the network operation).
Where users find such a solution unacceptable, a pragmatic solution can have the system:
Fig 2.6.2: Networking
1. Recognize when a race condition has occurred; and
2. Repair the ill effects.
Energy Management System provided by GE Energy and used by Ohio-based FirstEnergy Corp.
(and by many other power facilities as well). A race condition existed in the alarm subsystem;
when three sagging power lines were tripped simultaneously, the condition prevented alerts from
being raised to the monitoring technicians, delaying their awareness of the problem. This
software flaw eventually led to the North American Blackout of 2003. (GE Energy later developed
a software patch to correct the previously undiscovered error.)
STUDY SESSION 7
Synchronization Section and Subsection Headings:
Introduction
Synchronization refers to one of two distinct, but related concepts: synchronization of processes
and synchronization of data. Process synchronization refers to the idea that multiple processes are
to join up or handshake at a certain point, so as to reach an agreement or commit to a certain
sequence of action while Data synchronization refers to the idea of keeping multiple copies of a
dataset in coherence with one another, or to maintain data integrity. Process synchronization
primitives are commonly used to implement
data synchronization. In this study session, we shall also talk about the of process synchronization.
2.2.1 Motivation
The traditional approach to multi-threaded programming is to use locks to synchronize access to
shared resources. Synchronization primitives such as mutexes, semaphores, and critical sections
are all mechanisms by which a programmer can ensure that certain sections of code do not
execute concurrently if doing so would corrupt shared memory structures. If one thread attempts
to acquire a lock that is already held by another thread, the thread will block until the lock is free.
Blocking a thread, though, is undesirable for many reasons. An obvious reason is that while the
thread is blocked, it cannot accomplish anything. If the blocked thread is performing a high-
priority or real- time task, it is highly undesirable to halt its progress. Other problems are less
obvious. Certain interactions between locks can lead to error conditions such as deadlock, live
lock, and priority inversion. Using locks also involves a trade-off between coarse-grained locking,
which can significantly reduce opportunities
for parallelism, and fine-grained locking, which requires more careful design, increases overhead
and is more prone to bugs. Non-blocking algorithms are also safe for use in interrupt handlers:
even though the preempted thread cannot be resumed, progress is still possible without it.
In contrast, global data structures protected by mutual exclusion cannot safely be accessed in a
handler, as the preempted thread may be the one holding the
lock.
thread to complete. However, as live lock is still possible in the modern definition,
threads have to wait when they encounter contention; hence, priority inversion is still possible
depending upon the contention management system used. Lock- free algorithms, below, avoid
priority inversion.
2.2.2 Implementation
Non-blocking algorithms use atomic read-modify-write primitives that the hardware must provide,
the most notable of which is compare and swap (CAS). Ultimately, all synchronizing algorithms
must use these; however, critical sections are almost always implemented using standard
interfaces over these primitives. Until recently, all non-blocking algorithms had to be written
"natively" with the underlying primitives to achieve acceptable performance. However, the
emerging field of software transactional memory promises standard abstractions for writing
efficient non-blocking code. Much research has also been done in providing basic data structures
such as stacks, queues,
sets, and hash tables. These allow programs to easily exchange data between threads
asynchronously.
2.2.3 Wait-freedom
Wait-freedom is the strongest non-blocking guarantee of progress, combining guaranteed
system-wide throughput with starvation-freedom. An algorithm is wait-free if every operation has
a bound on the number of steps it will take before completing. It was shown in the 1980s that all
algorithms can be implemented wait-free, and many transformations from serial code, called
universal constructions, have been demonstrated. However, the resulting performance does not
in general match even naive blocking designs. It has also been shown that the widely-available
atomic conditional primitives, compare- and-swap, cannot provide starvation-free
implementations of many common data structures without memory costs growing linearly in the
number of threads. Wait free algorithms are therefore rare, both in research and in practice.
2.2.4 Lock-freedom
Lock-freedom allows individual threads to starve but guarantees system-wide throughput. An
algorithm is lock-free if every step taken achieves global progress (for some sensible definition of
progress).
All wait-free algorithms are lock-free. In general, a lock-free algorithm can run in four phases:
completing one's own operation, assisting an obstructing operation, aborting an obstructing
operation, and waiting. Completing one's own operation is complicated by the possibility of
concurrent assistance and abortion, but is invariably the fastest path to completion. The decision
about when to assist, abort or wait when an obstruction is met is the responsibility of a contention
manager. This may be very simple (assist higher priority operations,
abort lower priority ones), or may be more optimized to achieve better throughput, or lower the
latency of prioritized operations.
Correct concurrent assistance is typically the most complex part of a lock-free algorithm, and
often very costly to execute: not only does the assisting thread slow down, but thanks to the
mechanics of shared memory, the thread being assisted will be slowed, too, if it is still running.
2.2.5 Obstruction-freedom
Obstruction-freedom is possibly the weakest natural non-blocking progress guarantee. An
algorithm is obstruction-free if at any point, a single thread executed in isolation (i.e. with all
obstructing threads suspended) for a bounded number of steps will complete its operation. All
lock-free algorithms are obstruction-free. Obstruction-freedom demands only that any partially-
completed operation can be aborted and the changes made rolled back. Dropping concurrent
assistance can often result in much simpler algorithms that are easier to validate. Preventing the
system from continually live-locking is the task of a contention manager.
Recent research has yielded a promising practical contention manager, whimsically named Polka,
combining exponential back off with "priority accumulation". As an operation progresses, it gains
"priority"; when an operation is obstructed by another with higher priority, it will back off, with
back off intervals increasing exponentially. Each back off increases the operation's priority; only
when its priority is greater than that of its obstructer will it abort it. Aborted operations retain their
former priority, giving their next attempt a greater chance of success. Polka achieves good
throughput in benchmarks because it minimizes both wasted effort, by prioritizing long
transactions, and memory interconnect contention, using exponential back off. This can inform
other parallel algorithms, such as lock-free ones, to achieve greater throughput in the common
case.
STUDY SESSION 8
Mutual Exclusion Section and Subsection Headings:
Introduction:
In the previous study session of this module, you have been introduced to some pertinent
concepts in process synchronization. This unit will further expose you to another important
concept in process synchronization which is mutual exclusion. It is an algorithm that is often used
in concurrent programming to avoid the simultaneous use of a common resource by pieces of
computer code known as critical section (this will be discussed in this next study session.
If concurrent processes or activities do not access common resources, there is no problem, but
there‘s a problem if they do. A solution to this problem is to keep the critical activities sequential
rather than concurrent. This solution is not always practical. Problems in achieving mutual
exclusion include lockstep, loss of mutual exclusion, deadlock and indefinite postponement.
The instruction test is the value of its argument i. If the value is 0, then it replaces it by 1 and
returns true. Otherwise, the value is not changed and false is returned. The entire test set function
is carried out automatically; that is, it is not subject to interruption some computers have similar
indivisible multiple- operation instructions, e.g., compare and- swap, for manipulating the linked
lists used for event queues and other data structures commonly used in operating systems.
Mutual Exclusion: Software Approach
Beside the hardware supported solution, some software solutions exist that use "busy wait" to
achieve the goal. Examples of these include:
· Dekker's algorithm
· Peterson's algorithm
· Lamport's bakery algorithm
· The Black-White Bakery Algorithm
· Semaphores
· Monitor (synchronization)
· Message passing
Most classical mutual exclusion methods attempt to reduce latency and busy- waits by using
queuing and context switches. Some claim that benchmarks indicate that these special algorithms
waste more time than they save.
Software approaches can be implemented for concurrent processes that execute on a single
processor or a multiprocessor machine with shared main memory.
These approaches usually assume elementary mutual exclusion at the memory access level. That
is, simultaneous accesses (reading and/or writing) to the same location in main memory are
serialized by some sort of memory arbiter, although the order of access granting is not specified
ahead of time. Beyond this, no support at the hardware, operating system, or programming-
language level is assumed. Peterson's Algorithm provided a simple and elegant solution. That
mutual exclusion is preserved is easily shown.
Consider process P0. Once it has set flag [0] to true, P1 cannot enter its critical section. If P1
already is in its critical section, then flag [1] = true and P0 is blocked from entering its critical
section. On the other hand, mutual blocking is prevented. Suppose that P0 is blocked in its while
loop. This means that flag [1] is true and turn = 1. P0 can enter its critical section when either flag
[1] becomes false or turn becomes 0. Now consider three exhaustive cases:
1. P1 has no interest in its critical section. This case is impossible, because it implies flag [1] =
false.
2. P1 is waiting for its critical section. This case is also impossible, because if turn = 1, P1 is
able to enter its critical section.
P1 is using its critical section repeatedly and therefore monopolizing access to it. This cannot
happen, because P1 is obliged to give P0 an opportunity by setting turn to 0 before each attempt
to enter its critical section.
This algorithm is easily generalized to the case of n processes.
Many forms of mutual exclusion have side-effects. For example, classic semaphores permit
deadlocks, in which one process gets a semaphore, another process gets a second semaphore,
and then both wait forever for the other semaphore to be released. Other common side-effects
include starvation, in which a process never gets sufficient resources to run to completion, priority
inversion, in which a higher priority thread waits for a lower-priority thread, and "high latency" in
which response to interrupts is not prompt.
Much research is aimed at eliminating the above effects, such as by guaranteeing non-blocking
progress. No perfect scheme is known.
STUDY SESSION 9
Critical Section Problem
Introduction:
Consider a system consisting of n processes {P0, P1, …, Pn-1}. Each process has a segment of
code called critical section, in which the processes may be changing common variables, updating
a table, writing a file, etc. The important feature of the system is that, when one process is
executing, in its
critical section, no other process is to be allowed to execute in its critical section. Therefore, the
execution of the critical section by the processes is mutually exclusive in time. The critical section
problem is to design a protocol that the processes can use to cooperate. Each process must
request permission to enter its critical section. The section of code implementing this request is
the entry section. The critical section may be followed by an exit section. The remaining code is
the remainder
section.
A solution to the critical section problem must satisfy the following three requirements:
1. Mutual Exclusion: if process Pi is executing in its critical section, then no other processes
can be executing in their critical sections.
2. Progress: If no process is executing in its critical section and some processes wish to enter
their critical sections, then only those processes that are not executing in their remainder section
can participate in deciding on which will enter its critical section next, and this selection cannot be
postponed indefinitely.
3. Bounded Waiting: there exists a bound on the number of times that other processes are
allowed to enter their critical sections after a process has made a request to enter its critical
section and before that request is granted. Based on
these three requirements, we will discuss some solutions to critical section problem in this study
session.
section at the same time as the original thread, but are free to gain control of the CPU and
execute other code, including other critical sections that are protected by different semaphores.
Some confusion exists in the literature about the relationship between different critical sections in
the same program. In general, a resource that must be protected from concurrent access may be
accessed by several pieces of code. Each piece must be guarded by a common semaphore. Is
each piece now a critical section or are all the pieces guarded by the same semaphore in
aggregate a single critical section? This confusion is evident in definitions of a critical section such
as "... a piece of code that can only be executed by one process or thread at a time". This only
works if all access to a protected resource is contained in one "piece of code", which requires
either the definition of a piece of code or the code itself to be somewhat contrived.
Note that on Windows NT (not 9x/ME), the function TryEnterCriticalSection() can be used to
attempt to enter the critical section. This function returns immediately so that the thread can do
other things if it fails to enter the critical section (usually due to another thread having locked it).
Note that the use of a Critical Section is not the same as a Win32 Mutex, which is an object used
for inter-process synchronization. A Win32 Critical Section is for inter-thread synchronization (and
is much faster as far as lock times), however it cannot be shared across processes.
for execution. Critical sections should not be used as a long-lived locking primitive. They should
be short enough that the critical section will be entered, executed, and exited without any
interrupts occurring, from neither hardware much less the scheduler.
2.2. Semaphores
The first major advance in dealing with the problems of concurrent processes came in 1965 with
Dijkstra's treatise. The fundamental principle is this: two or more processes can cooperate by
means of simple signal, such that a process can be forced to stop at a specified place until it has
received a specified signal. Any complex coordination requirement can be satisfied by the
appropriate structure of signals. For signaling, special variables called semaphores are used. To
transmit a signal via semaphores, a process executes the primitive signal(s).
To receive a signal via semaphore s, a process executes the primitive wait(s); if the corresponding
signal has not yet been transmitted, the process is suspended until the transmission takes place.
To achieve the desired effect, we can view the semaphore as a variable that
ii. monitors
2.3. Monitors
Monitors are common high-level synchronization tools which solve some of the problems
associated with semaphores. Monitors are actually a much nicer way of implementing mutual
exclusion than semaphores. One of the reasons for this is that, the code that implements mutual
exclusion is all in one place, the monitor. With semaphores, code can be distributed all over the
place in the form of wait and signal semaphore function calls. Additionally, it is next to impossible
to setup a monitor incorrectly. On the other hand with semaphores, it is quite common to do a
wait (B) when you should have done a wait (C). Simple little mistakes are easy to be made with
semaphores.
the incrementing of the semaphore. The signal function on a Monitor‘s condition variable is
different. If there are no processes blocked on the condition variable then the signal function
does nothing. The signal is not remembered. In order to remember "empty signals", you have to
use some other form of variables. The good part of this is that using other variables within a
monitor is simple because we can be assured that mutual exclusion is being implemented.
MODULE 3
Deadlocks and Memory Management
Introduction:
In a multiprogramming environment, several processes may compete for a finite number of
resources. A process requests resources; if the resources are not available, at that time, the
process enters a wait state. Waiting processes may never again change state, because the
resources they have requested for are held by other waiting processes. This situation is called
deadlock. We have already mentioned this briefly in module 2 in connection with semaphores. In
this module, you will be taken through methods that an operating system can use to prevent or
deal with deadlocks.
1. =
five instances. A process must request a resource before using it, and must release the resource
after using it. A process may request as many resources as it requires carrying out its designated
task. Obviously, the number of resources requested may not exceed the total number of
resources available in the system
i.e. a process cannot request three printers if the system has only two.
Under normal mode of operation, a process may utilize a resource in only the following sequence.
1. Request: If the request cannot be granted immediately (for example, if the resource is been
used by another process), then the requesting process must wait until it can acquire the resource.
2. Use: The process can operate on the resource (for example, if the resource is a printer, the
process can print on the printer)
3. Release: The process releases the resource.
Request and release of resources can be accomplished through the wait and signal operations on
semaphores. Therefore, for each use, the operating system checks to make sure that the using
process has requested and been allocated the resource. A system table records whether each
resource is free or allocated, and, if a resource is allocated, to which process. If a process requests
a resource that is currently allocated to another process, it can be added to a queue of processes
waiting for this resource.
A set of processes is in a deadlock state when every process in the set is waiting
for an event that can only be caused by another process in the set. To illustrate deadlock state,
consider a system with three tape drives. Suppose each of three processes holds one of these
tape drives. If each process now requests another tape drive, the three processes will be in
deadlock. Each is waiting for the event
―tape drive is releasedǁ which can be caused only by one of the other waiting processes. This
example illustrates a deadlock involving the same resource type.
Deadlocks may also involve different resource type. E.g. consider a system with one printer and
one tape drive. Suppose that process P1 is holding the tape drive and process P2 is holding the
printer. If P1 requests the printer and P2 requests the tape drive, a deadlock occurs.
A deadlock is also called a deadly embrace. Deadlocks occur most commonly in multitasking and
client/server environments. Therefore, a programmer who is developing multithreaded
applications must pay particular attention to this problem: Multithreaded programs are good
candidates for deadlock because multiple threads can compete for shared resources.
process requests that resource, the requesting process must be delayed until the resource has
been released.
2. Hold-and-wait condition: A process must be holding at least one resource and waiting to
acquire additional resources that are currently being held by other processes.
3. No-preemption condition: Resources cannot be preempted; i.e. only a process holding a
resource may voluntarily release the resource after completing its task.
4. Circular-wait condition: two or more processes form a circular chain where each process
waits for a resource that the next process in the chain holds. i.e. A set (P0, P1, …, Pn) of waiting
processes must exist such that P0 is waiting for a resource that is held by P1, P1 is waiting for a
resource that is held by P2, …, Pn-1 is waiting for a resource that is held by Pn, and Pn is waiting
for a resource that is held by P0. Deadlock only occurs in systems where all these four conditions
hold. You should note that the circular-wait condition implies hold-and-wait condition. So, the
four conditions are not completely independent.
allocated to process Pi. A directed edge Pi → Rj is called a request edge; a directed edge Rj → Pi
is called an assignment edge.
Pictorially, each process Pi is represented as a circle, and each resource type Rj as a square. Since
resource type Rj may have more than one instance, we represent each such instance as a dot
within the square. You should note that a request edge points only to the square whereas an
assignment edge must also designate one of the dots in the square. When a process Pi request
an instance of resource type Rj, a request edge is inserted in the resource-allocation graph.
When this request can be fulfilled; the request edge is instantaneously transformed to an
assignment edge. When the process no longer needs access to the resource it releases the
resource, and as a result the assignment edge is deleted.
Figure 3.1.3: Resource-Allocation Graph (RAG)
The resource-allocation graph shown in figure 3.1.3 depicts the following situation:
1. The sets P, R and E
i. P = (P1, P2, P3)
ii. R = (R1, R2, R3, R4)
iii. E = {P1 → R1, P2 → R3, R1 → P2, R2 → P2, R2 → P1, R3 → P3}
2. Resource instances:
i. One instance of resource type R1
ii. Two instances of resource type R2
iii. One instance of resource type R3
Given the definition of a RAG, it can be shown that, if the graph contains no cycles, then no
process in the system is deadlocked. If the graph does contain a cycle, then a deadlock may exist.
If each resource type has exactly one instance, then a cycle implies that deadlock has occurred. If
the cycle involves only a set resource types, each of which has only a single instance, then a
deadlock has occurred. Each process involved the cycle is deadlocked. In this case, a cycle in the
graph is both a necessary and a sufficient condition for the existence of deadlock. If each resource
type has several instances, then a cycle does not necessarily imply that a deadlock has occurred.
In this case, a cycle in the graph is both a necessary but not a sufficient condition for the existence
of deadlock.
To illustrate this concept, let us return to the R-A graph depicted in figure 3.1.4
above. Suppose process P3 requests an instance of resource type R2. Since no resource instance
is currently available, a request edge P3 → R2 is added to the graph (see figure 3.1.4). At this
point, two minimal cycles exist in the system: P 1 → R1 → P2 → R3 → P3 → R2 → P1
P2 → R3 → P3 → R2 → P2
Figure 3.1.4: Resource-Allocation Graph with a Deadlock
Processes P1, P2, and P3 are deadlocked. Process P2 is waiting for resource R3, which is held by
process P3. Process P3, on the other hand, is waiting for either Process P1 or P2 to release
resource R2. In addition, process P1 is waiting for P2 to release resource R1. Now consider the R-
A graph in figure 3.1.4.
3. We can ignore the problem altogether, and pretend that deadlocks never occur in the
system. This method is use by most operating system including UNIX.
We shall elaborate briefly on each method. Then in the later units, we shall present you detailed
algorithms.
To ensure that deadlocks never occur, the system can use either deadlock- prevention or a
deadlock-avoidance scheme. Deadlock-prevention is a set of methods for ensuring that at least
one of the necessary conditions (study session 2) cannot hold. These methods prevent deadlocks
by constraining how requests for resources can be made. These methods are discussed in study
session 2.
Deadlock avoidance, on the other hand, requires that the operating system be given in advance
additional information concerning which resources a process will request and use during its
lifetime. With this additional knowledge, we can decide for each request whether or not the
process should wait. To decide whether the current request can be satisfied or must be delayed,
the system must consider the resources currently allocated to each process, and the future
requests and releases of each process. These schemes are discussed in later units.
If system does not employ either a deadlock-prevention or a deadlock- avoidance algorithm, then
a deadlock situation may occur. In this environment, the system can provide an algorithm that
examines the state of the system to determine whether a deadlock has occurred, and an
algorithm to recover from the deadlock (if a deadlock has indeed occurred). This issue is
discussed in study session 2.
If the system does not ensure that a deadlock will never occur, and also does not provide a
mechanism for deadlock detection and recovery, then we may arrive at a situation where the
system is in deadlock state yet has no way of recognizing what has happened. In this case, the
undetected deadlock will result in the deterioration of the system performance, because
resources are being held
by processes that cannot run, and because more and more processes, as they make requests for
resources, enter a deadlock state. Eventually, system will stop functioning and will need to be
restarted manually. Although this method does not seem to be a viable approach to the deadlock
problem, it is nevertheless used in some operating systems. In many systems, deadlocks occur
infrequently like once in a year. Therefore, this method is cheaper than the costly deadlock-
prevention, deadlock-avoidance, or deadlock-detection and recovery methods that must be used
constantly.
STUDY SESSION 2
Methods for Dealing with Deadlocks
Introduction
As you have seen in the previous study session, for a deadlock to occur, each of the four
necessary conditions must hold. You were also introduced to some of the methods for handling a
deadlock situation. In this unit, you will be fully exposed to deadlock prevention and deadlock
avoidance approaches. As discussed before, deadlock prevention is all about ensuring that at
least one of the four necessary conditions cannot hold, we will elaborate further by examining
each of the four conditions separately. Deadlock avoidance is an alternative method for avoiding
deadlocks which takes care of some of the shortcomings of deadlock-prevention such as low
device utilization and reduced system throughput. In this study session, you will therefore learn
how some of the algorithms for deadlock prevention, deadlock avoidance and deadlock
detection and recovery are implemented.
The mutual exclusion condition must hold for non-sharable resources. For instance, a printer
cannot be simultaneously shared by several processes. Sharable resources, on the other hand, do
not require mutually exclusive access, and thus cannot be involved in a deadlock. Read-only files
are a good example of a sharable resource. If several processes attempt to open a read-only file
at the same time, they can be granted simultaneous access to the file. A process never needs to
wait for a sharable resource. In general, however, we cannot prevent deadlocks by denying the
mutual-exclusion condition: Some resources are intrinsically non-sharable. Algorithms that avoid
mutual exclusion are called non-blocking synchronization algorithms.
2.1.2 Hold and Wait
To ensure that the hold-and-wait condition never occurs, in the system, we must guarantee that,
whenever a process requests a resource, it does not hold any other resources. One protocol that
can be used requires each process to request and be allocated all resources before it begins
execution. We can implement this provision by requiring that system calls requesting resources
for a process precede all other system calls. An alternative protocol allows a process to request
resources only when the process has none. A process may request some
resources and use them. Before it can request any additional resources, however, it must release
all the resources that it is currently allocated.
To illustrate the difference between these two protocols, we consider a process that copies data
from a tape drive to a disk file, sorts the disk file, and then prints the results to a printer. If all
resources must be requested at the beginning of the process, then the process must initially
request the tape drive, disk file and printer. It will hold the printer for its entire execution, even
though it needs the printer only at the end. The second method allows the process to request
initially only the tape drive and disk file. It copies from the tape drive to the disk file. The process
must then request the disk file and the printer. After copying the disk file to the printer, it releases
these two resources and terminates.
These protocols have two main disadvantages:
i. Resource utilization may be low, since many of the resources may be allocated but unused
for a long period. In the example given, for instance, we can release the tape drive and disk file,
and again request the disk file and printer, only if we can sure that our data will remain on the disk
file. If we cannot be assured that they will, then we must request all resources at the beginning for
both protocols.
ii. Starvation is possible. A process that needs several popular resources may have to wait
indefinitely; because at least one of the resources that it needs is always allocated to some other
process.
2.1.3 No Pre-emption
The third necessary condition is that there be no pre-emption of resources that have been
allocated. To ensure that this condition does not hold, we can use the following protocol. If a
process is holding some resources and requests another resource that cannot be immediately
allocated to it (that is, the process must wait), then all resources are preempted. In other words,
these resources are implicitly released. The pre-empted resources are added to the list of
resources
Fig 3.2.2: No Pre-emption
for which the process is waiting. The process will be started only when it can regain its old
resources, as well as the new ones that it is requesting.
Alternatively, if a process requests some resources, we first check whether they are available. If
they are, we allocate
them. If they are not available we check
whether they are allocated to some other processes; i.e. which waiting for additional resources. If
so, we pre-empt the desired resources from the waiting process and allocate them to the
requesting process. If the resources are not either available or held by a waiting process, the
requesting process must wait. While it is waiting, some of its resources may be pre-empted, but
only if another process requests them. A process can be restarted only when it is allocated the
new resources it is requesting and recovers any resources that were pre-empted while it is
waiting. This protocol is often applied to resources whose state can be easily saved and restored
later such as CPU registers and memory space. It cannot generally be applied to such resources
as printer and tape drives.
circular wait cannot occur. Another approach is to allow holding only one resource per process; if
a process requests another resource, it must first free the one it's currently holding (or hold-and-
wait).
examines the resource-allocation state to ensure that a circular wait condition can never exist.
The resource-allocation state is defined by the number of available and allocated resources, and
the maximum demands of the processes. One known algorithm that is used for deadlock
avoidance is the Banker's algorithm, which requires resource usage limit to be known in advance.
However, for many systems it is impossible to know in advance what every process will request.
This means that deadlock avoidance is often impossible.
Two other algorithms are Wait/Die and Wound/Wait, each of which uses a symmetry breaking
technique. In both these algorithms there exists an older process (O) and a younger process (Y).
Process age can be determined by a timestamp at process creation time. Smaller timestamps are
older processes, while larger timestamps represent younger processes.
It is important to note that a process may be in unsafe state but would not result in a deadlock.
The notion of safe/unsafe state only refers to the ability of the system to enter a deadlock state or
not. For example, if a process requests A which would result in an unsafe state, but releases B
which would prevent circular wait, then the state is unsafe but the system is not in deadlock.
A state is safe if the system can allocate resources to each process (up to its maximum) in some
order and still avoid a deadlock. More formerly, a system is in a safe state only if there exists a
safe sequence. A sequence of processes <P1, P2,,…, Pn> is a safe sequence for the current
allocation state if, for each Pi, the resources that Pi, can still request can be satisfied by the
currently available resources plus the resources held by all the Pj, with j < i. In this situation, if the
resources that process Pi needs are not immediately available, then Pi can wait until all Pj have
finished. When they have finished, Pi can obtain all its needed resources, complete its designated
task, return its allocated resources and terminate. When Pi terminates, Pi+1 can obtain its needed
resources and so on. If no such sequence exists, then the system state is said to be unsafe.
A safe state is not a deadlock state. Conversely, a deadlock state is an unsafe state. Not all unsafe
states are deadlocks, however, an unsafe state may lead to a
deadlock. As long as the state is safe, the operating system can avoid unsafe (and deadlock)
state. In an unsafe state, the operating system cannot prevent processes from request resources
such that a deadlock occurs: The behaviours of the processes control unsafe states.
Figure 3.2.3: Safe, Unsafe, and Deadlock State Spaces
To illustrate, let us consider a system with 12 magnetic tape drives, and processes: P0, P1, and
P2. Process P0, requires 10 tape drives, process P1 may need as many as 4, and process P2 may
need up to 9 tape drives. Suppose that at time t0, process P0 is holding 5 tape drives, process P1
is holding 2, and process P2 is holding 2 tape drives. Therefore, there are 3 free tape drives.
At time t0, the system is in a safe state. The sequence < P1, P0 P3> satisfies the safety condition,
since process P1 can immediately be allocated all its tape drives and then return them (the system
will then have 5 available tape drives), then process P0 can get all its tape and return them (the
system will then have 10 available tape drives), and finally process P2 could get all its tape drives
and return them (the system will then have all its 12 tape drives available).
A system may go from a safe state to an unsafe state. Suppose that at time t1, process P2
requests and is allocated one more tape drive. The system is no longer in a safe state. At this
point, only process P1 can be allocated all its tape drive. If we had made P2 wait until either of
the other processes had finished and released its resources, then we could have avoided the
deadlock. Given the concept of safe state, we can define avoidance algorithms that ensure that
the system will never deadlock. The idea is simply to ensure that the system will always remain in
a safe state. Initially, the system is in a safe state. Whenever a process requests a resource that is
currently available, the system must decide whether the resource can be allocated immediately or
whether the process must wait. The request is granted only if the allocation leaves the system in a
safe state. In this scheme, if a process requests a resource that is currently available, it may still
have to wait. Therefore, resource utilization may be lower than it would be without a deadlock-
avoidance algorithm.
2.2.2 Resource-Allocation Graph Algorithm
If we have a resource-allocation system with only one instance of each resource type, a variant of
the resource-allocation graph defined in Section 2.2.2 of the last unit can be used for deadlock
avoidance. In addition to the request and
assignment edges, we introduce a new type of edge, called a claim edge. A claim edge Pi → Rj
indicates that process Pi may request resource Rj at some time in the future. This edge resembles
a request edge in direction, but is represented by a dashed line. When process Pi requests
resource Rj, the claim
Figure 3.2.4: Resource-Allocation graph for deadlock avoidance
2.2. Suppose that P2 requests R2. Although R2 is currently free, we cannot allocate it to P2, since
this will create a cycle in the graph (Figure 3.2.5). A cycle indicates that the system is in an unsafe
state. If P1 requests R2, and P2 requests R1, then a deadlock will occur.
2.2.3.1 Algorithm
The Banker's algorithm is run by the operating system whenever a process requests resources.
The algorithm prevents deadlock by denying or postponing the request if it determines that
accepting the request could put the system in an unsafe state (one where deadlock could occur).
2.2.3.2 Resources
For the Banker's algorithm to work, it needs to know three things:
1. How much of each resource each process could possibly request
2. How much of each resource each process is currently holding
3. How much of each resource the system has available
Some of the resources that are tracked in real systems are memory, semaphores and interface
access.
2.2.3.3 Safe and Unsafe States
A state (as in the above example) is considered safe if it is possible for all processes to finish
executing (terminate). Since the system cannot know when a process will terminate, or how many
resources it will have requested by then, the system assumes that all processes will eventually
attempt to acquire their stated maximum resources and terminate soon afterward. This is a
reasonable assumption in most cases since the system is not particularly concerned with how long
each process runs (at least not from a deadlock avoidance perspective).
Also, if a process terminates without acquiring its maximum resources, it only makes it easier on
the system. Given that assumption, the algorithm determines if a state is safe by trying to find a
hypothetical set of requests by the processes that would allow each to acquire its maximum
resources and then terminate (returning its resources to the system). Any state where no such set
exists is an unsafe state.
2.2.3.4 Requests
When the system receives a request for resources, it runs the Banker's algorithm to determine if it
is safe to grant the request. The algorithm is fairly straight forward once the distinction between
safe and unsafe states is understood.
1. Can the request be granted?
If not, the request is impossible and must either be denied or put on a waiting list
2. Assume that the request is granted
3. Is the new state safe?
i. If so, grant the request
ii. If not, either deny the request or put it on a waiting list
Whether the system denies an impossible or unsafe request or makes it wait, is an operating
system specific decision.
.
2.2.3.5 Trade-offs
Like most algorithms, the Banker's algorithm involves some trade-offs. Specifically, it needs to
know how much of each resource a process could possibly request. In most systems, this
information is unavailable, making the Banker's algorithm useless. Besides, it is unrealistic to
assume that the number of processes is static. In most systems, the number of processes varies
dynamically. Moreover, the requirement that a process will eventually release all its resources
(when the process terminates) is sufficient for the correctness of the algorithm, however it is not
sufficient for a practical system. Waiting for hours (or even days) for resources to be released is
usually not acceptable.
computed for a long time, and the results of these partial computations must be discarded and
probably recomputed later.
2. Abort one process at a time until the deadlock cycle is eliminated: this method incurs
considerable overhead, since, after each process is aborted, a deadlock detection algorithm must
be invoked to determine whether any processes are still deadlocked. Aborting a process may not
be easy. If the process was in the midst of updating a file, terminating it will leave that file in an
incorrect state. Similarly, if the process was in the midst of printing data on the printer, the system
must reset the printer to a correct state before printing the next job. If the partial termination
method is used, then, given a set of deadlocked processes, we must determine which process (or
processes) should be terminated in an attempt to break the deadlock. This determination is a
policy decision, similar to CPU scheduling problems. The question is basically an economic one.
We should abort those processes, the termination of which will incur the minimum cost.
Unfortunately, the term minimum cost is not a precise one. Many factors may determine which
process is chosen, including:
1. What the priority of the process is.
2. How long the process has computed, and how much longer the process will compute
before completing its designated task?
3. How many and what type of resources the process has used (for example, whether the
resources are simple to pre-empt)?
4. How many more resources the process needs in order to complete?
5. How many processes will need to be terminated?
6. Whether the process is interactive or batch?
2.5.2 Resource Pre-emption
To eliminate deadlocks using resource pre-emption, we successfully pre-empt some resources
from processes and give these resources to other processes until the deadlock cycle is broken.
If pre-emption is required to deal with deadlocks, then three issues need to be addressed:
1. Selecting a victim: which resources and which processes are to be pre- empted?
As in process termination, must determine the order of pre-emption to minimize cost. Cost
factors may include such parameters as the number of resources a deadlock process is holding,
and the amount of time a deadlocked process has thus far consumed during its execution.
2. Rollback: if you pre-empt a resource from a process, what should be done with that
process? Clearly it cannot continue with its normal execution; because it is missing some needed
resource. You must roll back the process to some safe state, and restart it from that state.
Since, in general, it is difficult to determine what a safe state is, the simplest solution is a total
rollback. Abort the process and then restart it. However, it is more effective to roll back the
process only as far as necessary to break the deadlock. On the other hand, this method requires
the system to keep more information about the state of all the running processes.
3. Starvation: how do we ensure that starvation will not occur? That is, how can we guarantee
that resources will not always be pre-empted from the same process? In a system where victim
selection is based primarily on cost factors, it may happen that the same process is always picked
as a victim. As a result, this process never completes its designated task, a starvation situation
that needs to be dealt with in any practical system. Clearly, we must ensure that a process can be
picked as a victim only a (small) finite number of times. The most common solution is to include
the number of rollbacks in the cost factor.
2.6 Live-lock
A live-lock is similar to a deadlock, except that the states of the processes involved in the live-lock
constantly change with regard to one another, none
progressing. Live-lock is a special case of resource starvation; the general definition only states
that a specific process is not progressing. As a real-world example, live-lock occurs when two
people meet in a narrow corridor, and each tries to be polite by moving aside to let the other
pass, but they end up swaying from side to side without making any progress because they
always both move the same way at the same time. Live-lock is a risk with some algorithms that
detect and recover from deadlock. If more than one process takes action, the deadlock detection
algorithm can repeatedly trigger. This can be avoided by ensuring that only one process (chosen
randomly or by priority) takes action.
STUDY SESSION 3
Memory Management
Introduction:
In module 2, you were shown how the CPU can be shared by a set of processes. As a result of
CPU scheduling, we can improve both the utilization of the CPU and the speed of the computer‘s
response to its users. To realize this increase in performance, however, we must keep several
processes in memory; that is, we must share memory. In this module, we will discuss various ways
to manage memory. The memory management algorithms vary from a primitive bare- machine
approach to paging and segmentation strategies. Each approach has its own advantages and
disadvantages. Selection of a memory-management
method for a specific system depends on many factors, especially on the
hardware design of the system.
As you shall see, many algorithms require hardware support, although recent designs have closely
integrated the hardware and operating system. As you learnt in the first module of this course,
memory is central to the operation of a modern computer system. Memory consists of a large
array of words or bytes, each with its own address. The CPU fetches instructions from memory
according to the value of the program counter. These instructions may cause additional loading
from and storing to specific memory addresses. A typical instruction-execution cycle, for example,
first fetches an instruction from memory. The instruction is then decoded and may cause
operands to be fetched from memory. After the instruction has been executed on the operands,
results may be stored back in memory. The memory unit sees only a stream of memory
addresses; it does not know how they are generated (by the instruction counter, indexing,
indirection, literal addresses, etc.) or what they are for (instructions or data). Accordingly, we can
ignore how a memory address is generated by a program. We are interested in only the
sequence of memory addresses generated by a running program.
Usually, a program resides on a disk as a binary executable file. The program must be brought
into memory and placed within a process for it to be executed. Depending on the memory
management in use, the process may be moved between disk memories during its execution.
The collection of processes on the
disk that is waiting to be brought into memory for execution forms the input
queue. The normal procedure is to select one of the processes in the input queue and to load
that process into memory. As the process is executed, it accesses instructions and data from
memory. Eventually, the process terminates, and its memory space is declared available.
Most systems allow user process to reside in any part of the physical memory. Therefore,
although the address space of the computer starts at 00000, the first address of the user process
does not need to be 00000. This arrangement affects the addresses that the user program can
use. In most cases, a user program will go through several steps, some of which may be optional,
before being executed (Figure 3.3.1). Addresses may be represented in different ways during
these steps. Addresses in the source program are generally symbolic (such as count). Compilers
will typical bind these symbolic addresses to relocatable addresses (such as ―14 bytes from the
beginning of this moduleǁ). The linkage editor or
loader will in turn bind these relocatable addresses to absolute addresses (such as 74014). Each
binding is a mapping from one address space to another.
Classically, the binding of instructions and data to memory addresses can be done at any step
along the following ways:
Fig 3.3.1: Address Binding
Compile time: If you know at compile time where the process will reside in memory, then
absolute code can be generated. For instance, if you know a priori that a user process resides
starting at location R, then the generated compiler
code will start at that location and extend up from there. If at some later time, the starting
location changes, then it will be necessary to recompile this code. The MS-DOS .COM-format
programs are absolute code bound at compile time. Load time: If it is not known at compile time
where the process will reside in memory, then the compiler must generate relocatable code. In
this case, final binding is delayed until load. If the starting address changes, you need only to
reload the user code to incorporate this changed value.
Execution time: If the process can be moved during its execution from one memory segment to
another, then binding must be delayed until run time. Special hardware must be available for this
scheme to work, as you will learn in the next section. Most general purpose operating system
uses this method. A major part of this module is devoted to showing how these various bindings
can
Meanwhile, we illustrate this mapping with a simple MMU scheme which is a generalization of
the base-register scheme. This method requires the hardware support illustrated in figure
3.2.3. The base register here is called relocation register. The value in the relocation register is
added to every address generated by a user process at the time it is sent to memory. For
example, if the base is at 14000, then an attempt by the user to address location 0 is dynamically
relocated to location 14000; an access to location 455 is mapped to location 14455. The MS-DOS
operating system running on the Intel 80x 86 family of processors uses four relocation registers
when loading and running processes. The user program never sees the real physical addresses.
The program can create a pointer to location 455, store it in memory, manipulate it and compare
it to other addresses – all as the number 455. Only when it is used as a memory address (in an
indirect load or store, perhaps) is it relocated relative to the base register.
The user program deals with logical addresses. The memory-mapping hardware converts logical
addresses into physical addresses. The final location of a referenced memory address is not
determined until the reference is made. We now have two different types of addresses: logical
addresses (in the range of 0 to max) and physical addresses (in the range R + 0 to R + max for
base value R). The user generates only logical addresses and thinks that the process runs in
location 0 to max. The user program supplies logical addresses, these logical addresses must be
mapped to physical addresses before they are used.
Note that, the concept of logical-address space that is bound to a separate
physical address space is central to proper memory management.
program image. The concept of dynamic linking is similar to that of dynamic loading. Rather than
loading being postponed until execution time, linking is postponed. This feature is usually used
with system libraries, such as language subroutine libraries. Without this facility, all programs on a
system need to have a copy of their language library (or at least the routine referenced by the
program) included in the executable image. This requirement wastes both disk space and main
memory. With dynamic linking, a stub is included in the image for each library-routine reference.
This stub is a small piece of code that indicates how to locate the appropriate memory-resident
library routine or how to load the library if the routine is not already present.
When this stub is executed, it checks to see whether the needed routine is already in memory. If
not, the program loads the routine into memory. Either way, the stub replaces itself with the
address of the routine, and executes the routine. Hence, the next time that the code segment is
reached, the library routine is executed directly incurring no cost for dynamic linking. Under this
scheme, all processes that use a language library execute only one copy of the library code. This
feature can be extended to library updates (such as bug fixes). A library may be replaced by a
new version, and all programs that reference the library will automatically use the new version.
Without dynamic linking, all such programs would need to be relinked to gain access to the new
library. So that programs will not accidentally execute new, incompatible versions of libraries,
version information is included in both the program and the library. More than one version of a
library may be loaded into memory, and each program uses its version information to decide
which copy of the library to use. Minor changes retain the same version number, whereas major
changes increment the version number. Therefore, only programs that are compiled with new
library version are affected by the incompatible changes incorporated in it. Other programs linked
before the new library was installed
will continue using the older library. This system is also known as shared libraries.
Unlike dynamic loading, dynamic linking generally requires help from the operating system. If the
processes in memory are protected from one another, then the operating system is the only entity
that can check to see whether the needed routine is in another process‘s memory space, or that
can allow multiple processes to access the same memory addresses.
2.5 Overlays
You can use overlays to enable a process to be larger than the amount of memory allocated to it.
The idea is to keep in memory only the instructions and data that are need at any given time.
When other instructions are needed, they are loaded into space previously occupied by
instructions that are no longer needed. For example, consider a two- pass assembler. During pass
1, it constructs a symbol table and during pass 2, it generates machine-language code. You may
be able to partition such an assembler into pass 1 code, pass 2 codes, the symbol table and
common support routines used by both passes 1 and
2. Assume that the sizes of these components are as follows: Pass 1 90 KB
Pass 2 60 KB Symbol table 40 KB
Common routines 50 KB
To load everything at once, you would require 240 KB of memory. If only 200 KB is available, you
cannot run your process. However, notice that pass 1 and pass 2 do not need to be in memory at
the same time. You can therefore define two overlays as follows:
Overlay A is the symbol table, common routines and pass 1. Overlay B is the symbol table,
common routines and pass 2.
You then add an overlay driver of say 10 KB and start with overlay A in memory. When you finish
pass 1, you jump to the overlay driver which reads overlay B into memory overwriting overlay A,
and then transfer control to pass
2. Overlay A needs 180 KB while overlay B needs only 150 KB (see figure 3.2.4). You can then run
your assembler in the 200 KB memory. It will load faster due to the fact that fewer data need to
be transferred before execution
starts. However, it will run slower, due to the extra I/O to read the code for overlay B over the
code for overlay A.
The codes for overlay A and B are kept on disk as absolute memory images, and are read by the
overlay drivers as needed.
As in dynamic loading, overlays do not require any special support from the operating system.
2.5 Swapping
As you have learnt so far in the course, a process needs to be in memory to be executed. A
process, however, can be swapped temporarily out of memory to a backing store, and then
brought back into memory for continued execution. For example, assume a multiprogramming
environment with a round-robin CPU- scheduling algorithm. When a quantum expires, the
memory manager will start to swap out the process that just finished, and to swap in another
process to the
memory space that has been freed (Figure 3.3.5). In the meantime, the CPU scheduler will
allocate a time slice to some other process in memory. When each process finishes its quantum, it
will be swapped with another process.
Normally a process that is swapped out will be swapped back into the same memory space that
it occupied previously
especially if binding is done at assembly or load time. However, if execution time binding is being
used, then the process can be swapped into a different memory space because the physical
addresses are computed at execution time. As earlier mentioned, swapping requires a backing
store. The backing store is commonly a fast disk which must be large enough to accommodate
copies of all memory images for all users. It must also provide a direct access to these memory
images. The system maintains a ready queue consisting of all processes whose memory images
are on the backing store or in memory and are ready to run. Whenever the CPU scheduler
decides to execute a process, it calls the dispatcher. The dispatcher checks to see whether the
next process in the queue is in memory. If not, and there is no free memory region, the dispatcher
swaps out a process currently in memory and swaps in the desired process. It then reloads
registers as normal and transfer control to the selected process. The context-switch time in such a
swapping system is fairly high.
STUDY SESSION 4
Memory Allocation Techniques Section and Subsection Headings:
Introduction:
As you have learnt from the previous study session, memory management is essential to process
execution which is the primary job of the CPU. The main memory must accommodate both the
operating system and the various user processes. You therefore need to allocate different parts of
the main memory in the most efficient way possible.
In this unit, therefore, you will learn about some memory management algorithms such as
contiguous memory allocation and its different flavours. Also, the problems that may arise from
contiguous memory allocation (fragmentation) will be discussed in this study session.
longer in use. The method we are going to describe next is a generalization of the fixed-partition
scheme (called MVT). It is used primarily in a batch environment. The ideas presented are also
applicable to time-sharing environment in which pure segmentation is used for memory
management.
two. One part is allocated to the arriving process, the other is returned to the set of holes. When
a process terminates, it releases its block of memory. Which is then placed back in the set of
holes? If the new hole is adjacent to other holes, these adjacent holes are merged to form one
larger hole. At this point, the system may need to check whether there are processes waiting for
memory and whether this newly freed and recombined memory could satisfy the demands of any
of these waiting processes. The procedure is a particular instance of the general dynamic storage
allocation problem, which is how to satisfy a request of size n from a list of free holes. There are
many solutions to this problem. The set of holes is searched to determine which hole is best to
allocate.
2.3.1. First-Fit
In first-fit algorithm, you allocate the first hole that is big enough. Searching can start either at the
beginning of the set of holes or where the previous first-fit search ended. You can stop searching
as soon as you find a free hole that is large enough.
2.3.2 Best-Fit
In best-fit algorithm, you allocate the smallest hole that is big enough. You must search the entire
list from top to bottom except in a case where the list is ordered by size. This strategy produces
the smallest leftover hole.
2.3.3 Worst-Fit
In worst-fit algorithm, you allocate the largest available hole. As in best-fit, you must search the
entire list, unless the list is kept ordered by size. This strategy produces the largest leftover hole,
which may be more useful than the smaller leftover hole from a best-fit approach. It can be
shown, using techniques such as simulations, that both first-fit and best-fit are better than worst-fit
in terms of decreasing both time and storage utilization. Neither first-fit nor best-fit is clearly
better in terms of storage utilization, but first-fit is generally faster.
However, these algorithms suffer from external fragmentation. As processes are loaded and
removed from memory, the free memory space is broken into little pieces. External fragmentation
exists when enough total memory space exists to satisfy a request but it is not contiguous.
Storage is fragmented into large number of small holes. This fragmentation problem can be
severe. In the worst case, we could have a block of free (or wasted) memory between every two
processes. If all this memory were in one big free block, we might be able to run several more
processes. The selection of the first-fit versus best-fit strategies can affect the amount of
fragmentation. First-fit is better for some systems whereas best-fit is better for others. Another
factor is which end of a free block do you allocate? However, you should note that no matter
which algorithm you use, external fragmentation will be a problem.
2.4 Fragmentation
In the previous section you learnt about external fragmentation. You should, however, note that
memory fragmentation can be internal or external.
allocate the requested blocks, you are left with a hole of 2 bytes. The overhead to keep track of
this hole will be substantially larger than the hole itself. The general approach is to break the
physical memory into fixed-sized blocks, and allocate memory in unit of block sizes. With this
approach, the memory allocated to a process may be slightly larger than the requested memory.
The difference between these two numbers is internal fragmentation i.e. memory that is internal
to a partition but is not being used.
segmentation. You will be exposed to these two techniques in the next study session.
STUDY SESSION 5
Non-Contiguous Allocation Section and Subsection Headings:
Introduction:
In the last study session, you learnt about contiguous memory allocation in which it was
highlighted that external fragmentation is a major problem with this method of memory
allocation. It was also mentioned that compaction and non-contiguous logical address space are
solutions to external fragmentation. In this unit, we shall go further to discuss some of the
techniques for making the physical address space of a process non-contiguous such as paging,
segmentation, etc.
page number (p) and a page offset (d). The page number is used as an index into a page table.
The page table contains the based address of each page in physical memory. This base address is
combined with the page offset to define the physical memory address that is sent to the memory
unit.
The paging model of memory is shown in Figure 3.5.2 below.
The page size, like the frame size, is defined by the hardware. The size of a page is of power 2
and it varies between 512 bytes and 16 MB per page, depending on the computer architecture.
In other processors, assistance from the operating system is needed. An exception is raised, and
the operating system handles this exception by replacing one of the entries in the TLB with an
entry from the primary translation table, and the instruction which made the original memory
reference is restarted.
2.1.2 Protected memory
Hardware that supports virtual memory almost always supports memory protection mechanisms
as well. The MMU may have the ability to vary its operation according to the type of memory
reference (for read, write or execution), as well as the privilege mode of the CPU at the time the
memory reference was made. This allows the operating system to protect its own code and data
(such as the translation tables used for virtual memory) from corruption by an erroneous
application program and to protect application programs from each other and (to some extent)
from themselves (e.g. by preventing writes to areas of memory that contain code).
2.1.3 Issues with Paging
As you may have noticed, paging is a form of dynamic relocation. Every logical address is
bounded by the paging hardware to some physical address. Using paging is similar to using a
table of base/relocation registers, one for each frame. When you use a paging scheme, you have
no external fragmentation. However, internal fragmentation may occur since frames are allocated
as units.
2.2. Segmentation
Users do not think of memory as a linear array of bytes with some containing instructions and
other containing data. Instead users prefer to view memory as a collection of variable sized
segments with no necessary ordering among segments. See Figure 3.5.3 below.
The segment base contains the starting physical address where the segment resides in memory,
whereas the segment limit specifies the length of the segment. The use of a segment table is as
illustrated in Figure 2.4 below. A logical address consists of two parts: a segment number, s and
an offset into that segment, d. The segment number is used as an index into the segment table.
The offset d of the logical address must be between 0 and the segment limit.
If it is not, we trap to the operating system (logical addressing attempt beyond end of segment).
If this offset is legal, it is added to the segment base to produce the address in physical
memory of the desired byte. The segment table is therefore essentially an array of base-limit
register pairs.
2.2.2. Advantages and Problems of Segmentation
Some of the advantages include:
i. operating system may allow segments to grow and shrunk dynamically with unchanging
addressing
ii. protection on segment level of related data
iii. sharing on segment level is easy
The useful life of the program was limited to the employment of its programmer. During the
second generation, programmers started dividing their programs into sections that resembled
working sets, really segments, originally called roll in/roll out and now called overlays (see section
3 above). It was the concept of overlays that suggested paging and segmentation and led to
virtual memory, which was then, implemented through demand paging and segmentation
schemes. Before virtual memory, sharing meant that copies of files were stored in each user‘s
account. This allowed them to load their own copy and work on it at any time. This kind of sharing
created a great deal of unnecessary system cost—the I/O overhead in loading the copies and the
extra secondary storage needed. With virtual memory, those costs are substantially reduced
because shared programs and subroutines are loaded on demand, satisfactorily reducing the
storage requirements of main memory. The use of virtual memory requires cooperation between
the Memory Manager (which tracks each page or segment) and the processor hardware (which
issues the interrupt and resolves the virtual address). Virtual memory works well in a
multiprogramming environment because most programs spend a lot of time waiting—they wait
for I/O to be performed; they wait for pages to be swapped in or out; and in a time-sharing
environment, they wait when their time slice is up (their turn to use the processor is expired). In a
multiprogramming environment, the waiting time isn‘t lost, and the CPU simply moves to another
job. Virtual memory has increased the use of several programming techniques. For instance, it
aids the development of large software systems because individual pieces can be developed
independently and linked later on.
2.4.1 Advantages
Virtual memory management has several advantages:
i. A job‘s size is no longer restricted to the size of main memory (or the free space within
main memory).
ii. Memory is used more efficiently because the only sections of a job stored in memory are
those needed immediately, while those not needed remain in secondary storage.
iii. It allows an unlimited amount of multiprogramming, which can apply to many jobs, as in
dynamic and static partitioning, or many users in a time- sharing environment.
iv. It eliminates external fragmentation and minimizes internal fragmentation by combining
segmentation and paging (internal fragmentation occurs in the program).
v. It allows the sharing of code and data.
vi. It facilitates dynamic linking of program segments.
2.4.2 Disadvantages
The advantages far outweigh these disadvantages:
i. Increased processor hardware costs.
ii. Increased overhead for handling paging interrupts.
iii. Increased software complexity to prevent thrashing.