This is the 67th original article by Java builders. Previous article We dissect the nature of processes and threads, how processes and threads are implemented. In this article, we will explore how they communicate. The process tells me that threads don't want to live anymore. I d

2025/07/2522:35:51 hotcomm 1145

This is the 67th original article by Java builders. Previous article We dissect the nature of processes and threads, how processes and threads are implemented. In this article, we will explore how they communicate. The process tells me that threads don't want to live anymore. I d - DayDayNews

This is the 67th original article of the builder of Java

Previous article We dissected the essence of processes and threads , how processes and threads are implemented. In this article, we will explore how they communicate. The process told me that the thread does not want to live anymore. I don’t care about its life or death. I just want to know who I am? How did the process tell me? Is there any necessary connection between the occurrence of a process and the death of a thread? The article reveals to you. In the previous article, we have dissected the nature of processes and threads, and how processes and threads are implemented. In this article, we will explore how they communicate. The process tells me that the thread does not want to live anymore. I don’t care about its life or death. Who am I? How did the process tell me? Is there any necessary connection between the occurrence of a process and the death of a thread? The article reveals it to you...

Inter-process communication

process needs to communicate frequently with other processes. For example, in a shell pipeline, the output of the first process must be passed to the second process, so that it proceeds along the pipeline. Therefore, if communication is required between processes, a good data structure must be used so that it cannot be interrupted. Next, we will discuss issues related to inter-process communication (IPC).

Regarding communication between processes, there are three questions here

  • mentioned the first question above, that is, how a process passes messages to other processes.
  • The second question is how to ensure that two or more threads do not interfere with each other. For example, both airlines are trying to snap up the last seat on the plane for different customers.
  • The third problem is the order of data, if process A generates data and process B prints data. Then, before process B prints data, you need to wait until A generates data before printing.

It should be noted that the next two of these three problems are also applicable to threads

The first problem is easier to solve between threads because they share an address space and they have the same runtime environment. You can imagine that in the process of writing multi-threaded code in a high-level language, is the thread communication problem easier to solve?

The other two problems also apply to threads, and the same problems can be solved in the same way. We will discuss these three issues slowly later, and you can just have a rough impression in your mind now.

race condition

In some operating systems, collaborative processes may share some public resources that can be read and written to each other. Public resources may be in memory or in a shared file. In order to explain clearly how processes communicate, here we give an example: a spooler. When a process needs to print a file, it will place the file name in a special background directory. Another process Print background process (printer daemon) periodically checks whether the file needs to be printed, and if so, prints and deletes the file name from the directory.

Assume that our background directory has a lot of slots, the numbers are 0, 1, 2,..., and each slot stores a file name. At the same time, assume that there are two shared variables: out, pointing to the next file to be printed; in, pointing to the next free slot in the directory. These two files can be saved in a file that can be accessed by all processes, with the length of the file being two characters. At some point, slots 0 to 3 are empty, and slots 4 to 6 are occupied. At the same time, process A and process B decide to queue up to print a file. The situation is as follows:

This is the 67th original article by Java builders. Previous article We dissect the nature of processes and threads, how processes and threads are implemented. In this article, we will explore how they communicate. The process tells me that threads don't want to live anymore. I d - DayDayNews

Murphy's Law (Murphy) said that any possible error will eventually occur. When this sentence takes effect, the following situation may occur.

Process A reads that the value of in is 7, and 7 is present in a local variable next_free_slot. At this time, a clock interrupt occurred, and the CPU believed that process A had been running for a long enough time and decided to switch to process B. Process B also reads the value of in and finds it is 7. Then process B writes 7 into its local variable next_free_slot. At this moment, both processes think that the next available slot is 7.

Process B continues to run now, it will write the print file name into slot 7, then change the pointer of in to 8, and then process B leaves to do other things

Now process A starts to resume running. Since process A checks next_free_slot, it also finds that slot 7 is empty, so it saves the print file name into slot 7, and then updates the value of in to 8. Since there is already a value written by process B in slot 7, the printing file name of process A will overwrite the file name of process B. Since it is impossible to find which process is updated inside the printer, its functions are relatively limited, so at this time process B It is never possible to print out. In this case, means that when two or more threads modify a shared data at the same time, thereby affecting the correctness of the program's operation, this is called race condition . Debugging race conditions is a very difficult task, because the program runs well in most cases, but in rare cases some unexplained strange phenomena occur. Unfortunately, this problem brought about by multi-core growth has made race conditions increasingly common.

critical area

not only will share resources cause race conditions, but in fact, shared files and shared memory of will also cause race conditions. So how to avoid it? Perhaps one sentence can be summarized: prohibits one or more processes from reading and writing shared resources (including shared memory, shared files, etc.) at the same time. In other words, we need a mutual exclusion condition, which means that if a process uses shared variables and files in some way, processes other than that process prohibit this (access to unified resources). The confusion about the above problem is that process B uses it before process A's use of shared variables is completed. In any operating system, choosing the appropriate primitive to achieve mutually exclusive operations is a major design issue, and we will focus on it next.

The conditions for avoiding competition problems can be described in an abstract way. Most of the time, processes are busy with internal calculations and other calculations that do not cause race conditions. However, sometimes processes access shared memory or files, or do operations that can lead to race conditions. We call program segments that access shared memory a critical region or critical section. If we can operate correctly and make it impossible for two different processes to be in the critical zone at the same time, we can avoid race conditions, which is also carried out from the perspective of operating system design.

Although the above design avoids race conditions, it cannot ensure the correctness and efficiency of concurrent threads accessing shared data at the same time. A good solution should include the following four conditions

  1. At any time, two processes cannot be in the critical area at the same time
  2. should not make any assumptions about the speed and number of CPUs. The process
  3. is located outside the critical area. The process
  4. cannot block other processes
  5. cannot make any process wait infinitely to enter the critical area

This is the 67th original article by Java builders. Previous article We dissect the nature of processes and threads, how processes and threads are implemented. In this article, we will explore how they communicate. The process tells me that threads don't want to live anymore. I d - DayDayNews

From an abstract perspective, we usually hope that the behavior of the process is as shown in the figure above. At the moment of t1, process A enters the critical area, and at the moment of t2, process B attempts to enter the critical area, because at this time process A is in the critical area, so process B will block until at t3, process A leaves the critical area, and at this time process B Able to allow access to the critical zone. Finally, at t4, process B leaves the critical area and the system returns to its original state without a process.

busy and other mutually exclusive

