Only $2.99/month

Terms in this set (162)

a) What is the cost of 1 MB of main memory?
###
1*102410248*0.08024*1024*8*0.001cents=$84.

b) What is the cost of 1 MB of main memory using cache technology?
###
1*102410248*0.08024*1024*8*0.01cents=$840.

c) Design a main memory/cache system with 1 MB of main memory whose
effective access time is no more than 10% greater than cache memory
access time. What is its cost?
###
The effective access time ( from the equation 1.1 given in page 39)
with T1 = 100 ns and T2 = 1200 ns.

we get Ts = T1 +(1-H) (T1+T2)
= 100 + 0.05 * 1300 ns
= 165 ns

Since we need an access efficiency of 110 ns, the hit ratio has
to be improved:

110 = 100 + (1-H) * 1300

10/1300 = 1-H
H = 1290/1300 = 0.99

The ratio of the size ( s1/s2) has to be 0.1 ( for strong locality) by
looking at performance curve.

cache has .1 M and main memory is .9 M

cost is 83.89+75.49 = $159.38

A main memory system consists of a number of memory modules attached
to the system bus. When a write request is made, the bus is occupied for
100 nanosec by the data, address and control signals. During the same
100nanosec and for 500nanosec thereafter memory module executes one
cycle accepting, and storing the data. The operation of the memory
modules may overlap, but only one request can be on the bus any time.
Assume that there are eight such modules connected to the bus. What is
the maximum possible rate (in bytes per second) at which data can be
stored.
###
In a cycle, maximum possible data = 8* (100+500)/100=48bits=6bytes
The maximum possible rate= 6/0.0006=10000bytes/second
In IBM's mainframe OS, OS/390, one of the major modules in the kernel is the System
Resource Manager. This module is responsible for the allocation of resources among
address spaces (processes). The SRM gives OS/390 a degree of sophistication unique
among operating systems. No other mainframe OS, and certainly no other type of OS,
can match the functions performed by SRM. The concept of resource includes processor,
real memory, and I/O channels. SRM accumulates statistics pertaining to utilization
of processor, channel, and various key data structures. Its purpose is to provide optimum
performance based on performance monitoring and analysis. The installation sets
forth various performance objectives, and these serve as guidance to the SRM, which
dynamically modifies installation and job performance characteristics based on system
utilization. In turn, the SRM provides reports that enable the trained operator to refine
the configuration and parameter settings to improve user service.
This problem concerns one example of SRM activity. Real memory is divided
into equal-sized blocks called frames, of which there may be many thousands. Each
frame can hold a block of virtual memory referred to as a page. SRM receives control
approximately 20 times per second and inspects each and every page frame. If the
page has not been referenced or changed, a counter is incremented by 1. Over time,
SRM averages these numbers to determine the average number of seconds that a
page frame in the system goes untouched. What might be the purpose of this and what
action might SRM take?
Blocked : Blocked/Suspend: If there are no ready processes, then at least
one blocked process is swapped out to make room for another process that
is not blocked. This transition can be made even if there are ready processes
available, if the OS determines that the currently running process or a ready
process that it would like to dispatch requires more main memory to maintain
adequate performance.
• Blocked/Suspend : Ready/Suspend: A process in the Blocked/Suspend state
is moved to the Ready/Suspend state when the event for which it has been
waiting occurs. Note that this requires that the state information concerning
suspended processes must be accessible to the OS.
• Ready/Suspend : Ready: When there are no ready processes in main memory,
the OS will need to bring one in to continue execution. In addition, it
might be the case that a process in the Ready/Suspend state has higher priority
than any of the processes in the Ready state. In that case, the OS designer may
dictate that it is more important to get at the higher-priority process than to
minimize swapping.
• Ready : Ready/Suspend: Normally, the OS would prefer to suspend a
blocked process rather than a ready one, because the ready process can now
be executed, whereas the blocked process is taking up main memory space
and cannot be executed. However, it may be necessary to suspend a ready
process if that is the only way to free up a sufficiently large block of main
memory. Also, the OS may choose to suspend a lower-priority ready process
rather than a higher-priority blocked process if it believes that the blocked
process will be ready soon.
New : Ready/Suspend and New : Ready: When a new process is created, it
can either be added to the Ready queue or the Ready/Suspend queue. In either
case, the OS must create a process control block and allocate an address space
to the process. It might be preferable for the OS to perform these housekeeping
duties at an early time, so that it can maintain a large pool of processes that
are not blocked. With this strategy, there would often be insufficient room in
main memory for a new process; hence the use of the (New : Ready/Suspend)
transition. On the other hand, we could argue that a just-in-time philosophy of
creating processes as late as possible reduces OS overhead and allows that OS
to perform the process-creation duties at a time when the system is clogged
with blocked processes anyway.
• Blocked/Suspend: Blocked: Inclusion of this transition may seem to be poor
design. After all, if a process is not ready to execute and is not already in
main memory, what is the point of bringing it in? But consider the following
scenario: A process terminates, freeing up some main memory. There is a
process in the (Blocked/Suspend) queue with a higher priority than any of the
processes in the (Ready/Suspend) queue and the OS has reason to believe that
the blocking event for that process will occur soon. Under these circumstances,
it would seem reasonable to bring a blocked process into main memory in
preference to a ready process.
• Running : Ready/Suspend: Normally, a running process is moved to the
Ready state when its time allocation expires. If, however, the OS is preempting
the process because a higher-priority process on the Blocked/Suspend
queue has just become unblocked, the OS could move the running process
directly to the (Ready/Suspend) queue and free some main memory.
• Any State : Exit: Typically, a process terminates while it is running, either
because it has completed or because of some fatal fault condition. However, in
some operating systems, a process may be terminated by the process that created
it or when the parent process is itself terminated. If this is allowed, then a
process in any state can be moved to the Exit state.
Process identification data always include a unique identifier for the process (almost invariably an integer number) and, in a multiuser-multitasking system, data like the identifier of the parent process, user identifier, user group identifier, etc. The process id is particularly relevant, since it's often used to cross-reference the OS tables defined above, e.g. allowing to identify which process is using which I/O devices, or memory areas.
Processor state data are those pieces of information that define the status of a process when it's suspended, allowing the OS to restart it later and still execute correctly. This always include the content of the CPU general-purpose registers, the CPU process status word, stack and frame pointers etc.
Process control information is used by the OS to manage the process itself. This includes:
The process scheduling state (different from the task state above discussed), e.g. in terms of "ready", "suspended", etc., and other scheduling information as well, like a priority value, the amount of time elapsed since the process gained control of the CPU or since it was suspended. Also, in case of a suspended process, event identification data must be recorded for the event the process is waiting for. Process structuring information:process's children id's, or the id's of other processes related to the current one in some functional way, which may be represented as a queue, a ring or other data structures. Interprocess communication information: various flags, signals and messages associated with the communication among independent processes may be stored in the PCB. Process privileges, in terms of allowed/unallowed access to system resources.
Assign a unique process identifier to the new process. At this time, a new entry
is added to the primary process table, which contains one entry per process.
2. Allocate space for the process. This includes all elements of the process image.
Thus, the OS must know how much space is needed for the private user address
space (programs and data) and the user stack. These values can be assigned by
default based on the type of process, or they can be set based on user request
at job creation time. If a process is spawned by another process, the parent
process can pass the needed values to the OS as part of the process-creation
request. If any existing address space is to be shared by this new process, the
appropriate linkages must be set up. Finally, space for a process control block
must be allocated.
3. Initialize the process control block. The process identification portion contains
the ID of this process plus other appropriate IDs, such as that of the parent
process. The processor state information portion will typically be initialized
with most entries zero, except for the program counter (set to the program
entry point) and system stack pointers (set to define the process stack boundaries).
The process control information portion is initialized based on standard
default values plus attributes that have been requested for this process. For
example, the process state would typically be initialized to Ready or Ready/
Suspend. The priority may be set by default to the lowest priority unless an
explicit request is made for a higher priority. Initially, the process may own
no resources (I/O devices, files) unless there is an explicit request for these or
unless they are inherited from the parent.
4. Set the appropriate linkages. For example, if the OS maintains each scheduling
queue as a linked list, then the new process must be put in the Ready or
Ready/Suspend list.
5. Create or expand other data structures. For example, the OS may maintain
an accounting file on each process to be used subsequently for billing and/or
performance assessment purposes.
1. Important new transitions are the following :
• Blocked->Blocked/Suspend: If there are no ready processes, then at least one blocked process is swapped out to make room for another process that is not blocked. This transition can be made even if there are ready processes available, if the OS determines that the currently running process or a ready process that it would like to dispatch requires more main memory to maintain adequate performance.
• Blocked/Suspend->Ready/Suspend: A process in the Blocked/Suspend state is moved to the Ready/Suspend state when the event for which it has been waiting occurs. Note that this requires that the state information concerning suspended processes must be accessible to the OS.
• Ready/Suspend->Ready: When there are no ready processes in main memory, the OS will need to bring one in to continue execution. In addition, it might be the case that a process in the Ready/Suspend state has higher priority than any of the processes in the Ready state. In that case, the OS designer may dictate that it is more important to get at the higher-priority process than to minimize swapping.
• Ready ->Ready/Suspend: Normally, the OS would prefer to suspend a blocked process rather than a ready one, because the ready process can now be executed, whereas the blocked process is taking up main memory space and cannot be executed. However, it may be necessary to suspend a ready process if that is the only way to free up a sufficiently large block of main memory.Also, the OS may choose to suspend a lower-priority ready process rather than a higherpriority blocked process if it believes that the blocked process will be ready soon.
Several other transitions that are worth considering are the following:
• New->Ready/Suspend and New ->Ready: When a new process is created, it can either be added to the Ready queue or the Ready/Suspend queue. In either case, the OS must create a process control block and allocate an address space to the process. It might be preferable for the OS to perform these housekeeping duties at an early time, so that it can maintain a large pool of processes that are not blocked.With this strategy, there would often be insufficient room
in main memory for a new process;hence the use of the (New SReady/Suspend) transition. On the other hand, we could argue that a just-in-time philosophy of creating processes as late as possible reduces OS overhead and allows that OS to perform the process-creation duties at a time when the system is clogged with blocked processes anyway.
• Blocked/Suspend ->Blocked: Inclusion of this transition may seem to be poor design. After all, if a process is not ready to execute and is not already in main memory, what is the point of bringing it in? But consider the following scenario:
A process terminates, freeing up some main memory.There is a process in the Blocked/Suspend) queue with a higher priority than any of the processes in the (Ready/Suspend) queue and the OS has reason to believe that the blocking event for that process will occur soon. Under these circumstances, it would seem reasonable to bring a blocked process into main memory in preference to a ready process.
• Running->Ready/Suspend: Normally, a running process is moved to the Ready state when its time allocation expires. If, however, the OS is preempting the process because a higher-priority process on the Blocked/Suspend queue has just become unblocked, the OS could move the running process directly to the (Ready/Suspend) queue and free some main memory.
• Any State->Exit: Typically, a process terminates while it is running, either because it has completed or because of some fatal fault condition. However, in some operating systems, a process may be terminated by the process that created it or when the parent process is itself terminated. If this is allowed, then a process in any state can be moved to the Exit state.
2. Recall that the reason for all of this elaborate machinery is that I/O activities are much slower than computation and therefore the processor in a uniprogramming system is idle most of the time. But the arrangement of Figure 3.8b does not entirely solve the problem. It is true that, in this case, memory holds multipleprocesses and that the processor can move to another process when one process is blocked. But the processor is so much faster than I/O that it will be common for all of the processes in memory to be waiting for I/O. Thus, even with multiprogramming,
a processor could be idle most of the time. What to do? Main memory could be expanded to accommodate more processes. But there are two flaws in this approach. First, there is a cost associated with main memory, which, though small on a per-byte basis, begins to add up as we get into the gigabytes of storage. Second, the appetite of programs for memory has grown as fast
as the cost of memory has dropped. So larger memory results in larger processes, not more processes. Another solution is swapping, which involves moving part or all of a process from main memory to disk.When none of the processes in main memory is in the Ready state, the OS swaps one of the blocked processes out onto disk into a suspend queue.This is a queue of existing processes that have been temporarily kicked out of main memory, or suspended. The OS then brings in another process from the suspend queue, or it honors a new-process request. Execution then continues with the newly arrived process. Swapping, however, is an I/O operation, and therefore there is the potential for making the problem worse, not better. But because disk I/O is generally the fastest I/O on a system (e.g., compared to tape or printer I/O), swapping will usually enhance performance. With the use of swapping as just described, one other state must be added to our process behavior model (Figure 3.9a): the Suspend state. When all of the
processes in main memory are in the Blocked state, the OS can suspend one process by putting it in the Suspend state and transferring it to disk.The space that is freed in main memory can then be used to bring in another process. When the OS has performed a swapping-out operation, it has two choices for selecting a process to bring into main memory: It can admit a newly created process or it can bring in a previously suspended process. It would appear that the preference
should be to bring in a previously suspended process, to provide it with service rather than increasing the total load on the system. But this line of reasoning presents a difficulty. All of the processes that have been suspended were in the Blocked state at the time of suspension. It clearly would not do any good to bring a blocked process back into main memory, because it is still not ready for execution. Recognize, however, that each process in the Suspend state
was originally blocked on a particular event.When that event occurs, the process is not blocked and is potentially available for execution.
The VAX/VMS operating system makes use of four processor access modes to facilitate
the protection and sharing of system resources among processes. The access mode
determines
• Instruction execution privileges: What instructions the processor may execute
• Memory access privileges: Which locations in virtual memory the current instruction
may access The four modes are as follows:
• Kernel: Executes the kernel of the VMS operating system, which includes memory
management, interrupt handling, and I/O operations
• Executive: Executes many of the OS service calls, including file and record (disk
and tape) management routines
• Supervisor: Executes other OS services, such as responses to user commands
• User: Executes user programs, plus utilities such as compilers, editors, linkers, and
debuggers
A process executing in a less-privileged mode often needs to call a procedure that
executes in a more-privileged mode; for example, a user program requires an operating
system service. This call is achieved by using a change-mode (CHM) instruction,
which causes an interrupt that transfers control to a routine at the new access mode. A
return is made by executing the REI (return from exception or interrupt) instruction.
a. A number of operating systems have two modes, kernel and user. What are the
advantages and disadvantages of providing four modes instead of two?
b. Can you make a case for even more than four modes be accessible in domain D j but not accessible in domain D i , then we must have
j 6 i . But this means that every segment accessible in D i is also accessible in D j .
Explain clearly what the problem is that is referred to in the preceding quote.
OS/2 is an obsolete OS for PCs from IBM. In OS/2, what is commonly embodied in
the concept of process in other operating systems is split into three separate types
of entities: session, processes, and threads. A session is a collection of one or more
processes associated with a user interface (keyboard, display, and mouse). The session
represents an interactive user application, such as a word processing program
or a spreadsheet. This concept allows the personal-computer user to open more than
one application, giving each one or more windows on the screen. The OS must keep
track of which window, and therefore which session, is active, so that keyboard and
mouse input are routed to the appropriate session. At any time, one session is in
foreground mode, with other sessions in background mode. All keyboard and mouse
input is directed to one of the processes of the foreground session, as dictated by the applications. When a session is in foreground mode, a process performing video
output sends it directly to the hardware video buffer and thence to the user's screen.
When the session is moved to the background, the hardware video buffer is saved to
a logical video buffer for that session. While a session is in background, if any of the
threads of any of the processes of that session executes and produces screen output,
that output is directed to the logical video buffer. When the session returns to foreground,
the screen is updated to reflect the current contents of the logical video buffer
for the new foreground session.
There is a way to reduce the number of process-related concepts in OS/2 from
three to two. Eliminate sessions, and associate the user interface (keyboard, mouse,
and screen) with processes. Thus, one process at a time is in foreground mode. For
further structuring, processes can be broken up into threads.
a. What benefits are lost with this approach?
b. If you go ahead with this modification, where do you assign resources (memory,
files, etc.): at the process or thread level?
Consider the following code using the POSIX Pthreads API:
thread2.c
#include <pthread.h>
#include <stdlib.h>
#include <unistd.h>
#include <stdio.h>
int myglobal;
void thread_function(void arg) {
int i,j;
for ( i=0; i<20; i++ ) {
j=myglobal;
j=j+1;
printf(".");
fflush(stdout);
sleep(1);
myglobal=j;
}
return NULL;
}
int main(void) {
pthread_t mythread;
int i;
if ( pthread_create( &mythread, NULL, thread_function,
NULL) ) {
printf(ldquo;error creating thread.");
abort();
}
for ( i=0; i<20; i++) {
myglobal=myglobal+1;
printf("o");
fflush(stdout);
sleep(1);
}
if ( pthread_join ( mythread, NULL ) ) {
printf("error joining thread.");
abort();
}
printf("\nmyglobal equals %d\n",myglobal);
exit(0);
}
In main() we first declare a variable called mythread, which has a type of
pthread_t. This is essentially an ID for a thread. Next, the if statement creates
a thread associated with mythread. The call pthread_create() returns
zero on success and a nonzero value on failure. The third argument of pthread_
create() is the name of a function that the new thread will execute when it starts.
When this thread_function() returns, the thread terminates. Meanwhile, the
main program itself defines a thread, so that there are two threads executing. The
pthread_join function enables the main thread to wait until the new thread
completes.
a. What does this program accomplish?
b. Here is the output from the executed program:
$ ./thread2
..o.o.o.o.oo.o.o.o.o.o.o.o.o.o..o.o.o.o.o
myglobal equals 21
Is this the output you would expect? If not, what has gone wrong?
An uninterruptable process is a process which happens to be in a system call (kernel function) that cannot be interrupted by a signal.

To understand what that means, you need to understand the concept of an interruptable system call. The classic example is read(). This is a system call that can take a long time (seconds) since it can potentially involve spinning up a hard drive, or moving heads. During most of this time, the process will be sleeping, blocking on the hardware.

While the process is sleeping in the system call, it can receive a unix asynchronous signal (say, SIGTERM), then the following happens:

The system calls exits prematurely, and is set up to return -EAGAIN to userspace.
The signal handler is executed.
If the process is still running, it gets the return value from the system call, and if it is written correctly it will make the same call again.
The crux of the issue is that (for some reason I do not really understand), the execution needs to get out of the system call for the userspace signal handler to run.

On the other hand, some system calls are not allowed to be interrupted in this way. If the system calls stalls for some reason, the process can indefinitely remains in this unkillable state.

LWN ran a nice article that touched this topic in July.

To answer the original question:

How to prevent this from happening: figure out which driver is causing you trouble, and either stop using, or become a kernel hacker and fix it.

How to kill an uninterruptible process without rebooting: somehow make the system call terminate. Frequently the most effective manner to do this without hitting the power switch is to pull the power chord. You can also become a kernel hacker and make the driver use TASK_KILLABLE, as explained in the LWN article.
Processes and threads provide a powerful structuring tool for implementing programs
that would be much more complex as simple sequential programs. An earlier construct
that is instructive to examine is the coroutine. The purpose of this problem is to
introduce coroutines and compare them to processes. Consider this simple problem
from [CONW63]:
Read 80-column cards and print them on 125-character lines, with the following
changes. After every card image an extra blank is inserted, and every adjacent
pair of asterisks (**) on a card is replaced by the character.
a. Develop a solution to this problem as an ordinary sequential program. You
will find that the program is tricky to write. The interactions among the various
elements of the program are uneven because of the conversion from a length of
80 to 125; furthermore, the length of the card image, after conversion, will vary
depending on the number of double asterisk occurrences. One way to improve
clarity, and to minimize the potential for bugs, is to write the application as three
separate procedures. The first procedure reads in card images, pads each image
with a blank, and writes a stream of characters to a temporary file. After all of
the cards have been read, the second procedure reads the temporary file, does the
character substitution, and writes out a second temporary file. The third procedure
reads the stream of characters from the second temporary file and prints lines of
125 characters each.
b. The sequential solution is unattractive because of the overhead of I/O and temporary
files. Conway proposed a new form of program structure, the coroutine, that
allows the application to be written as three programs connected by one-character
buffers ( Figure 5.25 ). In a traditional procedure , there is a master/slave relationship
between the called and calling procedure. The calling procedure may execute
a call from any point in the procedure; the called procedure is begun at its entry
point and returns to the calling procedure at the point of call. The coroutine exhibits
a more symmetric relationship. As each call is made, execution takes up from
the last active point in the called procedure. Because there is no sense in which
a calling procedure is "higher" than the called, there is no return. Rather, any coroutine
can pass control to any other coroutine with a resume command. The first
time a coroutine is invoked, it is "resumed" at its entry point. Subsequently, the coroutine
is reactivated at the point of its own last resume command. Note that only
one coroutine in a program can be in execution at one time and that the transition
points are explicitly defined in the code, so this is not an example of concurrent
processing. Explain the operation of the program in Figure 5.25 .
c. The program does not address the termination condition. Assume that the I/O
routine READCARD returns the value true if it has placed an 80-character image
in inbuf ; otherwise it returns false. Modify the program to include this contingency.
Note that the last printed line may therefore contain less than 125 characters.
d. Rewrite the solution as a set of three processes using semaphores.
Consider a sharable resource with the following characteristics: (1) As long as there
are fewer than three processes using the resource, new processes can start using it
right away. (2) Once there are three process using the resource, all three must leave
before any new processes can begin using it. We realize that counters are needed to
keep track of how many processes are waiting and active, and that these counters are
themselves shared resources that must be protected with mutual exclusion. So we
might create the following solution:
1 semaphore mutex = 1, block = 0; / share variables: semaphores, /
2 int active = 0, waiting = 0; / counters, and /
3 boolean must_wait = false; / state information /
4
5 semWait(mutex); / Enter the mutual exclusion /
6 if(must_wait) { / If there are (or were) 3, then /
7 ++waiting; / we must wait, but we must leave /
8 semSignal(mutex); / the mutual exclusion first /
9 semWait(block); / Wait for all current users to depart /
10 SemWait(mutex); / Reenter the mutual exclusion /
11 --waiting; / and update the waiting count /
12 }
13 ++active; / Update active count, and remember /
14 must_wait = active == 3; / if the count reached 3 /
15 semSignal(mutex); / Leave the mutual exclusion /
16
17 / critical section /
18
19 semWait(mutex); / Enter mutual exclusion /
20 --active; / and update the active count /
21 if(active == 0) { / Last one to leave? /
22 int n;
23 if (waiting < 3) n = waiting;
24 else n = 3; / If so, unblock up to 3 /
25 while( n > 0 ) { / waiting processes /
26 semSignal(block);
27 --n;
28 }
29 must_wait = false; / All active processes have left /
30 }
31 semSignal(mutex); / Leave the mutual exclusion /
The solution appears to do everything right: All accesses to the shared variables are
protected by mutual exclusion, processes do not block themselves while in the mutual
exclusion, new processes are prevented from using the resource if there are (or
were) three active users, and the last process to depart unblocks up to three waiting
processes.
a. The program is nevertheless incorrect. Explain why.
b. Suppose we change the if in line 6 to a while. Does this solve any problem in the
program? Do any difficulties remain?
Now consider this correct solution to the preceding problem:
1 semaphore mutex = 1, block = 0; / share variables: semaphores, /
2 int active = 0, waiting = 0; / counters, and /
3 boolean must_wait = false; / state information /
4
5 semWait(mutex); / Enter the mutual exclusion /
6 if(must_wait) { / If there are (or were) 3, then /
7 ++waiting; / we must wait, but we must leave /
8 semSignal(mutex); / the mutual exclusion first /
9 semWait(block); / Wait for all current users to depart /
10 } else {
11 ++active; / Update active count, and /
12 must_wait = active == 3; / remember if the count reached 3 /
13 semSignal(mutex); / Leave mutual exclusion /
14 }
15
16 / critical section /
17
18 semWait(mutex); / Enter mutual exclusion /
19 --active; / and update the active count /
20 if(active == 0) { / Last one to leave? /
21 int n;
22 if (waiting < 3) n = waiting;
23 else n = 3; / If so, see how many processes to unblock /
24 waiting -= n; / Deduct this number from waiting count /
25 active = n; / and set active to this number /
26 while( n > 0 ) { / Now unblock the processes /
27 semSignal(block); / one by one /
28 --n;
29 }
30 must_wait = active == 3; / Remember if the count is 3 /
31 }
32 semSignal(mutex); / Leave the mutual exclusion /
a. Explain how this program works and why it is correct.
b. This solution does not completely prevent newly arriving processes from cutting
in line but it does make it less likely. Give an example of cutting in line.
c. This program is an example of a general design pattern that is a uniform way to
implement solutions to many concurrency problems using semaphores. It has been
referred to as the I'll Do It For You pattern. Describe the pattern.
Now consider another correct solution to the preceding problem:
1 semaphore mutex = 1, block = 0; / share variables: semaphores, /
2 int active = 0, waiting = 0; / counters, and /
3 boolean must_wait = false; / state information /
4
5 semWait(mutex); / Enter the mutual exclusion /
6 if(must_wait) { / If there are (or were) 3, then /
7 ++waiting; / we must wait, but we must leave /
8 semSignal(mutex); / the mutual exclusion first /
9 semWait(block); / Wait for all current users to depart /
10 --waiting; / We've got the mutual exclusion; update count /
11 }
12 ++active; / Update active count, and remember /
13 must_wait = active == 3; / if the count reached 3 /
14 if(waiting > 0 && !must_wait) / If there are others waiting /
15 semSignal(block);; / and we don't yet have 3 active, /
16 / unblock a waiting process /
17 else semSignal(mutex); / otherwise open the mutual exclusion /
18
19 / critical section /
20
21 semWait(mutex); / Enter mutual exclusion /
22 --active; / and update the active count /
23 if(active == 0) / If last one to leave? /
24 must_wait = false; / set up to let new processes enter /
25 if(waiting == 0 && !must_wait) / If there are others waiting /
26 semSignal(block);; / and we don't have 3 active, /
27 / unblock a waiting process /
28 else semSignal(mutex); / otherwise open the mutual exclusion /
a. Explain how this program works and why it is correct.
b. Does this solution differ from the preceding one in terms of the number of processes
that can be unblocked at a time? Explain.
c. This program is an example of a general design pattern that is a uniform way to
implement solutions to many concurrency problems using semaphores. It has been
referred to as the Pass The Baton pattern. Describe the pattern.
The following pseudocode is a correct implementation of the producer/consumer
problem with a bounded buffer:
item[3] buffer; // initially empty
semaphore empty; // initialized to +3
semaphore full; // initialized to 0
binary_semaphore mutex; // initialized to 1
void producer() void consumer()
{ {
... ...
while (true) { while (true) {
item = produce(); c1: wait(full);
p1: wait(empty); / wait(mutex);
/ wait(mutex); c2: item = take();
p2: append(item); \ signal(mutex);
\ signal(mutex); c3: signal(empty);
p3: signal(full); consume(item);
} }
} }
Labels p1, p2, p3 and c1, c2, c3 refer to the lines of code shown above (p2 and c2 each
cover three lines of code). Semaphores empty and full are linear semaphores that can
take unbounded negative and positive values. There are multiple producer processes,
referred to as Pa, Pb, Pc, etc., and multiple consumer processes, referred to as Ca, Cb,
Cc, etc. Each semaphore maintains a FIFO (first-in-first-out) queue of blocked processes.
In the scheduling chart below, each line represents the state of the buffer and
semaphores after the scheduled execution has occurred. To simplify, we assume that
scheduling is such that processes are never interrupted while executing a given portion
of code p1, or p2, ..., or c3. Your task is to complete the following chart.
Scheduled
Step of Execution
full's State and
Queue Buffer
empty's State
and Queue
Initialization full = 0 OOO empty = +3
Ca executes c1 full = -1 (Ca) OOO empty = +3
Cb executes c1 full = -2 (Ca, Cb) OOO empty = +3
Scheduled
Step of Execution
full's State and
Queue Buffer
empty's State
and Queue
Pa executes p1 full = -2 (Ca, Cb) OOO empty = +2
Pa executes p2 full = -2 (Ca, Cb) X OO empty = +2
Pa executes p3 full = -1 (Cb) Ca X OO empty = +2
Ca executes c2 full = -1 (Cb) OOO empty = +2
Ca executes c3 full = -1 (Cb) OOO empty = +3
Pb executes p1 full = empty =
Pa executes p1 full = empty =
Pa executes __ full = empty =
Pb executes __ full = empty =
Pb executes __ full = empty =
Pc executes p1 full = empty =
Cb executes __ full = empty =
Pc executes __ full = empty =
Cb executes __ full = empty =
Pa executes __ full = empty =
Pb executes p1-p3 full = empty =
Pc executes __ full = empty =
Pa executes p1 full = empty =
Pd executes p1 full = empty =
Ca executes c1-c3 full = empty =
Pa executes __ full = empty =
Cc executes c1-c2 full = empty =
Pa executes __ full = empty =
Cc executes c3 full = empty =
Pd executes p2-p3 full = empty =