The final result of two or more processes reading and writing shared data depends on the exact sequence of processes running. This is easy to happen when resources are shared. The solution is to set up a critical zone to allow processes to access and share resources with each other. A good solution to avoid competitive conditions must meet four conditions:
- Any two processes cannot be in the critical zone at the same time.
- We should not make any assumptions about the CPU speed and quantity.
- Processes running outside the critical section cannot block other processes.
- Do not wait for the process to enter the critical section indefinitely.
Busy waiting mutex 1. Block the interruption process from blocking the interruption (including clock interruption) when it enters the critical section, and enable the interruption when it leaves the critical section. This prevents the CPU from switching to other processes. The disadvantage of this solution is that the process that blocks the interruption may not be opened again, and the CPU will never be able to switch the process. What is blocked is the CPU interruption of the process, other CPUs that are not blocked and interrupted can still access shared resources. In multi-core systems, this method is not applicable.
2. Before the lock variable process enters the critical zone, it must hold the lock first and then occupy the lock. Other processes cannot get the lock and wait outside the critical zone. However, the disadvantage of this solution is that the process may be scheduled to determine whether the lock is available to occupy the lock. Another process also finds that the lock is available and enters the critical section. This causes two processes to enter the critical section at the same time.
3. The process of strict rotation can enter the critical section only after a variable is set, as shown in:
Process a Waits cyclically before turning to 0; process B waits cyclically before turning to 1. This is a busy wait, which clearly wastes CPU time. A lock used for waiting is called a spin lock. The problem with this solution is that the two processes must enter the critical section in strict order, which will reduce the execution efficiency of fast processes. That is, violation of the above conditions 3.
4. The Peterson algorithm is very simple and effective. It consists of only two C functions:
The key point is the while loop in the enter_region function. When two processes enter the enter_region function at the same time, the process that first enters the function enters the critical section, and then enters the while loop to wait.
5. The form of the TSL/xchg command TSL command is as follows: tsl rx, lock # Read the lock into the register RX and write 1 into the memory where the lock is located, the read and write operations are an inseparable atomic operation. The CPU running TSL locks the bus, making it impossible for any other CPU to access the shared memory, which is equivalent to blocking the interrupted memory version. Use the TSL command as follows:
The xchg commands exchange content in two locations atomically, so it can act as a substitute for the TSL command to control the process into the critical section. The principle is actually the same as it is, as shown in. All intel X86 CPUs use the xchg command in the underlying synchronization.
One common drawback of sleep and wake-up is that the process is in a waiting status when it cannot enter the critical section, which wastes CPU time. To block a process when it cannot enter the critical section, rather than waiting, you can use the inter-process communication primitive. For example, sleep and wakeup. The following are other solutions for mutual exclusion and synchronization between processes.
1. the semaphore is proposed by Dijkstra, a great god. It includes two operations: Down (p, indicating an attempt) and up (v, indicating an increase), which are also called PV operations:
- Down: Check whether the semaphore value is greater than 0. If it is greater than 0, it is reduced by 1 and continues. If it is equal to 0, the process is sleep. The entire operation is inseparable.
- Up: increase the value of the signal by 1, wake up the process that is sleeping due to the down operation, so that it continues to execute the unfinished down operation, the entire operation is inseparable.
Semaphores can be used for mutual exclusion and synchronization between processes.
- Mutex: Only one process can operate at a time. For example, the system enters the critical section mutex.
- Synchronization: processes must run in a certain sequence. For example, the producer must stop when it finds that the buffer is full, and the consumer must stop when it finds that the buffer is empty.
Example of how to use semaphores to solve producer-consumer problems:
Among them, empty and full are semaphores used for synchronization; mutex is used for mutex semaphores.
2. The mutex does not have the counting capability. It is a simplified version of the semaphore. The mutex contains two states: Unlock (0) and lock (1 ). The functions mutex_lock and mutex_unlock are implemented as follows:
The difference between mutex_lock and enter_region is that when the thread that calls mutex_lock cannot enter the critical section, the CPU (thread_yield function) is released to execute another thread; the call to enter_region will continuously perform cyclic testing (busy waiting) until the critical section is available.
3. There is a problem with the use of semaphores and mutex in the process: deadlock. For example, in the above semaphores code, if you sort down (& empty) and down (& mutex) sequentially, a deadlock may occur. The reason is that when the producer locks mutex and empty is 0 to sleep, the consumer will sleep because the mutex lock is not available, so that the two processes will sleep forever. This problem can be solved by using the management process. A process consists of a process, variable, and data structure. processes must access the process in this module with mutual exclusion, as shown in. An important feature of a management process is that there can be only one active process in the management process at any time.
There is a pipe program named example, which contains an integer variable I, a conditional variable C, and two procedures. The process provides an environment for mutually exclusive access to the process. The next step is to solve the synchronization problem. The solution is to use the condition variable: when the process in the pipe finds itself unable to continue running (for example, the producer finds that the buffer is full ), the wait operation will be performed on a condition variable to block itself and call other processes into the pipeline. After another process (such as the consumer) consumes the buffer, you can call signal to send a signal to the condition variable to wake up a process (such as a producer) That is blocked by calling wait, and then exit the process itself. The awakened process enters the process.
4. message transmission communicates with receive through two primitives. Send is used to send messages and receive is used to receive messages (blocking may occur ), that is to say, message is the carrier of shared resources. The following code uses the message transmission mechanism to solve the producer-consumer problem:
5. The barrier is a synchronization mechanism applied to process groups. It specifies that all processes have completed the nth stage before entering the nth + 1 stage. That is to say, when some processes have finished stage N and some other processes have not completed stage N, the processes completed need to be blocked and wait for the unfinished processes, as shown in. This can be achieved by adding barriers at the end of each stage.
Reference: Modern OS P66-P82.