Next we will continue to explore various designs to implement mutually exclusive. In these solutions, when a process is busy updating shared memory in its critical area, no other process will enter its critical area and will not have any impact.

blocking interrupts

On a single processor system, the easiest solution is to have each process block all interrupts immediately after entering the critical zone and re-enable them before leaving the critical zone. After blocking interrupts, clock interrupts will also be blocked. The CPU will switch process only if a clock interrupt or other interrupt occurs.This way, the CPU does not switch to other processes after the blocking interrupt is done. So, once a process blocks interrupts, it can check and modify shared memory without worrying about other processes intervening in accessing shared data. Is this solution feasible? Who decides when the process enters a key area? Isn't it a user process? When the process enters a critical area, the user process closes the interrupt. If the process does not leave after a long period of time, then the interrupt will not be enabled. What will happen? This may cause the entire system to terminate. And if it is a multiprocessor, blocking interrupts are only valid for the CPU that executes the disable instructions. Other CPUs will continue to run and can access shared memory.

On the other hand, it is convenient for the kernel to block interrupts during execution of several instructions for updating variables or lists. For example, if an interrupt occurs when multiple processes are processed in the ready list, race conditions may occur. Therefore, blocking interrupts is a very useful technology for the operating system itself, but for user threads, blocking interrupts is not a general mutually exclusive mechanism.

lock variable

As a second attempt, we can find a software-level solution. Consider having a single shared (lock) variable with an initial value of 0. When a thread wants to enter a critical area, it first checks whether the lock's value is 0. If the lock's value is 0, the process will set it to 1 and let the process enter the critical area. If the lock state is 1, the process waits until the value of the lock variable becomes 0. Therefore, the value of the lock variable is 0, which means that no threads enter the critical area. If 1, it means that there are processes in the critical area. After we modify the above figure, is the design method of

This is the 67th original article by Java builders. Previous article We dissect the nature of processes and threads, how processes and threads are implemented. In this article, we will explore how they communicate. The process tells me that threads don't want to live anymore. I d - DayDayNews

as shown below? Are there any mistakes? Suppose a process reads out the value of the lock variable and finds that it is 0 , and just before it sets it to 1 , another process schedules to run, reads out the lock's variable to 0 , and sets the lock's variable to 1. Then the first thread runs and sets the value of the lock variable to 1 again. At this time, two processes will run at the same time in the critical area.

This is the 67th original article by Java builders. Previous article We dissect the nature of processes and threads, how processes and threads are implemented. In this article, we will explore how they communicate. The process tells me that threads don't want to live anymore. I d - DayDayNews

Perhaps some readers can think so, check once before entering and check again in the key area you want to leave, isn’t it solved? In fact, this situation is not helpful, because other threads may still modify the value of the lock variable during the second check. In other words, this set-before-check is not an atomic operation, so there will also be race conditions.

Strict Polling Method

The third mutually exclusive method first throws out a piece of code. The program here is written in C language. The reason for C is that the operating system is generally written in C (occasionally C++) and basically does not use languages such as Java, Modula3 or Pascal. The underlying layer of native keywords in Java is also the source code written in C or C++. For writing operating systems, it is necessary to use a powerful, efficient, predictable and distinctive language like C, which is unpredictable for Java because it will run out of memory at critical moments and call the garbage collection mechanism to recycle memory when it is inappropriate. In C, this situation does not happen, and garbage collection memory is not actively called in C. For comparisons of C, C++, Java and other four languages, please refer to the code of

process 0

while(TRUE){while(turn != 0){/* Enter the critical area */critical_region();turn = 1;/* Leave the critical area */noncritical_region();}}

process 1

while(TRUE){while(turn != 1){critical_region();turn = 0;noncritical_region();}}

In the above code, the variable turn, the initial value is 0, is used to record which process is entering the critical area and check or update the shared memory. At the beginning, process 0 checks turn and finds that its value is 0, so it enters the critical area. Process 1 also found that its value is 0, so it keeps testing turn in a waiting loop to see when its value becomes 1.Continuously checking a variable until a value appears, this method is called busywaiting. Since this method wastes CPU time, this method should usually be avoided. Busy waiting can only be used if there is reason to think that the waiting time is very short. A lock used for busy waiting is called a spinlock.

When process 0 leaves the critical area, it sets the value of turn to 1 so that process 1 can enter its critical area. Assuming that process 1 quickly leaves the critical area, then both processes are outside the critical area at this time, and the value of turn is set to 0. Now process 0 quickly completes the entire loop, it exits the critical section and sets the value of turn to 1. At this time, the value of turn is 1, and both processes are executed outside their critical area.

Suddenly, process 0 ends the non-critical zone operation and returns to the beginning of the loop. However, at this time it cannot enter the critical area because the current value of turn is 1. At this time, process 1 is still busy with operations in non-critical areas. Process 0 can only continue while looping until process 1 changes the value of turn to 0. This shows that when one process executes much slower than another, taking turns to enter the critical zone is not a good way.

This situation violates the previous statement 3, that is, processes located outside the critical zone of must not block other processes , and process 0 is blocked by processes outside the critical zone. Due to violation of Article 3, it cannot be used as a good plan.

Peterson Solution

Dutch mathematician T.Dekker first proposed a software mutex algorithm that does not require strict rotation by combining lock variables with warning variables. Later, G.L.Peterson discovered a much simpler mutex algorithm. Its algorithm is as follows

#define FALSE 0#define TRUE1#define N 2 /* Number of processes */int turn; /* Who is the turn now */int interested[N]; /* All values are initialized to 0 (FALSE) */void enter_region(int process){ /* The process is 0 or 1 */int other; /* Another process number */other = 1 - process; /* Another process */interested[process] = TRUE; /* Indicates willing to enter the critical area */turn = process;while(turn == process&& interested[other] == true){}/* Empty loop */}void leave_region(int process){interested[process] == FALSE; /* Indicates leaving the critical area */}

Before using a shared variable (that is, entering its critical area), each process uses its respective process number 0 or 1 as a parameter to call enter_region, this function call will make the process wait until a safe critical area. After completing the operation on the shared variable, the process will call leave_region to indicate that the operation is completed and other processes are allowed to enter.

Now let’s see how this method works. At the beginning, no process was in the critical area, and now process 0 calls enter_region. It indicates that it wants to enter the critical section by setting the array element and setting turn to 0. Since process 1 does not want to enter the critical section, enter_region returns quickly. If the process now calls enter_region, process 1 will hang here until interested[0] becomes FALSE, which will only happen if process 0 calls leave_region to exit the critical section.

