Processes
The chief task of an operating system is to manage a set of processes and to perform work on their behalf.
So far we have considered processes as independent. They interact in some way with the operating system, but we have not examined ways in which processes can interact with each other.
Processes that interact with each by cooperating to achieve a common goal are called concurrent processes.
Heavyweight processes (processes)
Lightweight processes (threads)
Many problems can be naturally viewed in a way that leads to concurrent programming.
Some tasks have inherent parallelism that can be exploited by concurrent processes.
Spooling: Overlap computation with IO.
Time | 1 | 2 | 3 | 4 | 5 |
---|---|---|---|---|---|
Job 1 | Print Output | ||||
Job 2 | Computation | Print Output | |||
Job 3 | Read Input | Computation | Print Output | ||
Job 4 | Read Input | Computation | Print Output | ||
Job 5 | Read Input | Computation | Print Output |
Multiprogramming: The computation (and IO) phases of several jobs are interleaved.
Time | 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | 9 |
---|---|---|---|---|---|---|---|---|---|
Job 1 | C | IO | IO | IO | C | IO | IO | IO | W |
Job 2 | W | C | IO | W | W | C | W | IO | IO |
Job 3 | IO | IO | C | IO | IO | W | C | C | IO |
Job 4 | IO | IO | W | C | IO | IO | IO | IO | C |
Multiprocessing: several jobs are executing in full parallelism on more than one CPU.
individual CPUs may each be dedicated to a single job or they may be multiprogrammed.
jobs may be run on a single CPU or they may migrate between CPUs over their lifetime.
Tightly-coupled general-purpose multiprocessor configurations using 2-8 CPU's to achieve increased throughput in tasks which could be done on a single processor, but more slowly. These systems often have shared memory.
Concurrency used to enhance the throughput of traditional systems.
Array processors used for intensely compute-bound tasks - perhaps using scores or hundreds of processors each working on a portion of a single large computation.
Concurrency used to tackle problems otherwise not solvable in reasonable time - e.g. weather forecasting, research in nuclear physics, sophisticated AI-based query processing for databases.
Distributed and network systems in which specific computing resources are shared over long distances. These often have distributed memory.
Concurrency used to share resources more effectively - e.g. file servers that allow a single hard disk to serve multiple PC's.
Processes can cooperate in two distinct ways:
Indirect cooperation: independent processes sharing the same resources
Direct cooperation: processes designed from the outset to work together on a common goal.
As mentioned above, may tasks lend themselves to a parallel solution and so concurrent processes are useful even on uniprocessor systems. In this case concurrency is being used as an abstraction tool.
For example: Suppose we needed a program to read punched cards (or scantron forms, etc.) and copy the data to a disk file. Assume that each card contains 80 characters and we need to add a CR/LF sequence at the end of each record in the disk file. Also, assume that data is written to the disk in blocks of 512 characters.
82 does not divide 512 evenly, so we can't read an integral number of cards and write that data as a single disk block.
The problem thus splits into to distinct tasks:
reading characters from the cards
writing blocks to the disk
Here are some possible ways that we could approach this problem, along with some commentary about them.
We could organize the program as a loop that produces one disk block on each iteration:
repeat for i := 1 to 512 do begin get-a-character; put it in slot i end; write the block until doneHowever, this approach has some difficulties:
get-a-character would naturally be a procedure. However, it must remember certain variables between calls. Sometimes it will need to read a physical card before it can return a character. Other times it will need to return a CR or an LF.
certain variables will need to be global or static
Since 82 does not evenly divide 512, we will almost always run out of cards while in the middle of a disk block and the remainder of the current disk block will need to be padded (with spaces or nulls). Could be handled one of several ways:
get-a-character can return padding characters when there are no more cards. Somehow it also has to signal the calling code that there are no more cards.
change the calling code so that the call to get-a-character is replaced by
if not out of card characters then begin get-a-character put it in slot i end else put a padding character in slot iThus the code calling get-a-character also needs to know some of get-a-characters state information.
As written, the code above does not allow for overlap of IO with computation since when a new card is needed the code blocks while it is read in. Ditto for writing the disk block.
We could try to organize the program the other way, with the card reading code calling a disk writer procedure to handle each character:
repeat read a card for i := 1 to 80 do put-a-character(card-image[i]) put-a-character(CR) put-a-character(LF) until out-of-cardsbut this has problems similar to those discussed above.
It is much cleaner to think of this problem in terms of two cooperating processes:
Card reader:
repeat read a card for i := 1 to 80 do send card-image[i] to writer process send CR to writer process send LF to writer process until out of cards send EOF to writer processDisk writer:
finished := false repeat for i := 1 to 512 do begin if not finished then receive a character from reader process if character received was EOF then finished := true disk-block[i] := last character received end write a block until finished.
Each process is modular, with a clean interface to the other.
However, We still cannot overlap IO, since the sending process must wait until the receiving process has accepted a character before sending another.
By adding another process to act as a buffer between the reader and writer processes we can overlap IO.
When the reader has read a card, it could very quickly send off all the characters to the buffer, and then go off to read another card.
The disk writer can take characters from the buffer as it needs them. While it is waiting on disk IO, the card reader can run ahead, adding characters to the buffer.
Some examples of concurrent programs:
A sorting race. (Ada)
The river crossing problem (Pascal on Unix)
Concurrency affects operating systems in at least three way:
Operating system design: Operating systems traditionally have been monolithic monitors. This is changing and many recent operating systems consist of a kernel process along with separate processes for the file system, various IO devices, memory management, etc.
User requests for service by devices are transformed into messages to the various device managers, mediated by the kernel.
Application program interface: As concurrency finds its way into applications programs, the operating system must provide support to the language processors to allow the language constructs to be implemented at run-time.
Multiprocessor operating system design: The design of operating systems for multiprocessor configurations (loosely coupled and distributed) is an area in its own right.
Key issues for the remainder of our discussion:
Discovering the potential for parallelism inherent in existing sequential algorithms to allow them to run more quickly.
Programming language notations for expressing parallelism.
Types of interrelationship between cooperating processes.
Controlling access to shared data - the Critical Section Problem
Message passing as an alternative to shared data for cooperating processes.
$Id: concurrency1.html,v 1.2 1998/03/03 23:42:03 senning Exp $
These notes were written by Prof. R. Bjork of Gordon College. In February 1998 they were editied and converted into HTML by J. Senning of Gordon College.