Linux inter-process communication (IPC) programming practices (10) System V semaphores --- typical PV operations
// P primitive // P (semaphore * S)
Wait (semaphore * S ){
-- S-> value; if (S-> value <0)
{// Set the current process to blocking
// Insert the PCB of the current process into the block queue S-> block (S-> list) at the end of the list );
}}[
// V primitive // V (semaphore * S)
Signal (semaphore * S ){
+ + S-> value; if (S-> value <= 0) // indicates that a process is blocked.
{// Wake up a process waiting in the blocked queue S-> list and set it to the ready state;
// Insert it into the ready queue; wakeup (S-> list );
} P operation (wait): Apply for a unit resource and the process enters
V Operation (signal): releases a unit of resources and processes
Note the following when using the PV operation to achieve process mutex:
(1) The P and V operations that are mutually exclusive to the user in each program must appear in pairs. First, P operations are performed to enter the critical section, and then V operations are performed to output the critical section. If multiple branches exist, check their pairs carefully.
(2) P and V operations should be respectively close to the head and tail of the critical section. The code of the critical section should be as short as possible and there should be no endless loops.
(3) The initial value of mutex semaphores is generally 1.
In the analysis of the next typical question, we can regard the P operation as a judgment operation, that is, if; the V operation as a notification operation, which makes it easier to write pseudo code.
(1) producer and consumer issues
Producer-consumerproblem refers to the use of buffer resources when several processes exchange data through a limited shared buffer. Assume that the "producer" process constantly writes human data (that is, production data) to the shared buffer, while the "consumer" process constantly reads data (that is, consumption data) from the shared buffer. There are n shared buffer zones; only one process can operate on the shared buffer at any time. All producers and consumers must coordinate to complete operations on the shared buffer.
Producer process structure: do {
Wait (empty); wait (mutex );
Add nextp to buffer
Signal (mutex );
Signal (full);} while (1 );
Consumer process structure:
Do {wait (full );
Wait (mutex );
Remove an item from buffer to nextp
Signal (mutex); signal (empty );
} While (1 );
We can regard n buffer blocks in the shared buffer zone as shared resources. The buffer blocks of producer writer data become available resources for consumers, and the buffer blocks after consumers read data become available resources for producers. Therefore, you can set three semaphores: full, empty, and mutex. Where: full indicates the number of buffer blocks with data, the initial value is 0; empty indicates that the initial value of the number of empty buffer blocks is n; mutex is used to access the buffer zone, and the initial value is 1. In fact, the relationship between full and empty exists as follows: full + empty = N
Note:Here, the order of P operations in each process is important. Each process must first check its own number of resources before applying for mutex operations on the entire buffer after it is sure that there are available resources; otherwise, A deadlock may occur when you apply for a mutex operation on the entire buffer zone and then apply for the corresponding buffer block resources. When a deadlock occurs, after a mutex operation on the entire buffer zone is applied, the corresponding buffer block resource is discovered. At this time, it is impossible to discard the occupation of the entire buffer zone. If the AND semaphore set is used, the corresponding entry AND exit zones are very simple. If the producer's entry zone is Swait (empty, mutex), the exit zone is Ssignal (full, mutex ).
(2) reader writer problem reader-Writer problem (Readers-Writers problem) is also a classic concurrent program design problem and a frequent synchronization problem. Data (files and records) in computer systems are often shared by multiple processes, but some processes may only need to read data (called Reader ); other processes require data modification (called Writer ). In terms of data sharing, Reader and Writer are two groups of concurrent processes that share a group of data areas. Requirements: (1) Allow multiple readers to perform read operations simultaneously; (2) readers and writers are not allowed to operate simultaneously. (3) Multiple writers are not allowed to operate simultaneously. We use the reader priority policy to solve the problem: int rc = 0; // used to record the current number of readers semaphore rc_mutex = 1; // The mutex semaphores semaphore write = 1 for the rc operation of the shared variable; // The void reader () do {P (rc_mutex) used to ensure the mutex access by the reader and writer ); // start to mutex access to the rc shared variable rc ++; // a read process is generated. The number of read processes plus 1 if (rc = 1) P (write ); // if the first read process is used, determine whether a write process is in the critical section. // If yes, the read process waits. If no, the write process V (rc_mutex) is blocked ); // terminate the mutex access to the read file of the rc shared variable; P (rc_mutex); // start the mutex access to the rc shared variable rc --; // after reading a read process, the number of read processes minus 1 if (rc = 0) V (write); // The Last read process that leaves the critical section needs to determine whether a write process exists. // Interface area. If yes, wake up a write process into the Critical Zone V (rc_mutex); // end mutually exclusive access to the rc shared variable} while (1) void writer () do {P (write); // No read process, enter the write process; if there is a read process, the write process waits for the file to be written; V (write); // The write process is completed; determine whether a read process needs to enter the critical section. // If yes, wake up a read process in the critical section.} while (1) the reader's preferred design idea is that the read process can continue reading as long as other read processes are reading; the write process must wait until all read processes are not reading, even if the write process may submit an application earlier than some read processes. This algorithm allows subsequent readers to come in as long as there is another reader in the activity. The result of this policy is that if a stable reader stream exists, then these readers will be allowed to enter after they arrive. The writer is always suspended until there are no readers. (3) The dining question of philosophers is assumed that five philosophers sat around a circular dining table and did one of the following two things: eating or thinking. When they eat, they stop thinking, and when they think about it, they also stop eating. There is a cross between every two philosophers. Because it is difficult to eat with a single cross, it is assumed that philosophers must eat with two forks, and they can only use the two forks on their left and right sides. [Cpp] view plaincopyvoid philosopher (int I)/* I: Number of philosophers, from 0 to 4 */{while (TRUE) {think (); /* philosophers are thinking */take_fork (I);/* Take chopsticks on the left */take_fork (I + 1) % N);/* Take chopsticks on the left; % is modulo operation */eat ();/* eat */put_fork (I);/* put the left chopsticks back to the table */put_fork (I + 1) % N);/* put the chopsticks on the right back to the table */} analysis: If all philosophers pick up the chopsticks on the left at the same time, they can see that the chopsticks on the right are unavailable, and they all put down the chopsticks on the left, wait for a while, and at the same time pick up the chopsticks on the left side, so that it will always be repeated. In this case, all programs run indefinitely, but no progress can be made, that is, hunger, and no philosophers can eat. Problem Algorithm Description: after obtaining the chopsticks on the left, check whether the chopsticks on the right are available. If not, put down the chopsticks on the left and repeat the process for a while. Analysis: in the following situations, all philosophers started the algorithm at the same time, picked up the chopsticks on the left, and saw that the chopsticks on the right were unavailable, and put down the chopsticks on the left, wait for a while and pick up the chopsticks on the left ...... This will always be repeated. In this case, all programs are running, but no progress can be made, that is, hunger occurs, and all philosophers cannot eat. 2) describe an algorithm that does not starve to death (never get chopsticks. Four implementation methods (A, B, C, and D) are considered: A: Principle: at most four philosophers are allowed to eat at the same time to ensure that at least one philosopher can eat at the same time, in the end, the two chopsticks he used will be released, so that more philosophers can eat. The following uses room as a semaphore to allow only four philosophers to eat at the same time, so that at least one philosopher can eat at the same time, and the philosopher applying to enter the restaurant enters the waiting queue of the room, according to the FIFO principle, there will always be dining in the restaurant, so there will be no starvation or deadlock. Pseudo code: [cpp] view plaincopysemaphore chopstick [5] = {1, 1, 1}; semaphore room = 4; void philosopher (int I) {while (true) {think (); wait (room); // request to enter the room for meal wait (chopstick [I]); // request the chopsticks on the left hand side for wait (chopstick [(I + 1) % 5]); // request the chopsticks eat () on the right hand side; signal (chopstick [(I + 1) % 5]); // release the right-hand chopsticks signal (chopstick [I]); // release the left-hand chopsticks signal (room); // exit the room to release the semaphore room} B: Principle: the philosopher is allowed to pick up and eat chopsticks only when both of them are available. Method 1: Implemented by using the AND semaphore mechanism: according to the course, in a primitive, multiple critical resources required by a piece of code at the same time are either allocated to all of them, either one is not allocated, so no deadlock occurs. When some resources are insufficient, the calling process is blocked. Because of the existence of the waiting queue, requests to resources meet the requirements of FIFO, so there will be no hunger. Pseudo code: semaphore chopstick [5] = {1, 1, 1}; void philosopher (int I) {while (true) {think (); swait (chopstick [(I + 1)] % 5, chopstick [I]); eat (); Ssignal (chopstick [(I + 1)] % 5, chopstick [I]) ;}} Method 2: using the semaphore protection mechanism. Use mutex to protect the Left and Right chopsticks operations before eat () to make it an atomic operation. This prevents deadlocks. Pseudo code: semaphore mutex = 1; semaphore chopstick [5] = {1, 1, 1}; void philosopher (int I) {while (true) {think (); wait (mutex); wait (chopstick [(I + 1)] % 5); wait (chopstick [I]); signal (mutex); eat (); signal (chopstick [(I + 1)] % 5); signal (chopstick [I]);} C: Principle: require philosophers of odd numbers to pick up chopsticks on their left first, then take the chopsticks on his right, while the philosophers with even numbers are the opposite. according to this rule, philosophers No. 1 and No. 2 will compete for No. 1 chopsticks, and philosophers No. 3 and No. 3 compete for No. 3 chopsticks. that is to say, all five philosophers compete for odd-number chopsticks. After obtaining them, they compete for even-number chopsticks. In the end, there will always be a philosopher who can eat two chopsticks. Philosophers that cannot be applied enter the blocking wait queue. The root FIFO principle means that the philosophers who apply first can eat before they can, so there will be no philosophers who starve to death. Pseudo code: semaphore chopstick [5] = {1, 1, 1}; void philosopher (int I) {while (true) {think (); if (I % 2 = 0) // an even number of philosophers, first right and then left. {Wait (chopstick [I + 1] mod 5); wait (chopstick [I]); eat (); signal (chopstick [I + 1] mod 5 ); signal (chopstick [I]);} Else // an odd number of philosophers, first left and right. {Wait (chopstick [I]); wait (chopstick [I + 1] mod 5); eat (); signal (chopstick [I]); signal (chopstick [I + 1] mod 5) ;}} D: using the pipe process mechanism (the implementation fails in the end, see the following analysis): Principle: instead of semaphores, set semaphores for each philosopher. The test () function has the following functions: a. If the current philosopher is in hunger and the philosophers on both sides are not in the eating state, the current philosopher tries to enter the eating state through the test () function. B. if it fails to enter the EATING state through test (), the current philosopher waits in the semaphore until other philosopher processes set the state of the philosopher to EATING through test. C. when a philosopher process calls put_forks () to put down chopsticks, it tests its neighbor through test (). If the neighbor is hungry and the neighbor is not eating, the neighbor enters the eating state. As mentioned above, the algorithm will not experience deadlocks, because a philosopher can switch to the dining State only when two neighboring seats are not in the dining state. This algorithm will result in a situation where a philosopher cannot eat at the end, that is, when the philosophers of the philosopher are both in the eating state, the philosopher will never be able to enter the eating state, therefore, it does not meet the requirements of the question. However, this algorithm can achieve the maximum degree of parallelism for any number of philosophers, so it is of great significance. Pseudo code: # define N 5/* Number of philosophers */# define LEFT (I-1 + N) % N/* I LEFT Neighbor number */# define RIGHT (I + 1) % N/* I's right Neighbor number */typedef enum {THINKING, HUNGRY, EATING} phil_state; /* philosopher state */monitor dp/* manager */{phil_state state [N]; semaphore mutex = 1; semaphore s [N];/* Each philosopher has a semaphore, the initial value is 0 */void test (int I) {if (state [I] = HUNGRY & state [LEFT (I)]! = EATING & state [RIGHT (I)]! = EATING) {state [I] = EATING; V (s [I]) ;}} void get_forks (int I) {P (mutex); state [I] = HUNGRY; test (I);/* try to get two chopsticks */V (mutex); P (s [I]); /* If the chopsticks are not obtained, blocking */} void put_forks (int I) {P (mutex); state [I] = THINKING; test (LEFT (I )); /* check whether the left neighbor eats Meals */test (RIGHT (I);/* check whether the RIGHT neighbor eats Meals */V (mutex);} The Philosopher's process is as follows: void philosopher (int process) {while (true) {think (); get_forks (process); eat (); put_forks (process );}}