So the above discusses the case of sequential entry. Now let’s consider a situation where two processes call enter_region at the same time. They all save their own processes into turn, but only the process number saved in the last one is valid. The process number of the previous process is lost due to rewriting. If process 1 is last stored, then turn is 1. When both processes run to while, process 0 will not loop and enter the critical zone, while process 1 will loop infinitely and will not enter the critical zone until process 0 exits.

TSL instruction

Now let’s look at a solution that requires hardware help. Some computers, especially those designed as multiprocessors, will have the following instruction

TSL RX, lock 

is called test and set lock, which reads a memory word lock into the register RX, and then stores a non-zero value on that memory address.Reading and writing instructions can be guaranteed to be integrated, indivisible, and executed together. No other processors allow access to memory until this instruction is finished. The CPU executing the TSL instruction will lock the memory bus and use it to prohibit other CPUs from accessing memory before the instruction ends. The important point of

is that locking the memory bus is different from disabling interrupts. Disabling interrupts does not guarantee that one processor reads and writes to memory between reads and writes. That is, blocking interrupts on processor 1 has no effect on processor 2. The best way to keep processor 2 away from memory until processor 1 completes reading and writing is to lock the bus. This requires a special hardware (basically, a bus ensures that the bus is used by the processor that locks it, which is not available to other processors)

In order to use the TSL instruction, a shared variable lock is used to coordinate access to shared memory. When lock is 0, any process can set it to 1 using the TSL directive and read and write shared memory. When the operation is finished, the process uses the move directive to reset the value of lock to 0. How does the

instruction prevent two processes from entering the critical area at the same time? Here is the solution

enter_region: TSL REGISTER,LOCK | Copy the lock to register and set the lock to 1 CMP REGISTER,#0 | Is the lock 0?		JNE enter_region | If it is not zero, it means that the lock has been set, so loop RET | returns to the caller and enter the critical area leave_region: MOVE LOCK,#0 | Save 0 RET | Return to the caller 

We can see that the idea of this solution is very similar to Peterson's idea. Assume that there is an assembly language program with a total of 4 instructions below. The first instruction copies the original value of lock into the register and sets lock to 1, and then compares the original value with 0. If it is not zero, it means that the lock has been previously added, the program returns to start and tests again. After a period of time (can be long or short), the value becomes 0 (when the process currently in the critical area exits the critical area), the process returns and is locked at this time. It is also relatively simple to clear this lock. The program only needs to save 0 into lock, and there is no need for special synchronization instructions.

now has a very clear approach, that is, before the process enters the critical area, it will call enter_region to determine whether to loop. If the value of lock is 1, perform an infinite loop. If lock is 0, it will not enter the loop and enter the critical area. It calls leave_region when the process returns from the critical section, which sets lock to 0. Like all solutions based on critical area problems, the process must call enter_region and leave_region at the correct time for the solution to work.

also has an instruction that can replace TSL, which is XCHG, which atomically exchanges the contents of two locations, for example, a register and a memory word, the code is as follows

enter_region: MOVE REGISTER,#1 | Put 1 in the memory XCHG REGISTER,LOCK | Switch the contents of registers and lock variables CMP REGISTER,#0 | Is the lock 0?		JNE enter_region | If not 0 , the lock has been set, looping RET | Return to the caller and enter the critical area leave_region: MOVE LOCK,#0 | Store 0 in the lock RET | Return to the caller 

XCHG is essentially the same as the TSL solution. All Intel x86 CPUs use the XCHG instructions in underlying synchronization.

Sleep and wake up

The Peterson, TSL and XCHG solutions in the above solution are all correct, but they all have the disadvantages of being busy and waiting. The essentially the same solution is the same. First check whether it can enter the critical area. If not allowed, the process will wait in place until it is allowed.

This method not only wastes CPU time, but also may cause unexpected results. Consider that there are two processes on a computer, and these two processes have different priorities. H is a process with a relatively high priority and L is a process with a relatively low priority. The rule of process scheduling is to start running whenever the H process is in ready state.At some point, L is in the critical zone, at which time H becomes ready to run (for example, an I/O operation ends). Now H is going to start busy waiting, but since L will not be scheduled when H is ready and L will never have a chance to leave the critical area, H will become a dead loop, which is sometimes called the priority inversion problem.

Now let's look at the communication primitives between processes that block rather than waste CPU time before they are not allowed to enter critical areas, the easiest ones are sleep and wakeup. Sleep is a system call that can cause the caller to block, that is, this system call will be paused until other processes wake it up. The wakeup call has a parameter, that is, the process to be awakened. Another way is that both wakeup and sleep have a parameter, that is, sleep and wakeup need to match memory addresses.

Producer-Consumer Problem

As an example of these private primitives, let's consider the producer-consumer problem, also known as bounded-buffer problem. Two processes share a common fixed-size buffer. One of them is the producer, putting information into the buffer, and the other is the consumer, which will be taken out of the buffer. This problem can also be generalized to the problem of m producers and n consumers, but we only discuss the situation of one producer and one consumer here, which can simplify the implementation plan.

If the buffer queue is full, then a problem will occur when the producer still wants to write data to the buffer. Its solution is to let the producer sleep, that is, block the producer. Wait until the consumer takes one or more data items out of the buffer before waking it up. Similarly, when the consumer tries to fetch data from the buffer, but finds that the buffer is empty, the consumer will also sleep and block. Until the producer puts a new data into it. The logic of

sounds relatively simple, and this method also requires a variable called listening. This variable is used to monitor the data in the buffer. We tentatively set it as count. If the buffer stores up to N data items, the producer will determine whether count reaches N each time, otherwise the producer will put a data item into the buffer and increment the value of count. The logic of the consumer is also very similar: first test whether the value of count is 0. If 0, the consumer will sleep and block, otherwise the data will be taken out from the buffer and the number of counts will be decremented. Each process will also check whether other threads should be awakened. If they should be awakened, then wake up the thread. Below is the code of the producer consumer

