preface
Learning the operating system and taking notes.
(add the types of problems related to semaphores and solutions)
reference material: Operating system (essence and design principles, 6th Edition) Royal postgraduate entrance examination: 2019 Kingway postgraduate entrance examination operating system_ Beeping beeping beeping beeping beeping beeping beeping beeping beeping beeping beeping beeping beeping beeping beeping beeping beeping beeping beeping beeping beeping beeping beeping beeping beeping beeping beeping beeping beeping beeping beeping beeping beeping beeping beeping beeping beeping beeping beeping beeping beeping
text
First of all, for convenience (I'm lazy), P operation may be used to refer to semWait(x); Use the V operation to refer to semSignal(x).
Producer consumer problem
Problem Description:
- There are a group of producer processes and a group of consumer processes in the system. The producer process produces one product at a time and puts it into the buffer. The consumer process takes out one product from the buffer and uses it at a time. (Note: "product" here is understood as some kind of data)
- Producers and consumers share a buffer with an initial empty size of n.
- Only when the buffer is not full can the producer put the product into the buffer, otherwise it must wait.
- Only when the buffer is not empty can consumers take out products from it, otherwise they must wait.
- Buffer is a critical resource, and each process must access it mutually exclusive (only one producer is allowed to put in messages, and only one consumer is allowed to take out messages). Explain the last point in detail again. It means that only one producer or consumer can operate the buffer at the same time. The following situations are prohibited: multiple producers or consumers operate the buffer; Similarly, it is forbidden for a producer and a consumer to operate at the same time.
analysis:
- Mutual exclusion: two or more parties are not allowed to access the buffer at the same time.
- Synchronization relationship: there are two kinds of synchronization relationships: buffer full and buffer empty. (when consumers consume a product, they should tell the producer that there is a vacancy in the buffer zone; when producers produce a commodity, they should tell consumers that the buffer zone is not empty)
Therefore, according to the above analysis, three pairs of P and V operations should be required. Therefore, the following three semaphores need to be defined:
semaphore mutex = 1; //Mutually exclusive semaphores to access the buffer semaphore empty = n; //Synchronization semaphore, indicating the number of free buffers (the initial value is n) semaphore full = 0; //Synchronization semaphore, indicating the number of products, that is, the number of non buffers (the initial value is 0)
realization:
/*producer*/ void producer() { while(1){ /*Produce a product*/; semWait(empty); //Check whether the buffer is available (I) semWait(mutex); //Mutually exclusive operation (II) /*Put product into buffer*/; semSignal(mutex); //Mutually exclusive operation semSignal(full); //Tell consumers "produced a product" } } /*consumer*/ void consumer() { while(1){ semWait(full); //Check for products (non empty buffer) (III) semWait(mutex); //Mutually exclusive operation (Ⅳ) /*Take a product from the buffer*/; semSignal(mutex); //Mutually exclusive operation semSignal(empty); //Tell the producer that "a product has been taken and the buffer is free" /*Use products*/; } } void main() { parbegin(producer(), consumer()); }
Thinking: can the sequence of adjacent P and V operations be changed? (that is, the order of replacing (I), (II) and (III), (IV)) analysis:
- Analyze this situation: if the buffer is full of products, empty=0, full=n.
- Then the producer first executes the operation (II) (semWait(mutex)) to make mutex become 0. After executing the operation (I) (semWait(empty)), the producer is blocked because there is no free buffer. Then switch to the consumer process. The consumer process first executes the (IV) (semWait(mutex)) operation. Since mutex is 0, that is, the producer has not released control over the critical resources, the consumer is also blocked.
- This creates a deadlock situation (the producer waits for the consumer to release the free buffer, while the consumer waits for the producer to release the critical buffer)
- Similarly, we analyze another case: if there is no product in the buffer, that is, empty=n, full=0. According to the order of (IV) ` ` (III) ` ` (II), life and death lock will also occur.
Therefore:
- The mutually exclusive P operation must be implemented after the synchronous P operation.
- V operation will not cause process blocking, so the sequence of two V operations can be exchanged.
Another point to note: The producer's operation of "producing a product" and the consumer's operation of "using a product" can be placed between P and V operations, but we should be aware that these operations are unnecessary. If they are placed in the critical area, the amount of code will become larger, and a process will spend more time while accessing the critical area, which will reduce the concurrency between processes. (only necessary operation in critical area)
Multi producer multi consumer problem
Problem Description:
There is a plate on the table. Only one fruit can be put into it at a time. The father puts apples on the plate, the mother puts oranges on the plate, the son waits for oranges on the plate, and the daughter waits for apples on the plate. Only when the plate is empty can father or mother put a fruit on the plate. The son or daughter can take the fruit out of the plate only when there is the fruit they need. The above process is realized by P and V operation.
analysis:
- Mutex: treat the plate as a buffer. Everyone's access to it is mutually exclusive.
- Synchronization relationship:
- The father put the apple on the plate before the daughter could take it.
- The mother put the oranges on the plate before the son could take them.
- Only when the plate is empty can the father or mother put fruit in it. (the event of empty plate can be triggered by son or daughter)
Therefore, according to the above analysis, the following four semaphores need to be defined:
semaphore mutex = 1; //Implement mutually exclusive access (critical area) semaphore apple = 0; //There are several apples on the plate (the initial value is 0) semaphore orange = 0; //How many oranges are there on the plate (initial value is 0) semaphore plate = 1; //How many more fruits can you put on the plate
realization:
/*dad*/ void dad() { while(1){ /*Prepare an apple*/; semWait(plate); //Check if the plate is empty semWait(mutex); //Implement this pair of mutually exclusive buffers /*Put the apples on the plate*/; semSignal(mutex); semSignal(apple); //Tell your daughter "there are apples on the plate" } } /*mom*/ void mom() { while(1){ /*Prepare an orange*/; semWait(plate); //Check if the plate is empty semWait(mutex); //Realize mutually exclusive access to this buffer /*Put the oranges on the plate*/; semSignal(mutex); semSignal(orange); //Tell your son "there are oranges on the plate" } } /*daughter*/ void daughter() { while(1){ semWait(apple); //Check if there are any apples on the plate semWait(mutex); //Realize mutually exclusive access to this buffer /*Remove the apple from the plate*/; semSignal(mutex); semSignal(plate); //Tell your parents "the plate is empty" /*Eat apples*/; } } /*son*/ void son() { while(1){ semWait(orange); //Check the plate for oranges semWait(mutex); //Realize mutually exclusive access to this buffer /*Remove the orange from the plate*/; semSignal(mutex); semSignal(plate); //Tell your parents "the plate is empty" /*Eat oranges*/; } } void main() { parbegin(dad(), mom(), daughter(), son()); }
Think: can the above case not use mutex? Analysis (delete all semWait(mutex) and semSignal(mutex) operations in the above code):
- At first, the son and daughter processes will be blocked even if they run on the processor.
- If the parent process runs on the processor first at the beginning, then:
- The father performs the semWait(plate) operation and can access the plate.
- The mother performs the semWait(plate) operation and is blocked waiting for the plate.
- Father performs the semSignal(apple) operation and puts in the apple.
- Daughter i process is awakened (other processes will block even if they are running).
- The daughter performs the semWait(apple) operation to access the plate. And then execute the semSignal(plate) operation.
- The father or mother process is awakened again (the son process access will be blocked).
- ...
From the above analysis, it can be concluded that even if there is no mutex, it will not affect the normal operation. The reason is that the buffer size in this question is 1. At any time, at most one of the three synchronization semaphores apple, orange and plate is 1. Therefore, at any time, the P operation of at most one process will not be blocked and enter the critical area smoothly. However, if the capacity of the plate is 2 or more, it is impossible to ensure that the processes are mutually exclusive to access the plate. (analysis process omitted) It is best to set mutex directly to prevent errors.
Smoker Problem
Problem Description:
Suppose a system has three smoker processes and one supplier process. Every smoker keeps smoking and smoking it, but to roll up and smoke a cigarette, a smoker needs three materials: tobacco, paper and glue. Among the three smokers, the first has tobacco, the second has paper and the third has glue. The supplier process provides three materials infinitely. The supplier puts two materials on the table at a time. The smoker with the remaining third material rolls a cigarette and smokes it, and sends a signal to the supplier process that when it is completed, the supplier will put the other two materials on the table. This process is repeated all the time (let the three smokers smoke in turn)
analysis:
- Mutex: treat the table as a buffer, and the access of each process to it is mutually exclusive.
It is worth noting that although the supplier will place two materials on the table at a time, the capacity of the table (buffer zone) is still 1. We should treat the two different materials as a combination:
- Combination 1: paper + glue
- Combination 2: tobacco + glue
- Combination 3: tobacco + paper
- Synchronization relationship:
- There is a group on the table → the first smoker takes something away.
- There is combination two on the table → the second smoker takes something away.
- There is group three on the table → the third smoker takes something away.
- The smoker has finished smoking → the supplier puts the next combination material on the table.
- Three smokers need to smoke in turn.
Therefore, according to the above analysis, the following four semaphores need to be defined:
semaphore offer1 = 0; //Number of combinations on the table semaphore offer2 = 0; //Number of combination two on the table semaphore offer3 = 0; //Number of combination three on the table semaphore finish = 0; //Is smoking completed int i = 0; //Used to realize "three smokers smoke in turn"
realization:
/*provider*/ void provider() { while(1){ if(i == 0){ /*Put the combination on the table*/; semSignal(offer1); //Tell the first smoker "there's a combination on the table" } else if(i == 1){ /*Put combination 2 on the table*/; semSignal(offer2); //Tell the second smoker "there's combination two on the table" } else if(i == 2){ /*Put combination three on the table*/; semSignal(offer3); //Tell the third smoker "there's group three on the table" } i = (i + 1) % 3; semWait(finish); //Judge whether the smoker has finished smoking } } /*smoker1*/ void smoker1() { while(1){ semWait(offer1); //Judge whether there is a combination on the table /*Remove the combination one from the table; Cigarettes; Take out*/; semSignal(finish); //Tell the provider "the table is empty" } } /*smoker2*/ void smoker2() { while(1){ semWait(offer2); //Judge whether there is combination two on the table /*Remove combination 2 from the table; Cigarettes; Take out*/; semSignal(finish); //Tell the provider "the table is empty" } } /*smoker3*/ void smoker3() { while(1){ semWait(offer3); //Judge whether there is combination three on the table /*Take combination three from the table; Cigarettes; Take out*/; semSignal(finish); //Tell the provider "the table is empty" } } void main() { parbegin(provider(), smoker1(), smoker2(), smoker3()); }
Analysis: is it necessary to set another mutex? Because the capacity of the table is 1 (the same as the above example), there is no need to set it and there will be no error.
Reader writer problem
Problem Description:
There are two groups of concurrent processes, readers and writers, sharing a file. When two or more reading processes access the shared data at the same time, there will be no side effects. However, if a writing process and other processes (reading process or writing process) access the shared data at the same time, it may lead to the error of inconsistent data. Therefore, we request:
- Allow multiple readers to read files at the same time;
- Only one writer is allowed to write information into the file;
- Any writer shall not allow other readers or writers to work before completing the writing operation;
- Before the writer performs the writing operation, all existing readers and writers should exit.
analysis:
- Mutually exclusive relationship:
- Write process - write process
- Write process - read process (there is no mutually exclusive relationship between reading processes, so files can be read at the same time. How to realize this operation is the key to such problems)
Therefore, according to the above analysis, the following three semaphores need to be defined:
semaphore rw = 1; //Used to realize mutually exclusive access to files. Indicates whether a process is currently accessing the shared file int count = 0; //Used to record how many reading processes are currently accessing files semaphore mutex = 1; //Used to ensure mutually exclusive access to the count variable
realization:
/*writer*/ void writer() { while(1){ semWait(rw); //"Lock" before writing files /*Write file*/; semSignal(rw); //"Unlock" after writing } } /*reader*/ void reader() { while(1){ semWait(mutex); //Each read process is mutually exclusive and accesses count if(count == 0){ semWait(rw); //The first read process is responsible for "locking" } count++; //Number of read processes accessing the file plus one semSignal(mutex); /*read file*/; semWait(mutex); //Each read process is mutually exclusive and accesses count count--; //The number of read processes accessing the file is reduced by one if(count == 0){ semSignal(rw); //The last read process is responsible for "unlocking" } semSignal(mutex); } } void main() { parbegin(writer(), reader1(), reader2(),...,readern()); }
analysis: The mutex variable is set to solve the access of the read process to count (the operation of accessing multiple read processes)
- The first read process executes the semWait(mutex) operation and passes smoothly, and executes the subsequent semWait(rw) operation (ensuring the mutual exclusion of the read process and the write process)
- If another reader process accesses at this time, it will be blocked during the execution of semWait(mutex) until the first reader process executes semSignal(mutex) (unlock the access to count. Note that the count value is 1 at this time)
- After the count is "unlocked", another reader process will execute the semWait(mutex) operation, judge that the value of count is not 0, and directly skip the access of semWait(rw), so as to avoid mutually exclusive access between various reader processes
After careful experience, you will find that there is a potential problem in the above code, that is, if the reading process keeps accessing (the count value is never 0), the file cannot be "unlocked" and the writer process may "starve to death". Therefore, this writing method is read process first.
How to achieve write process priority?
Here we add another semaphore: semaphore w = 1.
Now there are four semaphores:
semaphore rw = 1; //Used to realize mutually exclusive access to files. Indicates whether a process is currently accessing the shared file int count = 0; //Used to record how many reading processes are currently accessing files semaphore mutex = 1; //Used to ensure mutually exclusive access to the count variable semaphore w = 1; //Used to implement "write process first"
realization:
/*writer*/ void writer() { while(1){ semWait(w); //Ⅰ semWait(rw); //"Lock" before writing files /*Write file*/; semSignal(rw); //"Unlock" after writing semSignal(w); //Ⅳ } } /*reader*/ void reader() { while(1){ semWait(w); //Ⅴ semWait(mutex); //Each read process is mutually exclusive and accesses count if(count == 0){ semWait(rw); //The first reading process is responsible for "locking" VI } count++; //Number of read processes accessing the file plus one semSignal(mutex); semSignal(w); //Ⅶ /*read file*/; semWait(mutex); //Each read process is mutually exclusive and accesses count count--; //The number of read processes accessing the file is reduced by one if(count == 0){ semSignal(rw); //The last reading process is responsible for "unlocking" Ⅷ } semSignal(mutex); } } void main() { parbegin(writer(), reader1(), reader2(),...,readern()); }
Analysis: (let me make complaints about the scalp)
- Reader 1 → Reader 2
- The first reading process successfully passed the V (semWait(w)) operation, and "locked" the file (rw), count + +, and finally read the file smoothly.
- The second reading process is the same as above.
- That is, the addition of V (semWait(w)) and VII (semSignal(w)) has no impact on multiple read processes accessing files concurrently.
- Writer 1 → writer 2
- The first write process will successfully pass I (semWait(w)) operation and II (semWait(rw)) operation, and write files smoothly.
- If the second write process executes concurrently, it will be blocked at I until the first write process executes "unlock write operation" in IV (semSignal(w)).
- It realizes the mutual exclusion between multiple write processes.
- Writer 1 → Reader 1
- Write that the process successfully passes the I (semWait(w)) operation and the II (semWait(rw)) operation and starts writing files.
- At this time, if the read process wants to execute, it will be blocked in the V (semWait(w)) operation. It can not continue to execute until the write process completes the IV (semSignal(w)) unlock write operation.
- It realizes the mutual exclusion between readers and writers.
- Reader 1 → writer 1 → Reader 2
- The first read process can successfully perform the operation of reading files.
- At this time, if the write process is executed, the I (semWait(w)) operation can be executed smoothly, but the II (semWait(rw)) operation will be blocked.
- Then, the second read process executes concurrently. Since the previous read process successfully executes I (semWait(w)) operation, the second read process will be blocked in V (semWait(w)) operation.
- Until the first reading process completes VIII (semSignal(rw)) operation to "unlock" the file, the writing process can be awakened and start writing the file.
- The second read process is still blocked in the V (semWait(w)) operation. The second read process cannot continue until the write process completes the IV (semSignal(w)) operation.
- Therefore, this situation does not lead to the "hunger" of the writing process.
- Writer 1 → Reader 1 → writer 2
- The first write process smoothly executes the previous operation and starts writing files.
- At this time, if the read process executes, it will be blocked in V (semWait(w)) operation.
- At this time, if the second write process executes, it will also be blocked in I (semWait(w)) operation.
- If the first write process completes the IV (semSignal(w)) operation, the read process will be awakened first according to the "first come, first served" principle, and the read process will execute smoothly.
- Then, the read process will execute VI (semWait(rw)) operation to "lock" the file. After VII (semWait(w)) operation, release the operation on the write process, and the second write process will continue to execute, but will be blocked in II (semWait(rw)) operation.
- The read process continues to execute until it is over, and the access control of the file is released, so that the second write process can execute smoothly.
- In this case, we can analyze that although it will not make the writing process "hungry", it is not a real "writing process priority", but in line with the relatively fair principle of first come, first serve.
the dining philosophers problem
Problem Description:
There are five philosophers sitting on a round table. A chopstick is placed on the table between each two philosophers, and a bowl of rice is in the middle of the table. Philosophers devote their whole life to thinking and eating. Philosophers do not affect others when thinking. Only when the philosopher was hungry did he try to pick up the left and right chopsticks (one by one). If the chopsticks are already in the hands of others, you need to wait. The hungry philosopher can start eating only when he picks up two chopsticks at the same time. After eating, he puts down his chopsticks and continues to think.
analysis:
- Mutual exclusion: there are five philosopher processes in the system, and the visits of five philosophers and their left and right neighbors to their middle chopsticks are mutually exclusive. There is only mutual exclusion in this problem, but different from the previous problems, each philosopher process needs to hold two critical resources at the same time to start eating. How to avoid deadlock caused by improper allocation of critical resources is the key to philosophers' problems.
Therefore, according to the above analysis, it is necessary to define the following semaphore array and a mutually exclusive semaphore:
semaphore chopstick[5] = {1,1,1,1,1}; //Used to realize mutually exclusive access to 5 chopsticks semaphore mutex = 1; //Take chopsticks mutually exclusive
It is also stipulated that philosophers are numbered from 0 to 4. The number of chopsticks on the left of philosopher i is i and the number of chopsticks on the right is (i + 1)% 5.
realization:
/*philosopher*/ void philosopher(int i) { while(1){ /*reflection*/; semWait(mutex); semWait(chopstick[i]); //Pick up the left chopsticks semWait(chopstick[(i+1)%5]); //Pick up the right chopsticks semSignal(mutex); /*Eat*/; semSignal(chopstick[i]); //Put down the left chopsticks semSignal(shopstick[(i+1)%5]); //Put down the right chopsticks /*reflection*/; } } void main() { parbegin (philosopher(0), philosopher(1), philosopher(2), philosopher(3), philosopher(4)); }
analysis: If the philosopher process wants to proceed normally, it needs to pick up the left and right chopsticks at the same time. Once the requested resources cannot be met, it will enter the blocking state.
Method 2:
Analysis: according to the "pigeon cage principle", as long as there are at most four philosophers competing for chopsticks for dinner, then one philosopher can get two chopsticks at the same time. For this purpose, we define the following semaphores:
semaphore chopstick[5] = {1,1,1,1,1}; //Used to realize mutually exclusive access to 5 chopsticks semaphore room = 4; //A maximum of 4 people are allowed to eat for philosophers
realization:
/*philosopher*/ void philosopher(int i) { while(1){ /*reflection*/; semWait(room); semWait(chopstick[i]); //Pick up the left chopsticks semWait(chopstick[(i+1)%5]); //Pick up the right chopsticks /*Eat*/; semSignal(chopstick[i]); //Put down the left chopsticks semSignal(shopstick[(i+1)%5]); //Put down the right chopsticks semSignal(room); /*reflection*/; } } void main() { parbegin (philosopher(0), philosopher(1), philosopher(2), philosopher(3), philosopher(4)); }
Method 3:
Ask odd numbered philosophers to take the chopsticks on the left first, and then the chopsticks on the right; On the contrary, philosophers with even numbers take the chopsticks on the right first, and then the chopsticks on the left. Under this strategy, if two adjacent parity philosophers want to eat, only one of them can pick up the first chopstick and the other will block directly. This avoids the situation of occupying one and then waiting for another.
Firstly, the following semaphores are defined:
semaphore chopstick[5] = {1,1,1,1,1}; //Used to realize mutually exclusive access to 5 chopsticks
realization:
/*philosopher*/ void philosopher(int i) { while(1){ if(i % 2 == 1){ //Odd number philosopher /*reflection*/; semWait(chopstick[i]); //Pick up the left chopsticks semWait(chopstick[(i+1)%5]); //Pick up the right chopsticks /*Eat*/; semSignal(chopstick[i]); //Put down the left chopsticks semSignal(shopstick[(i+1)%5]); //Put down the right chopsticks /*reflection*/; } if(i % 2 == 0){ //Even number philosopher /*reflection*/; semWait(chopstick[(i+1)%5]); //Pick up the right chopsticks semWait(chopstick[i]); //Pick up the left chopsticks /*Eat*/; semSignal(shopstick[(i+1)%5]); //Put down the right chopsticks semSignal(chopstick[i]); //Put down the left chopsticks /*reflection*/; } } } void main() { parbegin (philosopher(0), philosopher(1), philosopher(2), philosopher(3), philosopher(4)); }
Postscript
This article is not over yet. It is being updated continuously.
(criticism and correction in the comment area are welcome)