3.16k likes | 3.28k Views
Chapter 6, Process Synchronization. Cooperating processes can affect each other This may result from message passing It may result from shared memory space The general case involves concurrent access to shared resources. 6.1 Background.
E N D
Cooperating processes can affect each other • This may result from message passing • It may result from shared memory space • The general case involves concurrent access to shared resources
6.1 Background • This section illustrates how uncontrolled access to a shared resource can result in inconsistent state • The following overheads show: • How producer and consumer threads may both change the value of a variable, count • How the single increment or decrement of count is not atomic in machine code • How the interleaving of machine instructions can give an incorrect result
High level Producer Code • while(count == BUFFER_SIZE) • ; // no-op • ++count; • buffer[in] = item; • in = (in + 1) % BUFFER_SIZE
High Level Consumer Code • while(count == 0) • ; // no-op • --count; • Item = buffer[out]; • out = (out + 1) % BUFFER_SIZE
Machine Code for Incrementing count • register1 = count; • register1 = register1 + 1; • count = register1;
Machine Code for Decrementing count • register2 = count; • register2 = register2 - 1; • count = register2;
An Interleaving of Machine Instructions Which Leads to a Lost Increment • Let the initial value of count be 5 • S0: Producer executes • register1 = count ( register1 = 5) • S1: Producer executes • register1 = register1 + 1 ( register1 = 6) • Context switch • S2: Consumer executes • register2 = count ( register2 = 5) • S3: Consumer executes • register2 = register2 – 1 ( register2 = 4) • Context switch • S4: producer executes • count = register1( count = 6) • Context switch • S5: consumer executes • count = register2( final value of count = 4)
The point is that you started with a value of 5. • Then two processes ran concurrently. • One attempted increment the count. • The other attempted to decrement the count. • 5 + 1 – 1 should = 5 • However, due to synchronization problems the final value of count was 4, not 5.
Term: • Race Condition. • Definition: • This is the general O/S term for any situation where the order of execution of various actions affects the outcome (i.e., can result in an inconsistent state)
The derivation of the term “race” condition: • Execution is a “race”. • In interleaved actions, whichever sequence finishes first determines the final outcome. • Note in the concrete example that the one that finished first “lost”.
Process synchronization refers to the tools that can be used with cooperating processes to make sure that that during concurrent execution they access shared resources in such a way that a consistent state results • In other words, it’s a way of enforcing a desired interleaving of actions, or preventing an undesired interleaving of actions.
Yet another way to think about is that process synchronization reduces concurrency somewhat, because certain sequences that might otherwise happen are not allowed, and at least a partial sequential ordering of actions may be required
6.2 Critical Section Problem • Term: • Critical section. • Definition: • A segment of code where resources common to a set of threads are being manipulated • Note that the definition is given in terms of threads because it will be possible to concretely illustrate it using threaded Java code
Alternative definition: • A segment of code where access is regulated. • Only one thread at a time is allowed to execute in the critical section • This makes it possible to avoid conflicting actions which result in inconsistent state
Critical section definition using processes: • Let there be n processes, P0, …, Pn-1, that share access to common variables, data structures, or resources • Any segments of code where they access shared resources are critical sections • No two processes can be executing in their critical section at the same time
The critical section problem is to design a protocol that allows processes to cooperate • In other words, it allows them to run concurrently, but it prevents breaking their critical sections into parts and interleaving them • Once again recall that this will ultimately be illustrated using threads • In that situation, no two threads may be executing in the same critical section of code at the same time
For the purposes of thinking about the problem, the general structure of a process with a critical section can be diagrammed in this way: • while(true) • { • entry section // the synchronization entrance protocol is implemented here • critical section // this section is protected • exit section // the synchronization exit protocol is implemented here • remainder section // this section is not protected • }
Note the terminology: • Entry section • Critical section • Exit section • Remainder section • These terms for referring to the parts of a concurrent process will be used in the discussions which follow
A correct solution to the critical section problem has to meet these three conditions: • Mutual exclusion • Progress • Bounded waiting • In other words, an implementation of a synchronization protocol has to have these three characteristics in order to be correct.
Mutual exclusion • Definition of mutual exclusion: • If process Pi is executing in its critical section, no other process can be executing in its critical section • Mutual exclusion is the heart of concurrency control. • However, concurrency control is not correct if the protocol “locks up” and the program can’t produce results. • That’s what the following requirements are about.
Progress • Definition of progress: • If no process is in its critical section and some processes wish to enter, only those not executing in their remainder sections can participate in the decision
Progress explained • For the sake of discussion, let all processes be structured as infinite loops • Then a process can either be in its critical section, in its remainder section • In a system where mutual exclusion has been implemented, it may also be at the top of the loop, waiting for the entry section to allow it into the critical section
The premise of the progress condition is that no process is in its critical section • Some processes may be in their remainder sections • Others may be waiting to enter the critical section
Progress states that a process that is happily running in its remainder section has no part in the decision of which process to allow into the critical section. • A process in the remainder section can’t stop another process from entering the critical section. • A process in the remainder section also cannot delay the decision. • The decision on which enters can only take into account those processes that are currently waiting to get in
Bounded waiting • Definition of bounded waiting: • There exists a bound, or limit, on the number of times that other processes are allowed to enter their critical sections after a given process has made a request to enter its critical section and when that request is granted
Bounded waiting explained • Whatever algorithm or protocol is implemented, it cannot allow starvation • Granting access to a critical section is reminiscent of scheduling. • Eventually, everybody has to get a chance
For the purposes of this discussion, it is assumed that each process is executing at non-zero speed, although they may differ in their speeds • Bounded waiting is expressed in terms of “a number of times”. • No concrete time limit can be given, but the result is that allowing a thread into its critical section can’t be postponed indefinitely
More about the critical section problem • The critical section problem is unavoidable in operating systems • This idea already came up in chapter 5 in the discussion of preemption and interrupt handling
You might try to avoid the critical section problem by disallowing cooperation among user processes (although this diminishes the usefulness of multi-programming) • Such a solution would be very limiting for application code. • It doesn’t work for system code.
By definition, multiple system processes contend for shared resources like scheduling queues, I/O queues, lists of memory allocation, lists of processes, etc. • The code that manipulates these resources has to be in a critical section • Stated briefly: There has to be mutual exclusion between different processes that access the resources
The critical section problem in the O/S, elaborated • You might try to get rid of the critical section problem by making the kernel monolithic (although this goes against the grain of layering/modular design) • Even so, if the architecture is based on interrupts, whether the O/S is modular or not, one activation of the O/S can be interrupted and set aside, while another activation is started as a result of the interrupt.
The idea behind “activations” here, can be illustrated with interrupt handling code specifically. • One activation of the O/S may be doing one thing. • When the interrupt arrives a second activation occurs, which will run a different part of the O/S—namely an interrupt handler
There is only one ready queue, for example, in the O/S. • Any activation of the O/S has the potential to access and modify this shared resource • This would be inescapably true if the O/S were monolithic, but it’s no less true that different modules would affect shared resources
The situation can be framed in this way: You think of the O/S as “owning” and “managing” the processes, whether system or user processes • It turns out that in a sense, the processes own the O/S. • The O/S may create the processes, but the processes can then be viewed as having shared access to common O/S resources
This thought can be pursued one level deeper. • Even if the processes are user processes and don’t access O/S resources directly, user requests for service and the granting of those requests by the O/S causes changes to the O/S’s data structures
This is the micro-level view of the idea expressed at the beginning of the course, that the O/S is the quintessential service program • Everything that it does can ultimately be traced to some application request • Applications don’t own the system resources, but they are responsible for O/S behavior which requires critical section protection
Another way of thinking about this • As soon as multiple processes are allowed, whether an O/S is a microkernel or not, it is reasonable to implement some of the functionality in different processes • At that point, whether the O/S supported multi-programming or not, the O/S itself already has concurrency issues • Once you take the step of allowing >1 concurrent process, whether user or system processes, concurrency, or the critical section problem arises
What all this means to O/S code • O/S code can’t allow race conditions to arise • The O/S can’t be allowed to enter an inconsistent state • O/S code has to be written so that access to shared resources is done in critical sections • No two O/S processes can be in a critical section at the same time
The critical section problem has to be solved in order to implement a correct O/S. • If the critical section problem can be resolved in O/S code, then the solution tools can also be used when considering user processes or application code which exhibit the characteristics of concurrency and access to shared resources
Dealing with critical sections in the O/S • Keep in mind: • A critical section is a sequence of instructions that has to be run atomically without interleaving executions of >1 process • Pre-emptive scheduling a new process can be scheduled before the currently running one finishes
The question of critical sections in the O/S intersects with the topic of scheduling • The question of scheduling leads to two possible approaches to dealing with concurrency in the O/S: • A non-preemptive kernel • A preemptive kernel
In a non-preemptive kernel, any kernel mode process will run until: • It exits • It blocks due to making an I/O request • It voluntarily yields the CPU • Under this scenario, the process will run through any of its critical sections without interruption by another process
A preemptive kernel may be desirable. • A preemptive kernel • Will be more responsive for interactive time-sharing • Will support (soft) real-time processing
Writing a pre-emptive kernel means dealing with concurrency in the kernel code • In general, this is not easy • In some cases (like SMP) it is virtually impossible (how do you control two threads running concurrently on two different CPU’s?)
Windows XP and before were non-preemptive • Traditional Unix was non-preemptive. • Newer versions of Unix and Linux are preemptive
Returning to a discussion of what the critical section problem is, and how it can be solved, in general • Explaining the requirements for a successful solution to the critical section problem • Mutual exclusion • Progress • Bounded waiting
At this point, let it be assumed that the mutual exclusion requirement is clear • Progress and bounded waiting can be made clearer by considering possible solutions to the critical section problem that may not satisfy these requirements
Note that the latest edition of the textbook has taken out some of this material • I have kept it because I think it is useful • The basic ideas come from earlier editions of the book • The explanations of the ideas are unique to this set of overheads
Scenario 1 • Suppose your protocol is based on the idea that processes “take turns” • Consider two processes, P0 and P1 • Let “whose turn” be represented by an integer variable turn which takes on the values 0 or 1