#define N 100 /* Number of buffer slots */int count = 0 /* Number of buffer data */// Producer void producer(void){int item; while(TRUE){ /* Infinite loop */item = producer_item() /* Generate next data */if(count == N){sleep(); /* If the buffer is full, it will block */}insert_item(item); /* Put the current data in the buffer */count = count + 1; /* Increase the number of buffer count */if(count == 1){wakeup(consumer); /* Is the buffer empty? */}}}// Consumer void consumer(void){int item;while(TRUE){ /* Infinite loop */ if(count == 0){ /* If the buffer is empty, it will block */sleep();} item = remove_item(); /* Take out a data from the buffer */count = count - 1 /* Decrement the number of counts in the buffer by one */if(count == N - 1){ /* Is the buffer full? */wakeup(producer); }consumer_item(item); /* Print data items */}}

In order to describe system calls such as sleep and wakeup in C language, we will represent them in the form of library function calls. They are not part of the C standard library, but can be used on any system that actually has these system calls. The insert_item and remove_item that are not implemented in the code are used to record the data items being put into the buffer and the data being taken out from the buffer.

Now let's go back to the producer-consumer issue, there will be a race condition in the above code because the variable count is exposed to the public's view.It is possible that the following situation: the buffer is empty, and the consumer just reads the value of count and finds that it is 0. At this point the scheduler decides to suspend the consumer and start the run producer. The producer produces a piece of data and places it in the buffer, then increases the value of count and notices that its value is 1. Since count is 0, the consumer must be in sleep state, so the producer calls wakeup to wake the consumer. However, the consumer does not logically sleep at this time, so the wakeup signal will be lost. When the consumer starts the next time it looks at the count value it reads before, finds that its value is 0, and then sleeps here. Soon after that the producer will fill the entire buffer, which will block after that, so that both processes will sleep forever.

The essence of the above problem is that wakes up a process that has not yet been in sleep state, which will cause to be lost. If it's not lost, everything is normal. A quick way to solve the above problem is to add a wakeup waiting bit. When a wakeup signal is sent to a process that is still awake, the position is 1. After that, when the process tries to sleep, if the wake-up wait bit is 1, the bit is cleared and the process remains awake.

However, when there are many processes, you can say that you wake up the wait bit by increasing the number of wake bits, so there are 2, 4, 6, and 8 wake-up wait bits, but it does not fundamentally solve the problem.

semaphore

semaphore is a method proposed by E.W.Dijkstra in 1965, which uses a shaping variable to accumulate wake-up times for later use. In his view, there is a new type of variable called semaphore. The value of a semaphore can be 0, or any positive number. 0 means that no wake-up is required, and any positive number means the number of wake-up times.

Dijkstra proposed that there are two operations for semaphores. Now, down and up are usually used (can be represented by sleep and wakeup respectively). The operation of this directive will check whether the value is greater than 0. If it is greater than 0, the value is reduced by 1; if it is 0, the process will sleep, and the down operation will continue to be executed. Checking the numerical values, modifying the magnitude values, and possible sleep operations are all completed by a single, indivisible atomic action. This ensures that once the semaphore operation begins, no other process can access the semaphore until the operation is completed or blocked. This atomicity is absolutely essential to solve synchronization problems and avoid competition.

"

"

atomic operation refers to the fact that in many other fields of computer science, a group of related operations are all executed without interruption or not at all. The

up operation causes the value of the semaphore + 1. If one or more processes sleep on the semaphore and cannot complete a previous down operation, the system selects one of them and allows the process to complete the down operation. Therefore, after performing an up operation on the semaphore on which a process sleeps, the value of the semaphore is still 0, but one less process sleeping on it. Increasing the semaphore by 1 is also inseparable from waking up a process. There will be no process blocking by executing an up, just as in the previous model, there will be no process blocking by executing wakeup.

uses semaphore to solve producer - consumer problem

uses semaphore to solve the missing wakeup problem, the code is as follows

#define N 100 /* Define the number of buffer slots */typedef int semaphore; /* Semaphore is a special int */semaphore mutex = 1; /* Control the access of key areas */semaphore empty = N; /* Statistics buffer Number of empty slots */semaphore full = 0; /* Statistics buffer Number of full slots */void producer(void){int item;while(TRUE){ /* The constant of TRUE is 1 */item = producer_item(); /* produce some data placed in the buffer */down(&empty); /* reduce the number of empty slots by 1*/down(&mutex); /* Enter the critical area*/insert_item(item); /* Put the data into the buffer */up(&mutex); /* Leave the critical area */up(&full); /* Remove the buffer full slot number + 1 */}}void consumer(void){int item;while(TRUE){ /* Infinite loop */down(&full); /* Number of full slots in the buffer - 1 */down(&mutex); /* Enter the buffer */item = remove_item(); /* Extract data from the buffer */up(&mutex); /* Leave the critical area */up(&empty); /* Number of empty slots + 1 */consume_item(item); /* Process data */}}

In order to ensure that the semaphore can work correctly, the most important thing is to implement it in an indivisible way. Usually, up and down are implemented as system calls. Moreover, the operating system only needs to temporarily block all interrupts when performing the following operations: checks semaphores, updates, and makes the process sleep if necessary. Since these operations require very few instructions, interrupts have no impact. If multiple CPUs are used, the semaphore should be locked for protection. Use the TSL or XCHG instructions to ensure that only one CPU operates on the semaphore at the same time.

uses TSL or XCHG to prevent several CPUs from accessing a semaphore at the same time, which is completely different from the producer or consumer using busy waiting to wait for other buffers to be freed or filled. The former operation takes only a few milliseconds, while the producer or consumer may take any time.

The above solution uses three semaphores: one is called full, which records the number of buffer slots that are full, the other is called empty, and the other is called mutex, which ensures that the producer and consumer do not enter the buffer at the same time. Full is initialized to 0, empty is initialized to the number of slots in the buffer, and mutex is initialized to 1. Semaphores are initialized to 1 and used by two or more processes to ensure that only one of them can enter the critical region at the same time is called binary semaphores. If each process performs a down operation before entering a critical region and an up operation after leaving the critical region, you can ensure mutually exclusive.

Now we have a good guarantee of inter-process primitives. Then let's look at the order of interrupts to ensure that

  1. hardware pushes into the stack program counter and other
  2. hardware loads new program counter from interrupt vector
  3. assembly language process saves the value of register
  4. assembly language process sets new stack
  5. C interrupt server running (typical read and cache write)
  6. scheduler decides which of the following programs runs first
  7. C process returns to assembly code
  8. assembly language process starts running the new current process

