Survey
* Your assessment is very important for improving the workof artificial intelligence, which forms the content of this project
* Your assessment is very important for improving the workof artificial intelligence, which forms the content of this project
Operating System Principles AUA CIS215 Albert Minasyan Handout 7 Process Synchronization Content of this handout: 7.1. Cooperating Processes, Interprocess Communication Producer-Consumer problem. Synchronization bounded, unbounded buffer problems. 7.2. Shared Memory Systems 7.3. Message passing systems. Naming o Direct (symmetric, asymmetric) communication, Indirect communication Synchronization o Rendezvous Buffering Process Synchronization Semaphores o Binary Semaphores. Bounded Buffer Problem, Counting Semaphores. Deadlock Silberschatz,6th ed, Chapters 4,7,8., Silberschatz,9th ed, Chapter 5, 7. 7.1. Cooperating Processes. Interprocess Communication. The concurrent processes executing in the operating system may be either independent processes or cooperating processes. Processes running in the OS Independent Processes Cooperating Processes Cannot affect or be affected by the other processes. Doesn’t share any data with any other process. Affects or is affected by the other processes Shares data with other processes. A process is independent if it cannot affect or be affected by the other processes executing in the system. Any process that does not share any data (temporary or persistent) with any other process is independent. On the other hand, a process is cooperating if it can affect or be affected by the other processes executing in the system. Clearly, any process that shares data with other processes is a cooperating process. Process Cooperation Purposes Information sharing (Share files) Computation speedup (create subtasks to run simultaneously) Modularity (system function divide into separate processes or threads) Convenience (Run several tasks of the same user in the same environment) Operating System Principles AUA CIS215 Albert Minasyan There are several reasons for providing an environment that allows process cooperation: Information sharing: Since several users may be interested in the same piece of information (for instance, a shared file), we must provide an environment to allow concurrent access to these types of resources. Computation speedup: If we want a particular task to run faster, we must break it into subtasks, 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). Modularity: We may want to construct the system in a modular fashion, dividing the system functions into separate processes or threads. Convenience: Even an individual user may work on many tasks at the same time. For instance, a user may be editing, listening to music, and compiling in parallel. Concurrent execution of cooperating processes requires mechanisms that allow processes to communicate with one another and to synchronize their actions. Concurrent processes Mechanisms to Communicate Mechanisms to Synchronize Shared Resource Cooperating processes require an interprocess communication (IPC) mechanism that will allow them to exchange data and information to communicate and to synchronize. There are two fundamental models of interprocess communication: (1) shared memory and (2) message passing. In the shared-memory model, a region of memory that is shared by cooperating processes is established. Processes can then exchange information by reading and writing data to the shared region. In the message passing model, communication takes place by means of messages exchanged between the cooperating processes. The two communications models are contrasted in Figure below. Operating System Principles AUA CIS215 Albert Minasyan Figure: Communications models. (a) Message passing. (b) Shared memory. Both of the models just discussed are common in operating systems, and many systems implement both. Message passing is useful for exchanging smaller amounts of data, because no conflicts need be avoided. Message passing is also easier to implement than is shared memory for inter computer communication. Shared memory allows maximum speed and convenience of communication. Shared memory is faster than message passing, as message passing systems are typically implemented using system calls and thus require the more time-consuming task of kernel intervention. In contrast, in shared memory systems, system calls are required only to establish shared-memory regions. Once shared memory is established, all accesses are treated as routine memory accesses, and no assistance from the kernel is required. Message Passing Shared Memory Slow speed (kernel intervention) Complex to implement for inter process sharing.(data is sent by message). Easier to implement for Inter Process and Inter Computer communication / synchronization Maximum speed (memory operations) Convenience for sharing between local processes (simple write/read) Difficult to implement for Inter Computer sharing or communication (needs polling). Message Passing vs Shared Memory Shared memory enjoys the desirable feature that all communications are done using implicit loads and stores to a global address space. Another fundamental feature of shared memory is that synchronization and communication are distinct. Special synchronization operations (mechanisms), in addition to the loads and stores operations, need to be employed in order to detect when data have been produced and/or consumed. Operating System Principles AUA CIS215 Albert Minasyan On the other hand, message passing employs an explicit communication model. Explicit messages are exchanged among processors. Synchronization and communication are unified in message passing. The generation of remote, asynchronous events is an integral part of the message passing communication model. It is important, however, to indicate that shared memory and message passing communication models are universal; that is, it is possible to employ one to simulate the other. o However, it is observed that it is easier to simulate shared memory using message passing than the converse. o This is basically because of the asynchronous event semantics of message passing as compared to the polling semantics of the shared memory. Producer – Consumer paradigm To illustrate the concept of cooperating processes, let us consider the producer-consumer problem, which is a common paradigm for cooperating 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. A compiler may produce assembly code, which is consumed by an assembler. The assembler, in turn, may produce object modules, which are consumed by the loader. The producer-consumer problem also provides a useful metaphor for the client-server paradigm. We generally think of a server as a producer and a client as a consumer. For example, a Web server produces (that is, provides) HTML files and images, which are consumed (that is, read) by the client Web browser requesting the resource. Producer – Consumer common paradigm for cooperating processes To run concurrently – we need a buffer Producer Printing program Consumer Produce Information Consume Information Printer driver Buffer To allow producer and consumer processes to run concurrently, we must have available a buffer of items that can be filled by the producer and emptied by the consumer. This buffer will reside in a region of memory that is shared by the producer and consumer processes. A producer can produce one item while the consumer is consuming another item. The producer and consumer must be synchronized, so that the consumer 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. Operating System Principles AUA CIS215 Albert Minasyan Two types of buffers can be used. The unbounded-buffer producer-consumer problem 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. Synchronization: Unbounded buffer Producer – Consumer example Producer Never waits Consumer Produce Information Consume Information Waits only if the buffer is empty Buffer No limit on the size The bounded-buffer 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 Bounded Buffer Problem has to do with two processes, the producer and the consumer, who share a common, fixed-size buffer. The producer's job is to generate a piece of data, put it into the buffer and start again. At the same time the consumer is consuming the data (i.e. removing it from the buffer) one piece at a time. The problem is to make sure that the producer won't try to add data into the buffer if it's full and that the consumer won't try to remove data from an empty buffer. Synchronization: Bounded buffer Producer – Consumer example Producer Waits if the buffer is full Consumer Produce Information Consume Information Waits if the buffer is empty Buffer Limited size The buffer may either be provided by the operating system through the use of a message passing system, or by explicitly coded by the application programmer with the use of shared memory. Buffer is Provided: Either by the OS through the use of Message Passing system Or by explicitly coded application with the shared memory Operating System Principles AUA CIS215 Albert Minasyan 7.2. Shared Memory Systems Interprocess communication using shared memory requires communicating processes to establish a region of shared memory. Typically, a shared-memory region resides in the address space of the process creating the shared memory segment. Other processes that wish to communicate using this shared memory segment must attach it to their address space. Recall that, normally, the operating system tries to prevent one process from accessing another process's memory. Shared memory requires that two or more processes agree to remove this restriction. They can then exchange information by reading and writing data in the shared areas. The form of the data and the location are determined by these processes and are not under the operating system's control. The processes are also responsible for ensuring that they are not writing to the same location simultaneously. Typically for using the shared memory the processes should: • • • • • • • Establish (allocate) a region of shared memory Memory resides in the address space of the process creating the shared memory segment Other processes that wish to communicate using this shared memory segment must attach it to their address space Shared memory requires that two or more processes agree to remove the address space protection restriction They can then exchange information by reading and writing data in the shared areas After finishing the transfer the memory should be detached. Deallocate the shared memory. One solution to the producer-consumer problem uses shared memory. Let us illustrate a shared-memory solution to the bounded-buffer problem. The producer and consumer processes share the following variables: #define BUFFER-SIZE 5 typedef struct { . . . } item; item buffer [BUFFER-SIZE] ; int in = 0; int out = 0; in out pointers: in and out. in points to the next free position in the buffer; out points to the first full position in the buffer. The buffer is empty when in == out; The buffer is full when ((in + 1) % BUFFER-SIZE) == out. out in out out in in in out Operating System Principles AUA CIS215 Albert Minasyan The code for the producer and consumer processes follows. The producer process has a local variable nextproduced in which the new item to be produced is stored: while (1) { /* produce an item in nextproduced */ while ( ( (in + 1) % BUFFER-SIZE) == out) ; /* do nothing */ buffer [in] = nextproduced; in = (in + 1) % BUFFER-SIZE; } The consumer process has a local variable nextconsumed in which the item to be consumed is stored: while (1) { while (in == out) ; // do nothing nextconsumed = buffer [out] ; out = (out + 1) % BUFFER-SIZE; /* consume the item in nextconsumed */ } This scheme allows at most BUFFER-SIZE - 1 items in the buffer at the same time. We leave it as an exercise for you to provide a solution where BUFFERSIZE items can be in the buffer at the same time. Operating System Principles AUA CIS215 Albert Minasyan 7.3. Message Passing System When the cooperating processes communicate in a shared-memory environment the scheme requires that these processes share a common buffer pool, and that the code for implementing the buffer be written explicitly by the application programmer. Another way to achieve the same effect is for the operating system to provide the means for cooperating processes to communicate with each other via a message-passing facility. Message passing provides a mechanism to allow processes to communicate and to synchronize their actions without sharing the same address space (buffer is provided by OS). Message passing is particularly useful in a distributed environment where the communicating processes may reside on different computers connected with a network. For example, a chat program used on the World Wide Web could be designed so that chat participants communicate with one another by exchanging messages. The function of a message system is to allow processes to communicate with one another without the need to resort to shared data in the same address space. In the scheme when the services are provided as ordinary user processes the services operate outside of the kernel. Communication among the user processes is accomplished through the passing of messages. A Message passing facility provides at least the two operations: send(message) and 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 messages 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: If processes P and Q want to communicate a communication link (logical) must established between them they must send messages to and receive messages from each other. Operating System Principles AUA CIS215 Albert Minasyan 7.3.1. Naming Processes that want to communicate must have a way to refer to each other. They can use either direct or indirect communication. 7.3.1.1. 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: send (P , message) - Send a message to process P. receive (Q , message) - Receive a message from process Q. A communication link in this scheme has the following properties: 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. A link is associated with exactly two processes. 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. Direct Communication Processes must name each other explicitly: send (P, message) – send a message to process P receive(Q, message) – receive a message from process Q Properties of communication link Links are established automatically A link is associated with exactly one pair of communicating processes Between each pair there exists exactly one link The link may be unidirectional, but is usually bi-directional Direct Symmetric Communication Process P Process Q Send to Q message Receive from P message Process R Send to R message Receive from Q message OS Buffer Receive from Q message OS Buffer Send to P message Receive from R message OS Buffer OS Buffer Link QR Link PQ Send to Q message Operating System Principles AUA CIS215 Albert Minasyan 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, the send and receive primitives are defined as follows: send (P , message) - Send a message to process P. receive (id, message) -Receive a message from any process; the variable id is set to the name of the process with which the communication has taken place. Direct Asymmetric Communication Process P Process Q Process R Receive from X message X=R or X=Q Is set by OS Send to Q message OS Buffer Send to Q message OS Buffer Disadvantage: Direct communication is tightly related with the names of processes and changing the names cause changes in programs. 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. Operating System Principles AUA CIS215 Albert Minasyan 7.3.1.2. 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. The operating system must provide a mechanism that allows a process to do the following: Create a new mailbox. Send and receive messages through the mailbox. Delete a mailbox. Two processes can communicate only if they share a mailbox. The send and receive primitives are defined as follows: send (A, message) -Send a message to mailbox A. receive (A, message) -Receive a message from mailbox A. In this scheme, a communication link has the following properties: Indirect Communication Processes can communicate only if they share a mailbox or port (with unique identification): send (A, message) – send a message to mailbox A receive (A, message) – receive a message from mailbox A Properties of communication link A link is established between a pair of processes only if both members of the pair have a shared mailbox. A link may be associated with more than two processes. A number of different links may exist between each pair of communicating processes, with each link corresponding to one mailbox. The link may be unidirectional or bi-directional Indirect Communication Process P Process Q Receive from PQ1 message Process R Send to PQ1 message PQ mailbox 1 Send to PQR1 message Receive from PQR1 message PQ mailbox 2 Send to PQ2 message Send to PQR1 message User sends message Receive from PQ2 message User sends message PQR mailbox 1 Owner receives message Operating System Principles AUA CIS215 Albert Minasyan Mailbox owner could be the process (mailbox attached to it). Mailbox could be created deleted with the process. Ownership could be transferred to the other process. Mailbox owner could be OS. The process will request creation or deletion of mailbox Send and receive messages through the 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 execute a receive from A. Which process will receive the message sent by P1 ? The answer depends on the scheme that we choose: Allow a link to be associated with at most two processes. Allow at most one process at a time to execute a receive operation. 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 either by a process or by the operating system. If the mailbox is owned by a process (that is, the 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: Create a new mailbox. Send and receive messages through the mailbox. 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 this mailbox. However, the ownership and receive privilege may be passed to other processes through appropriate system calls. Of course, this provision could result in multiple receivers for each mailbox. Operating System Principles AUA CIS215 Albert Minasyan 7.3.2. Message 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 either blocking or nonblocking-also known as synchronous and asynchronous. Blocking send: The sending process is blocked until the message is received by the receiving process or by the mailbox. Nonblocking send: The sending process sends the message and resumes operation. Blocking receive: The receiver blocks until a message is available. Nonblocking 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. Process 1 Process 2 Non blocking receive (0) To the mailbox or to the process Non blocking receive (message) Non blocking send Blocking receive Waiting message Blocking send Waiting until message reaches recipient Non blocking send Rendezvous answering 7.3.3. Message 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: 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. 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 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. 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. Operating System Principles AUA CIS215 Albert Minasyan 7.3.4. Process Synchronization Synchronization involves the orderly sharing of system resources by processes. To illustrate, consider the following intersection diagram to the right. We can think of this intersection as a system resource that is shared by two processes: the car process and the train process. If only one process is active, then no resource conflict exists. But what happens when both processes are active and they both arrive at the intersection simultaneously? In this case, the shared resource becomes a problem. They cannot both use the resource at the same time or a collision will occur. Similarly, processes sharing resources on a computer must be properly managed in order to avoid "collisions." The illustration of this concept with the example of a shared printer: "Consider a machine with a single printer running a time-sharing operation system. If a process needs to print its results, it must request that the operating system give it access to the printer's device driver. At this point, the operating system must decide whether to grant this request, depending upon whether the printer is already being used by another process. If it is not, the operating system should grant the request and allow the process to continue; otherwise, the operating system should deny the request and perhaps classify the process as a waiting process until the printer becomes available. Indeed, if two processes were given simultaneous access to the machine's printer, the results would be worthless to both." Incorrect Value Unsynchronized Threads Sharing Memory Operating System Principles AUA CIS215 Albert Minasyan The figure above shows what can happen when two unsynchronized threads share a resource such as a memory location. Both threads increment variable N, but, because of the particular sequence in which the threads might execute, the final value of N is 5, whereas the correct value is 6. Notice that the particular result shown here is neither repeatable nor predictable; a different thread execution sequence could yield the correct results. Execution on an SMP system can aggravate this problem. Now that the problem of synchronization is properly stated, consider the following related definitions Critical Resource: a resource shared with constraints on its use (e.g., memory, files, printers, etc.) Critical Section: code that accesses a critical resource Mutual Exclusion: at most one process may be executing a Critical Section with respect to a particular critical resource simultaneously Using CRITICAL_SECTIONs is simple, and one common use to allow threads to access global shared variables or resources. Critical Resource Dear OS here the critical section starts. Please run it with MUTEX. Dear OS here the critical section starts. Please run it with MUTEX. Dear OS here the critical section ends. Please run the other critical sections if they blocked. Correct Value Synchronized Threads Sharing Memory Dear OS here the critical section ends. Please run the other critical sections if they blocked. Let’s return to the printer example. In the above example the printer is the critical resource. Let's suppose that the processes which are sharing this resource are called process A and process B. The critical sections of process A and process B are the sections of the code which issue the print command. Operating System Principles AUA CIS215 Albert Minasyan 7.3.5. Semaphores In order to insure that both processes do not attempt to use the printer at the same time, they must be granted mutually exclusive access to the printer driver. We can illustrate the idea of mutual exclusion with our railroad intersection by adding a semaphore to the picture. Semaphores are used in software systems in much the same way as they are in railway systems. Corresponding to the section of track that can contain only one train at a time is a sequence of instructions that can be executed by only one process at a time. Such a sequence of instructions is called a critical [section]. One way to implement semaphores in computers is to use a flag (i.e., a bit of memory) that can be tested and set to either 0 or 1 in a single machine operation. Because both the test and set actions occur in the same machine instruction, this operation is indivisible and cannot be interrupted by another process which is running on the machine. By placing test-and-set operations around the critical section of a program, programmers can guarantee mutually exclusive access to the critical resource. The mutual exclusion of critical sections ensures that the Test&Set section is executed atomically that is, as one uninterruptible unit. Operating System Principles AUA CIS215 Albert Minasyan If the critical section is too long then this approach with Atomic Test & Set for Mutex is an active waiting thread which consumes the CPU cycles. When the thread waits and while is waiting consumes the CPU cycles then it is called Busy Waiting and this is a disadvantage of this approach. Calls to either acquire() or release() must be performed atomically. Thus, mutex locks are often implemented using one of the hardware mechanisms The main disadvantage of the implementation given here is that it requires busy waiting. While a process is in its critical section, any other process that tries to enter its critical section must loop continuously in the call to acquire(). In fact, this type of mutex lock is also called a spinlock because the process “spins” while waiting for the lock to become available. This continual looping is clearly a problem in a real multiprogramming system, where a single CPU is shared among many processes. Busy waiting wastes CPU cycles that some other process might be able to use productively. Spinlocks do have an advantage, however, in that no context switch is required when a process must wait on a lock, and a context switch may take considerable time. Thus, when locks are expected to be held for short times, spinlocks are useful. They are often employed on multiprocessor systems where one thread can “spin” on one processor while another thread performs its critical section on another processor. Operating System Principles AUA CIS215 Albert Minasyan Let’s represent the above flowcharts by functions to use them in more comfortable way and also dedicate 2 different threads for a producer and a consumer. Semaphores suggest to sleep the process in waiting queue when it waits for the lock acquiring. In this case there is need in more variables than one Mutex but the main problem with the busy waiting is resolved. Mutex locks, as we mentioned earlier, are generally considered the simplest of synchronization tools. In this section, we examine a more robust tool that can behave similarly to a mutex lock but can also provide more sophisticated ways for processes to synchronize their activities. A semaphore S is an integer variable that, apart from initialization, is accessed only through two standard atomic operations: wait() and signal(). The wait() operation was originally termed P (from the Dutch proberen, “to test”); signal() was originally called V (from verhogen, “to increment”). All modifications to the integer value of the semaphore in the wait() and signal() operations must be executed indivisibly. That is, when one process modifies the semaphore value, no other process can simultaneously modify that same semaphore value. In addition, in the case of wait(S), the testing of the integer value of S (S ≤ 0), as well as its possible modification (S--), must be executed without interruption. Semaphore Usage Operating systems often distinguish between counting and binary semaphores. The value of a counting semaphore can range over an unrestricted domain. The value of a binary semaphore can range only between 0 and 1. Thus, binary semaphores behave similarly to mutex locks. In fact, on systems that do not provide mutex locks, binary semaphores can be used instead for providing mutual exclusion. Counting semaphores can be used to control access to a given resource consisting of a finite number of instances. The semaphore is initialized to the number of resources available. Each process that wishes to use a resource performs a wait() operation on the semaphore (thereby decrementing the count). When a process releases a resource, it performs a signal() operation (incrementing the count). Operating System Principles AUA CIS215 Albert Minasyan When the count for the semaphore goes to 0, all resources are being used. After that, processes that wish to use a resource will block until the count becomes greater than 0. We can also use semaphores to solve various synchronization problems. Mutex Semaphore usage for producer and consumer: Producer(item) { Lock_Acquire(); Enqueue(item); Lock_Release(); } Consumer() { Lock_Acquire(); item = Dequeue(); Lock_Release(); return item; } 7.3.5.1. Mutex or Binary Semaphore Example The pictures below illustrate three processes sharing one critical resource. In order to access the critical section of their code, the processes must wait until the semaphore Mutex (Mutual exclusion) is one. When this happens, the process uses the test-and-set operation to change the value of Mutex to zero and then it executes its critical section. Upon exiting the critical section, the process set the value of Mutex back to one to allow other processes to access the critical resource. The critical section is represented by a light color. http://courses.cs.vt.edu/~csonline/OS/Lessons/ Operating System Principles AUA CIS215 Albert Minasyan Another way to implement a semaphore is to use a count rather than just two values. Such semaphores are called counting semaphores in contrast to the binary semaphore presented above. Counting semaphores are used to solve synchronization problems such as the Bounded Buffer problem. 7.3.5.2. Bounded Buffer Problem. Counting Semaphore Example Suppose a system incorporates two processes, one of which produces information (the producer process) and another process that uses the information (the consumer process). The two processes communicate by having the producer obtain an empty buffer from an empty buffer pool, fill it with information, and place it in a pool of full buffers. The consumer obtains information by picking up a buffer from the full buffer pool, copying the information out of the buffer, and placing it in the empty buffer pool for recycling. The producer and consumer use a fixed, finite number, N, of buffers to pass an arbitrary amount of information between them. The producer and consumer processes must be synchronized so that the consumer process blocks (i.e., pauses its execution) whenever all the buffers are empty, and the producer process blocks whenever all the buffers are full. To enforce this synchronization, counting semaphores are used to count the number of empty and full buffers. The picture below illustrates the Bounded Buffer problem. In this example, the size of the buffer pool is five (N = 5) and the data being shared are characters. The producer process fills one buffer with a character each time it executes, and the consumer process removes one character from the buffer each time it executes. Dark pointer points the empty buffer where the producer next should fill the buffer. Light pointer shows the full buffer where from the consumer should take the information and empty it. Operating System Principles AUA CIS215 Albert Minasyan 7.3.6. Deadlock Silberschatz 9th ed. Chapter 7. 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 waiting state. Sometimes, a waiting process is never again able to change state, because the resources it has requested are held by other waiting processes. This situation is called a deadlock. In a deadlock, processes never finish executing, and system resources are tied up, preventing other jobs from starting. Although some applications can identify programs that may deadlock, operating systems typically do not provide deadlock-prevention facilities, and it remains the responsibility of programmers to ensure that they design deadlock-free programs. Deadlock problems can only become more common, given current trends, including larger numbers of processes, multithreaded programs, many more resources within a system, and an emphasis on long-lived file and database servers rather than batch systems. Consider the following situation: Suppose that two processes (A and B) are running on the same machine, and both processes require the use of the local printer and tape drive. Process A may have been granted access to the machine's printer but be waiting for the tape drive, while process B has access to the tape drive but is waiting for the printer. Such a condition is known as deadlock. Since both processes hold a resource the other process needs, the processes will wait indefinitely for the resources to be released and neither will finish executing. Thread 1 Thread 2 Got Printer access Holding Printer Waiting Disk access Got Disk access Holding Disk Waiting Printer access In the example given above, the printer and tape drive represent mutually exclusive resources. Since these resources cannot be space-multiplexed, processes using them are granted complete control of the resources until they are finished. Operating System Principles AUA CIS215 Albert Minasyan System Model A system consists of a finite number of resources to be distributed among a number of competing processes. The resources may be partitioned into several types (or classes), each consisting of some number of identical instances. CPU cycles, files, and I/O devices (such as printers and DVD drives) are examples of resource types. If a system has two CPUs, then the resource type CPU has two instances. Similarly, the resource type printer may have five instances. If a process requests an instance of a resource type, the allocation of any instance of the type should satisfy the request. If it does not, then the instances are not identical, and the resource type classes have not been defined properly. For example, a system may have two printers. These two printers may be defined to be in the same resource class if no one cares which printer prints which output. However, if one printer is on the ninth floor and the other is in the basement, then people on the ninth floor may not see both printers as equivalent, and separate resource classes may need to be defined for each printer. 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 to carry out its designated task. Obviously, the number of resources requested may not exceed the total number of resources available in the system. In other words, a process cannot request three printers if the system has only two. Under the normal mode of operation, a process may utilize a resource in only the following sequence: 1. Request. The process requests the resource. If the request cannot be granted immediately (for example, if the resource is being 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. A deadlock situation can arise if the following four conditions hold simultaneously in a system: 1. Mutual exclusion. At least one resource must be held in a nonsharable mode; that is, only one process at a time can use the resource. If another process requests that resource, the requesting process must be delayed until the resource has been released. 2. Hold and wait. 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. Resources cannot be preempted; that is, a resource can be released only voluntarily by the process holding it, after that process has completed its task. 4. Circular wait. A set {P0, P1, ..., Pn} of waiting processes must exist such that P0 is waiting for a resource held by P1, P1 is waiting for a resource held by P2, ..., Pn−1 is waiting for a resource held by Pn, and Pn is waiting for a resource held by P0. We emphasize that all four conditions must hold for a deadlock to occur. The circular-wait condition implies the hold-and-wait condition, so the four conditions are not completely independent. If the operating system allows process A and process B to hold-and-wait, and it does not forcibly remove any resources from these processes, then deadlock is possible. Note, however, that the four conditions do not guarantee deadlock. They are necessary, but not sufficient, for the occurrence of deadlock. Operating System Principles AUA CIS215 Albert Minasyan Resource-Allocation Graph Deadlocks can be described more precisely in terms of a directed graph called a system resourceallocation graph. This graph consists of a set of vertices V and a set of edges E. The set of vertices V is partitioned into two different types of nodes: P = {P1, P2, ..., Pn}, the set consisting of all the active processes in the system, and R = {R1, R2, ..., Rm}, the set consisting of all resource types in the system. A directed edge from process Pi to resource type Rj is denoted by Pi → Rj ; it signifies that process Pi has requested an instance of resource type Rj and is currently waiting for that resource. A directed edge from resource type Rj to process Pi is denoted by Rj → Pi ; it signifies that an instance of resource type Rj has been 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, we represent each process Pi as a circle and each resource type Rj as a rectangle. Since resource type Rj may have more than one instance, we represent each such instance as a dot within the rectangle. Note that a request edge points to only the rectangle Rj , whereas an assignment edge must also designate one of the dots in the rectangle. When process Pi requests an instance of resource type Rj , a request edge is inserted in the resourceallocation 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. As a result, the assignment edge is deleted. The resource-allocation graph shown in Figure 7.1 depicts the following situation. Figure 7.1 Resource-allocation graph. • The sets P, R, and E: ◦ P = {P1, P2, P3} ◦ R = {R1, R2, R3, R4} ◦ E = {P1 → R1, P2 → R3, R1 → P2, R2 → P2, R2 → P1, R3 → P3} Operating System Principles AUA CIS215 Albert Minasyan • Resource instances: ◦ One instance of resource type R1 ◦ Two instances of resource type R2 ◦ One instance of resource type R3 ◦ Three instances of resource type R4 • Process states: ◦ Process P1 is holding an instance of resource type R2 and is waiting for an instance of resource type R1. ◦ Process P2 is holding an instance of R1 and an instance of R2 and is waiting for an instance of R3. ◦ Process P3 is holding an instance of R3. Given the definition of a resource-allocation graph, 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 a deadlock has occurred. If the cycle involves only a set of resource types, each of which has only a single instance, then a deadlock has occurred. Each process involved in 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 a necessary but not a sufficient condition for the existence of deadlock. To illustrate this concept, we return to the resource-allocation graph depicted in Figure 7.1. Suppose that process P3 requests an instance of resource type R2. Since no resource instance is currently available,we add a request edge P3→ R2 to the graph (Figure 7.2). At this point, two minimal cycles exist in the system: P1 → R1 → P2 → R3 → P3 → R2 → P1 P2 → R3 → P3 → R2 → P2 Processes P1, P2, and P3 are deadlocked. Process P2 is waiting for the resource R3, which is held by process P3. Process P3 is waiting for either process P1 or process P2 to release resource R2. In addition, process P1 is waiting for process P2 to release resource R1. Operating System Principles AUA CIS215 Albert Minasyan Figure 7.2 Resource-allocation graph with a deadlock. Now consider the resource-allocation graph in Figure 7.3. In this example, we also have a cycle: P1 → R1 → P3 → R2 → P1 Figure 7.3 Resource-allocation graph with a cycle but no deadlock. However, there is no deadlock. Observe that process P4 may release its instance of resource type R2. That resource can then be allocated to P3, breaking the cycle. In summary, if a resource-allocation graph does not have a cycle, then the system is not in a deadlocked state. If there is a cycle, then the system may or may not be in a deadlocked state. This observation is important when we deal with the deadlock problem. Operating System Principles AUA CIS215 Albert Minasyan Methods for Handling Deadlocks Generally speaking, we can deal with the deadlock problem in one of three ways: • We can use a protocol to prevent or avoid deadlocks, ensuring that the system will never enter a deadlocked state. • We can allow the system to enter a deadlocked state, detect it, and recover. • We can ignore the problem altogether and pretend that deadlocks never occur in the system. One solution to this problem is to allow deadlock to occur, detect it, and then correct the problem. Usually this correction involves forcibly deallocating resources from deadlocked processes. Notice that this solution to deadlock involves removing the third condition, that is, the operation system now allows for preemption of resources whenever deadlock occurs. Another solution to the problem of deadlock is to remove the second condition by requiring processes to request all of their resources at the same time. Yet another solution is to remove the first condition by converting nonshareable resources into shareable ones. Suppose the resource in question is a printer and a variety of processes require its use. Each time a process requests the printer, the operating system grants the request. However, instead of connecting the process to the printer's device driver, the operating system connects it to a device driver that stores the information to be printed on a disk rather than sending it to the printer. Thus each process, thinking it has access to the printer, executes in its normal way. Later, when the printer is available, the operating system can transfer the data from the disk to the printer. In this manner the operating system has made the nonshareable resource appear shareable by creating the illusion of more than one printer. Deadlock example A famous illustration of the problem of deadlock was given by the Edgar Dijkstra in 1968. The problem, known as the Dining Philosophers, is illustrated in the picture below. Five philosophers sit around a circular table. Each philosopher spends his life alternatively thinking and eating. In the center of the table is a large plate of spaghetti. A philosopher needs two forks to eat the spaghetti. Unfortunately the philosophers can only afford five forks. One fork is placed between each pair of philosophers and they agree that each will only use the fork to his immediate right and left. When a philosopher thinks, she does not interact with her colleagues. From time to time, a philosopher gets hungry and tries to pick up the two chopsticks that are closest to her (the chopsticks that are between her and her left and right neighbors). A philosopher may pick up only one chopstick at a time. Obviously, she cannot pick up a chopstick that is already in the hand of a neighbor. When a hungry philosopher has both her chopsticks at the same time, she eats without releasing her chopsticks. When she is finished eating, she puts down both of her chopsticks and starts thinking again. Operating System Principles AUA CIS215 Albert Minasyan It’s possible that each of the philosophers becomes hungry and grabs a single fork. Since no more forks are available and two forks are required for eating, the philosophers all wait for another fork and deadlock occurs. Note that the Dining Philosophers problem meets all three of the conditions presented earlier. Only one philosopher can use a fork at a time so the fork resource is mutually exclusive. Hungry philosophers with only one fork will hold this resource until another fork becomes available, thus resulting in hold-and-wait. And since the philosophers are peaceful and courteous, no one is willing to forcibly remove a fork from his neighbor. Several possible remedies to the deadlock problem are listed next. Allow at most four philosophers to be sitting simultaneously at the table. Allow a philosopher to pick up her chopsticks only if both chopsticks are available (to do this she must pick them up in a critical section). Use an asymmetric solution; that is, an odd philosopher picks up first her left chopstick and then her right chopstick, whereas an even philosopher picks up her right chopstick and then her left chopstick. To avoid the possibility of deadlock even numbered philosophers should pick up the forks in a different order from the rest. That is, left first rather than right first. Notice that this solution differs from the other approaches presented. Rather than attacking one of the necessary conditions for deadlock, the solution imposes a particular resource allocation order which makes it impossible for all five philosophers to be holding a single fork. Finally, any satisfactory solution to the dining-philosophers problem must guard against the possibility that one of the philosophers will starve to death. A deadlock-free solution does not necessarily eliminate the possibility of starvation. Operating System Principles AUA CIS215 Albert Minasyan POSIX Semaphores DESCRIPTION This manual page documents POSIX 1003.1b semaphores, not to be confused with SystemV semaphores as described in ipc(5), semctl(2) and semop(2). #include <semaphore.h> int sem_init(sem_t *sem, int pshared, unsigned int value); - sem_init initializes the semaphore object pointed to by sem. int sem_wait(sem_t * sem); int sem_trywait(sem_t * sem); int sem_post(sem_t * sem); (suspends thread until sem is non 0, then atomically decreases the sem count) - non-blocking variant of sem_wait - atomically increases the count of the semaphore pointed to by sem int sem_getvalue(sem_t * sem, int * sval); - stores in the location pointed to by sval the current count of the semaphore sem. int sem_destroy(sem_t * sem); - destroys a semaphore object Operating System Principles AUA CIS215 Albert Minasyan WinAPI Semaphores case WM_CREATE: hSema= CreateSemaphore (NULL, 1, 1, "mysem"); . . . - global variable case WM_LBUTTONDOWN: CreateThread (NULL,0, (LPTHREAD_START_ROUTINE) MyThread1,(LPVOID)hwnd, 0, &Tid1); CreateThread (NULL,0, (LPTHREAD_START_ROUTINE) MyThread2,(LPVOID)hwnd, 0, &Tid2); . . . //-----------------------------------------------------------------DWORD MyThread1 (LPVOID param) { if (WaitForSingleObject (hSema, 10000) == WAIT_TIMEOUT) { MessageBox ((HWND) param, "Thread 1 - Timeout", "Semaphore error", MB_OK); return 0; } for (i=0; i<10; i++) { . . . } ReleaseSemaphore (hSema, 1, NULL); return 0; } DWORD MyThread2 (LPVOID param) { if (WaitForSingleObject (hSema, 10000) == WAIT_TIMEOUT) { MessageBox ((HWND)param, "Thread 2 - Timeout", "Semaphore error", MB_OK); return 0; } for (i=0; i<10; i++) { . . . } ReleaseSemaphore (hSema, 1, NULL); return 0; }