In a system using semaphores, the natural way to hide interrupts is to have each I/O device equipped with a semaphore, which semaphore is initially set to 0. After the I/O device is started, the interrupt handler immediately performs a down operation on the associated signal, and the process is immediately blocked. When an interrupt enters, the interrupt handler then performs an up operation on the relevant semaphore, which can restore the blocked process to run. In the above interrupt processing step, step 5 C, interrupt server operation is an up operation performed by the interrupt handler on the semaphore, so in step 6, the operating system can execute the device driver.Of course, if several processes are already in the ready state, the scheduler may choose to run a more important process next, and we will discuss the scheduling algorithm later. The code above

actually uses semaphores in two different ways, and the difference between these two semaphores is also very important. The mutex semaphore is used for mutual exclusion. It is used to ensure that only one process can read and write buffers and related variables at any time. Mutex is an operation necessary to avoid process chaos.

Another semaphore is about synchronization. The full and empty semaphores are used to ensure that events occur or do not occur. In this case, they ensure that the producer stops running when the buffer is full; and the consumer stops running when the buffer is empty. The use of these two semaphores is different from mutex.

Mutex

If the counting ability of the semaphore is not required, a simple version of the semaphore can be used, called mutex (mutex). The advantage of mutex is that it remains mutexed in some shared resources and a piece of code. Since the implementation of mutex is both simple and effective, this makes mutexes very useful when implementing user space thread packages.

mutex is a shared variable in one of two states: unlocked and locked. In this way, only one binary bit is needed to represent it, but in general, it is usually represented by an integer. 0 means unlocking, all other values represent locking, and a value larger than 1 means the number of times the locking is added.

mutex uses two procedures. When a thread (or process) needs to access a critical area, mutex_lock will be called to lock. If the mutex is currently in unlocked (indicating that the critical area is available), the call is successful and the calling thread is free to enter the critical area.

On the other hand, if the mutex mutex is locked, the calling thread will block until the thread in the critical area has completed execution and mutex_unlock is called. If multiple threads block on the mutex mutex, one thread will be randomly selected and allowed to obtain the lock.

This is the 67th original article by Java builders. Previous article We dissect the nature of processes and threads, how processes and threads are implemented. In this article, we will explore how they communicate. The process tells me that threads don't want to live anymore. I d - DayDayNews

Since mutex mutex is very simple, they can be easily implemented in user space as long as there are TSL or XCHG instructions. The mutex_lock and mutex_unlock codes for user-level thread packages are as follows, and the essence of XCHG is the same.

mutex_lock: TSL REGISTER,MUTEX | Copy the mutex semaphore to the register and set the mutex semaphore to 1 CMP REGISTER,#0 | Is the mutex semaphore 0?			JZE ok | If the mutex semaphore is 0, it is unlocked, so the code that returns CALL thread_yield | The mutex signal is being used; schedule other threads JMP mutex_lock | Try again ok: RET | Return to the caller and enter the critical area mutex_unlcok: MOVE MUTEX,#0 | Set mutex to 0 RET | The code that returns the caller 

mutex_lock is very similar to the code above enter_region. We can compare and see the biggest difference in the above code of

This is the 67th original article by Java builders. Previous article We dissect the nature of processes and threads, how processes and threads are implemented. In this article, we will explore how they communicate. The process tells me that threads don't want to live anymore. I d - DayDayNews

?

  • Based on our analysis of TSL above, we know that if TSL judges that the process that has not entered the critical area will undergo infinite loops to acquire the lock, and in the process of TSL, if mutex is in use, other threads will be scheduled for processing. So the biggest difference above is actually the processing after judging mutex/TSL.
  • In (user) threads, the situation is different because there is no clock to stop threads that have been running for too long. The result is that the thread trying to obtain the lock by busy waiting will loop forever and will never get the lock, because the running thread will not allow other threads to run and release the lock, and other threads will have no chance to obtain the lock at all. When the latter fails to acquire the lock, it calls thread_yield to abandon the CPU to another thread. As a result, there will be no busy waiting. The next time that thread runs, it tests the lock again.

is the difference between enter_region and mutex_lock. Since thread_yield is just a user-space thread scheduling, it runs very quickly.In this way, neither mutex_lock nor mutex_unlock requires any kernel calls. By using these processes, user threads can fully realize synchronization in user space, which only requires a small amount of synchronization.

The mutex we described above is actually a set of instructions in the calling framework. From a software perspective, there are always more features and synchronization primitives needed. For example, sometimes a thread package provides a call to mutex_trylock, which attempts to acquire a lock or return an error code, but does not perform a lock operation. This gives the calling thread a flexibility to decide what to do next, whether to use an alternative or wait.

Futexes

As parallel increases, effective synchronization and locking are very important for performance. If the process wait time is short, then the spin lock is very effective; but if the wait time is relatively long, then this will waste CPU cycles. If there are many processes, blocking this process and only letting the kernel unblock the lock is a more efficient way. Unfortunately, this approach can also lead to another problem: it can run well when the process competition is frequent, but the kernel switching will consume a lot of money when the competition is not very fierce, and more difficult, predicting the number of competitions for locks is less easy.

has an interesting solution to combine the advantages of the two and propose a new idea called futex or fast user space mutex. Doesn’t it sound interesting?

This is the 67th original article by Java builders. Previous article We dissect the nature of processes and threads, how processes and threads are implemented. In this article, we will explore how they communicate. The process tells me that threads don't want to live anymore. I d - DayDayNews

futex is a feature in Linux that implements basic locking (much like a mutex) and avoids getting stuck in the kernel, because the overhead of switching kernels is very high, so doing this can greatly improve performance. futex consists of two parts: kernel service and user library . The kernel service provides a wait queue that allows multiple processes to wait queue on the lock. They won't run unless the kernel explicitly unblocks them.

This is the 67th original article by Java builders. Previous article We dissect the nature of processes and threads, how processes and threads are implemented. In this article, we will explore how they communicate. The process tells me that threads don't want to live anymore. I d - DayDayNews

For a process, putting it into the waiting queue requires expensive system calls, and this method should be avoided. In the absence of competition, futex can work directly in user space. These processes share a 32-bit integer as a common lock variable. Assuming that the lock is initialized to 1, we believe that the lock has been released at this time. Threads preempt locks by performing atomic operations and decrement and test. decrement and set is an atomic function in Linux, consisting of an inline assembly wrapped in C functions and defined in a header file. Next, the thread will check the results to see if the lock has been released. If the lock is not locked now, then our thread can successfully preempt the lock. However, if the lock is held by other threads, the thread that preempts the lock has to wait. In this case, the futex library does not spin, but uses a system call to put the thread in the waiting queue in the kernel. In this way, the overhead of switching to the kernel is reasonable, because threads can block at any time. When the thread completes the lock work, it uses an atomic increase and test to release the lock, and checks the results to see if any processes are still blocked on the kernel waiting queue. If anything, it notifies the kernel that it can unblock one or more processes in the waiting queue. If there is no lock competition, the kernel does not need to compete. The mutex in

Pthreads

Pthreads provides some functions for synchronizing threads. The most basic mechanism is to use mutex variables that can be locked and unlocked to protect each critical area. The thread that wants to enter the critical area must first try to get the mutex. If mutex is not locked, the thread can enter immediately and the mutex can be automatically locked, thus preventing other threads from entering. If mutex is locked, the calling thread will block until mutex is unlocked. If multiple threads wait on the same mutex, when the mutex is unlocked, only one thread can enter and re-lock. These locks are not necessary and programmers need to use them correctly.

Below is a function call related to mutexes

This is the 67th original article by Java builders. Previous article We dissect the nature of processes and threads, how processes and threads are implemented. In this article, we will explore how they communicate. The process tells me that threads don't want to live anymore. I d - DayDayNews

As we imagined, mutex can be created and destroyed. The two roles are Phread_mutex_init and Pthread_mutex_destroy. mutex can also be locked through Pthread_mutex_lock, which will block the caller if the mutex has been locked. There is also a call to Pthread_mutex_trylock to try to lock the thread. When mutex has been locked, an error code will be returned instead of blocking the caller. This call allows threads to be busy effectively, etc. Finally, Pthread_mutex_unlock unlocks mutex and releases a waiting thread.

In addition to mutexes, Pthreads also provides a second synchronization mechanism: condition variables. mutex can be very good at allowing or blocking access to critical areas. Condition variables allow threads to block because certain conditions are not met. In most cases, the two methods are used together. Next, we will further study the relationship between threads, mutexes, and conditional variables.

Let’s re-understand the producer and consumer issues below: a thread puts things in a buffer and takes them out by another thread. If the producer finds that the buffer has no empty slots to use, the producer thread will block until there is a thread to use. Producers use mutex to perform atomicity checks so that they are not disturbed by other threads. But when the buffer is found to be full, the producer needs a way to block itself and wake it up later. This is the work done by the condition variable.

Below are some of the most important pthread calls related to condition variables.

This is the 67th original article by Java builders. Previous article We dissect the nature of processes and threads, how processes and threads are implemented. In this article, we will explore how they communicate. The process tells me that threads don't want to live anymore. I d - DayDayNews

The table above gives some calls to create and destroy condition variables. The main properties on the condition variable are Pthread_cond_wait and Pthread_cond_signal. The former blocks the calling thread until the other threads send out a signal (using the latter). Blocking threads usually need to wait for wake-up signals to free up resources or perform certain other activities. Only in this way can the blocking thread continue to work. Conditional variables allow waiting and blocking atomicity processes. Pthread_cond_broadcast is used to wake up multiple blocking threads that need to wait for the signal to wake up.

"

It should be noted that the condition variable (unlike a semaphore) will not exist in memory. If a semaphore is passed to a condition variable that has no thread waiting for, then the signal will be lost. Note that

Below is an example of using mutex and condition variables

#include stdio.h#include pthread.h#define MAX 10000000000 /* Quantity to be produced */pthread_mutex_t the_mutex;pthread_cond_t condc,condp; /* Use semaphore */int buffer = 0;void *producer(void *ptr){ /* Production data */int i;for(int i = 0;i = MAX;i++){pthread_mutex_lock(&the_mutex); /* Buffer exclusive access, that is, use mutex to obtain locks */while(buffer != 0){pthread_cond_wait(&condp,&the_mutex);}buffer = i; /* Put them in the buffer */pthread_cond_signal(&condc); /* Wake up the consumer */pthread_mutex_unlock(&the_mutex); /* Release the buffer */ }pthread_exit(0);}void *consumer(void *ptr){ /* Consumption data */int i;for(int i = 0;i = MAX;i++){pthread_mutex_lock(&the_mutex); /* Buffer exclusive access, that is, use mutex to obtain locks */while(buffer == 0){pthread_cond_wait(&condc,&the_mutex);}buffer = 0; /* Take them out of the buffer */pthread_cond_signal(&condp); /* Wake up the producer */pthread_mutex_unlock(&the_mutex); /* Free the buffer */}pthread_exit(0);} 

Pipe

In order to write more accurate programs, Brinch Hansen and Hoare proposed a more advanced synchronization primitive called monitor. The proposals of the two of them are slightly different, and you can know from the description below. A management is a collection of programs, variables and data structures, etc., which form a special module or package. Processes can call programs in the management program whenever they need it, but they cannot access data structures and programs from outside the management program.Below is a simple and abstract approach similar to that shown in Pascal. It cannot be described in C language because management is a language concept and C language does not support management.

monitor example integer i; condition c; procedure producer(); . . . end; procedure consumer(); . end; end monitor; 

pipe processes have a very important feature, that is, there can only be one active process in the pipe process at any time, which makes the pipe processes convenient to implement mutually exclusive operations. The program is a feature of the programming language, so the compiler knows their particularity, so it can use a different method than other procedure calls to the program to be handled. Normally, when a process calls a program in a management program, the first few instructions of the program will check whether there are other active processes in the management program. If so, the calling process will be suspended and will not wake it up until another process leaves the management process. If no active process is using the management process, then the calling process can enter. The mutex that

enters the pipeline is the responsibility of the compiler, but a common approach is to use mutex and binary semaphore. Since the compiler, not the programmer is operating, the chance of errors is greatly reduced. At any time, programmers who write management programs need not care about how the compiler handles it. He only needs to know how to convert all critical areas into a pipeline process. There will never be two processes that execute code in the critical section at the same time.

Even though the pipeline provides a simple way to achieve mutual exclusion, this seems to us not enough. Because we also need a blocked process that cannot be executed. In the producer-consumer problem, it is easy to put tests on buffer full and buffer empty in the pipeline program, but how should producers block when they find that the buffer is full? The solution to

is to introduce condition variables and two related operations wait and signal. When a manipulator finds that it cannot run (for example, the producer finds that the buffer is full), it performs a wait operation on a condition variable (for example, full). This operation causes the call process to block and also calls another process that was previously waiting outside the management process to the management process. In the previous pthread we have discussed the implementation details of conditional variables. Another process, such as a consumer, can wake up a blocking calling process by executing signal.

"

Brinch Hansen and Hoare are different in wake-up process. Hoare recommends that the newly awakened process continue to run; and suspend another process. Brinch Hansen suggests that the process executing signal must exit the management process. Here we adopt the suggestion of Brinch Hansen because it is conceptually simpler and easier to implement.

If several processes are waiting on a condition variable, after performing signal operations on the condition, the system scheduler can only select one of the processes to resume running.

By the way, there is a third method not proposed by the two professors above. Its theory is to enable the execution of signal after performing signal operations on the condition.

By the way, there is also a third method that the two professors above did not propose. Its theory is to enable the execution of signal. The process continues to run, and when this process exits the pipeline, other processes can only enter the pipeline. The

condition variable is not a counter. The condition variable cannot accumulate signals like a semaphore for later use. Therefore, if a signal is sent to a condition variable, but there is no waiting process on the condition variable, the signal will be lost. That is, the wait operation must execute before signal.

Below is a solution to the producer-consumer problem implemented through a management program using the Pascal language

monitor ProducerConsumer condition full,empty; integer count; procedure insert(item:integer); begin if count = N then wait(full); insert_item(item); count := count + 1; if count = 1 then signal(empty); end; function remove:integer; begin if count = 0 then wait(empty); remove = remove_item; count := count - 1; if count = N - 1 then signal(full); end; count := 0;end monitor;procedure producer; begin while true dobegin item = produce_item; ProducerConsumer.insert(item); endend;procedure consumer; begin while true do begin item = ProducerConsumer.remove; consume_item(item); endend;

Readers may think that wait and signal operations look like the aforementioned sleep and wakeup, and the latter has serious race conditions. They are indeed very similar, but there is a key difference: sleep and wakeup fail because when one process wants to sleep, another process tries to wake it up. This will not happen if you use the management system. This is guaranteed by the automatic mutual exclusion of the manipulator, and if the producer during the manipulator finds that the buffer is full, it will be able to complete the wait operation without worrying that the scheduler may switch to the consumer before the wait is completed. Even, consumers will not be allowed to enter the management process until the wait execution is completed and the producer is marked as unrunable.

Although the class Pascal is an imaginary language, it still has some real programming language support, such as Java (it is finally the turn of big Java). Java can support management. It is an object-oriented language that supports user-level threads and allows methods to be divided into classes. Just add the synchronized keyword to the method. Java can ensure that once a thread executes the method, other threads are not allowed to execute any synchronized methods in the object. Without the keyword synchronized, there is no guarantee that there is no cross-execution.

Below is the producer-consumer problem solved by Java using pipe processes

public class ProducerConsumer {static final int N = 100; // Define the length of the buffer size static Producer p = new Producer(); // Initialize a new producer thread static Consumer c = new Consumer(); // Initialize a new consumer thread static Our_monitor mon = new Our_monitor(); // Initialize a pipe process static class Producer extends Thread{public void run(){ // run Includes thread code int item; while(true){ // Producer loop item = produce_item();mon.insert(item);}}private int produce_item(){...} // Production code}static class consumer extends Thread {public void run( ) { // run contains thread code int item; while(true){item = mon.remove(); consume_item(item);}}private int produce_item(){...} // Consumption code}static class Our_monitor { // This is a public int buffer[] = new int[N];private int count = 0,lo = 0,hi = 0; // Counter and index private synchronized void insert(int val){if(count == N){go_to_sleep(); // If the buffer is full, enter sleep} buffer[hi] = val; // Insert content into the buffer hi = (hi + 1) % N; // until the next slot is found count = count + 1; // The number in the buffer increases by 1if(count == 1){notify(); // If the consumer sleeps, wake up}}private synchronized void remove(int val){int val; if(count === 0){go_to_sleep(); // The buffer is empty, enter sleep}val = buffer[lo]; // Extract data from the buffer lo = (lo + 1) % N; // Set the slot count of the data item to be retrieved = count - 1; // The number of data items in the buffer is reduced by 1 if(count = N - 1){notify(); // If the producer sleeps, wake it up}return val;}private void go_to_sleep() {try{wait( );}catch(Interr updatedExceptionexc) {};}}}

The above code mainly designs four classes, external class (outer class) ProducerConsumer Creates and starts two threads, p and c.The second and third classes Producer and Consumer contain producer and consumer codes, respectively. Finally, Our_monitor is a pipe that has two synchronous threads for inserting and fetching data in a shared buffer.

In all previous examples, the producer and consumer threads are functionally the same as them. The producer has an infinite loop that produces data and puts the data into a public buffer; the consumer also has an equivalent infinite loop that is used to fetch data from the buffer and completes a series of work. The more interesting thing in the

program is Our_monitor, which contains buffers, management variables and two synchronization methods. When the producer is active within insert, it ensures that the consumer cannot run in the remove method, thus ensuring the security of updating variables and buffers without worrying about race conditions. Variable count The amount of data recorded in the buffer. The variable lo is the sequence number of the buffer slot, indicating the next data item to be retrieved. Similarly, hi is the next data item number to be placed in the buffer. Allow lo = hi, which means there are 0 or N data in the buffer. The synchronization method in

Java is fundamentally different from other classical programs: Java does not have embedded condition variables. However, Java provides wait and notify equivalents to sleep and wakeup, respectively.

is automatically mutually exclusive through the critical area, and the pipeline is easier to ensure the correctness of parallel programming than semaphores. However, management processes also have disadvantages. As we mentioned earlier, management processes are a concept of programming languages. The compiler must identify management processes and ensure that they are mutually exclusive in some way. C, Pascal and most other programming languages do not have , so the compiler cannot be relied on to obey mutually exclusive rules. Another problem with

is that these mechanisms are designed to solve mutual exclusion problems on one or more CPUs that access shared memory. Contest can be avoided by placing semaphores in shared memory and protecting them with TSL or XCHG instructions. But if it is a distributed system that may have multiple CPUs at the same time, and each CPU has its own private memory, and they are connected through the network, then these primitives will be invalid. Because the semaphore is too low-level and the management process cannot be used outside of a few programming languages, other methods are needed.

Message passing

The other method mentioned above is message passing (messaage passing). This method of inter-process communication uses two primitives send and receive, which are like semaphores rather than manipulations, and are system calls rather than language level. The example is as follows:

send(destination, &message);receive(source, &message);

send method is used to send a message to a given destination and receive a message from a given source. If there is no message, the recipient may be blocked until a message is accepted or returned with an error code. Key points of design of

messaging system

messaging system now faces many problems and design difficulties that are not involved in semaphores and pipelines, especially for communication conditions on different machines in the network. For example, messages may be lost by the network. To prevent message loss, the sender and the receiver can agree that once the message is received, the receiver immediately sends back a special acknowledgement message. If the sender does not receive acknowledgement within a period of time, resend the message.

now considers that the message itself is correctly received and returns to the sent confirmation message is lost. The sender will resend the message so that the recipient will receive the same message twice.

This is the 67th original article by Java builders. Previous article We dissect the nature of processes and threads, how processes and threads are implemented. In this article, we will explore how they communicate. The process tells me that threads don't want to live anymore. I d - DayDayNews

For the receiver, it is very important to distinguish between a new message and a resent old message. This problem is usually solved by embedding a continuous sequence number in each original message. If the recipient receives a message that has the same sequence number as the previous message, he knows that the message is duplicate and can be ignored.

messaging system must also deal with the issue of how to name processes so that the process can be clearly specified in the sending or receiving calls.Authentication is also a problem, such as how the client knows that it is communicating with a real file server, and the information from the sender to the receiver may be tampered with by an intermediary.

solves producer-consumer problems with messaging

Now we consider how to solve producer-consumer problems using messaging, rather than shared cache. The following is a solution

#define N 100 /* Number of slots in buffer */void producer(void){int item;message m; /* Number of slots in buffer */while(TRUE){item = produce_item(); /* Generate data placed in the buffer */receive(consumer,&m); /* Wait for the consumer to send an empty buffer */build_message(&m,item); /* Create a message to be sent */send(consumer,&m); /* Send to the consumer */}}void consumer(void){int item,i;message m;for(int i = 0;i N;i++){ /* Loop N times */send(producer,&m); /* Send N buffers */}while(TRUE){receive(producer,&m); /* Accept messages containing data */ item = extract_item(&m); /* Extract data from messages */send(producer,&m); /* Send empty buffer back to producer */consume_item(item); /* Process data */}}

Assume that all messages have the same size and are automatically buffered by the operating system when the sent message has not been received. N messages are used in this solution, which is similar to N slots of a shared memory buffer. The consumer first sends N empty messages to the producer. When the producer passes a data item to the consumer, it takes an empty message and returns a message filled with the content. In this way, the total number of messages in the system remains unchanged, so messages can be stored in memory with a predetermined number of messages.

If the producer is faster than the consumer, all messages will eventually be filled, waiting for the consumer, the producer will be blocked, waiting for an empty message to be returned. If the consumer is fast, the situation will be the opposite: all messages are empty, waiting for the producer to fill, and the consumer will be blocked to wait for a filled message. There are many variations of the

message delivery method. Let’s first introduce how to address messages.

  • One way is to assign a unique address to each process, so that the message is addressed by the process's address. Another way to
  • is to introduce a new data structure, called mailbox. The mailbox is a data structure used to buffer certain data. There are also many ways to set messages in the mailbox. The typical method is to determine the number of messages when the mailbox is created. When using a mailbox, the address parameters called in send and receive are the address of the mailbox, not the address of the process. When a process tries to send a message to a full mailbox, it will be suspended until a message in the mailbox is taken, making address space for new messages.

Barrier

The last synchronization mechanism is prepared for the producer-consumer situation in process groups rather than inter-process. Several stages are divided in some applications and it is stipulated that unless all processes are ready to work on the next stage, no process can enter the next stage, and this behavior can be achieved by installing a barrier at the end of each stage. When a process reaches a barrier, it is blocked by the barrier until all barriers arrive. The barrier can be used for a set of processes to synchronize, as shown in the figure below

This is the 67th original article by Java builders. Previous article We dissect the nature of processes and threads, how processes and threads are implemented. In this article, we will explore how they communicate. The process tells me that threads don't want to live anymore. I d - DayDayNews

In the figure above, we can see that there are four processes close to the barrier, which means that each process is performing operations, but has not reached the end of each stage yet. After a period of time, the three processes A, B, and D have all reached the barrier, and their respective processes are suspended, but they cannot enter the next stage at this time because process B has not been executed yet. As a result, this process group can enter the next stage only after the last C reaches the barrier.

Avoid locks: Read-Copy-Update

The fastest lock is that there is no lock at all. The question is whether we allow access to concurrent read and write of shared data structures without locks. The answer is of course no.Suppose that process A is sorting an array of numbers, and process B is calculating its average value, and at this time you move A, it will cause B to read the repeated values many times, and some values have not been encountered at all.

However, in some cases we can allow write operations to update the data structure even if there are other processes in use. The trick is to make sure that each read operation either reads the old version or reads the new version, for example, in the tree below

This is the 67th original article by Java builders. Previous article We dissect the nature of processes and threads, how processes and threads are implemented. In this article, we will explore how they communicate. The process tells me that threads don't want to live anymore. I d - DayDayNews

above the tree, read operation traverses the entire tree from the root to the leaf. After adding a new node X, in order to achieve this, we want to make this node "just correct" before it is visible in the tree: we initialize all values in node X, including its child node pointers. Then, through atomic write operations, X is called the child node of A. All read operations will not read the inconsistent version

This is the 67th original article by Java builders. Previous article We dissect the nature of processes and threads, how processes and threads are implemented. In this article, we will explore how they communicate. The process tells me that threads don't want to live anymore. I d - DayDayNews

In the figure above, we will then remove B and D. First, point the left child node pointer of A to C. All read operations originally in A will subsequently read node C, and will never read B and D. That is, they will only read the new version of the data. Likewise, all current read operations in B and D will continue to follow the original data structure pointer and read the old version of the data. All operations work correctly, we don't need to lock anything. The main reason why B and D can be removed without locking the data is read-copy-update (RCU), which separates the removal and redistribution processes during the update process.

Previous selection of

million words long articles will take you to restore the concepts of operating systems such as processes and threads

, and you have never heard of it!

What is an operating system? | Tactical backward

Do you want to ask me about the application layer? I'll just talk to you

article reference:

"Modern Operating System"

"Modern Operating System" forth edition

https://www.encyclopedia.com/computing/news-wires-white-papers-and-books/interactive-systems

https://j00ru.vexillium.org/syscalls/nt/32/

https://www.bottomupcs.com/process_hierarchy.xhtmlhtml

https://en.wikipedia.org/wiki/Runtime_system

https://en.wikipedia.org/wiki/Execution_model

hotcomm Category Latest News