Linux basics 02-inter-process communication

Preface

The user address space of each process is independent and generally cannot access each other, but the kernel space is shared by each process, so communication between processes must pass through the kernel.

The Linux kernel provides a lot of inter-process communication mechanisms. Let’s take a look at them.

1. Piping

If you have learned Linux commands, you must be familiar with |the vertical line " ".

$ ps auxf | grep mysql

The |vertical line " " in the above command line is a pipe . Its function is to use ps auxfthe output of the previous command ( grep mysql) as the input of the next command ( ). From this function description, it can be seen that the data transmitted by the pipe is one-way , If we want to communicate with each other, we need to create two pipes.

At the same time, we learned that the above kind of pipe has no name, so |the pipe indicated by " " is called an anonymous pipe , and it is destroyed when it is used up.

Another type of pipe is named pipe , also called  FIFObecause data is first-in-first-out transmission method.

Before using the named pipe, you need mkfifo to create it through a  command and specify the pipe name:

$ mkfifo myPipe

myPipe is the name of this pipe, based on the Linux concept that everything is a file, so the pipe also exists as a file. We can use ls to see that the type of this file is p, which means pipe (pipe):

$ ls -l
prw-r--r--. 1 root    root         0 Jul 17 02:45 myPipe

Next, we write data to the myPipe pipeline:

$ echo "hello" > myPipe  // 将数据写进管道
                         // 停住了 ...

After you operate, you will find that the command stops here after execution. This is because the content in the pipeline has not been read. Only when the data in the pipeline has been read, the command can exit normally.

So, we execute another command to read the data in this pipe:

$ cat < myPipe  // 读取管道里的数据
hello

As you can see, the content in the pipeline is read out and printed on the terminal. On the other hand, the echo command also exits normally.

We can see that the pipeline communication method is inefficient and not suitable for frequent data exchange between processes . Of course, its benefits are naturally simple, and it is easy for us to know that the data in the pipeline has been read by another process.

How is the pipeline created, and what is the rationale behind it?

The creation of an anonymous pipe requires the following system call:

int pipe(int fd[2])

This means that an anonymous pipe is created and two descriptors are returned, one is the read-side descriptor  fd[0]of the pipe, and the other is the write-side descriptor of the pipe  fd[1]. Note that this anonymous pipe is a special file that only exists in memory, not in the file system.

In fact, the so-called pipeline is a string of caches in the kernel . The data written from one section of the pipeline is actually cached in the kernel, and the other end reads, that is, the data is read from the kernel. In addition, the data transmitted by the pipe is an unformatted stream and the size is limited.

Seeing this, you may have questions. These two descriptors are in the same process and do not play a role in inter-process communication. How can the pipeline span the two processes?

We can use to  fork create a child process, the created child process will copy the file descriptor of the parent process , so that the two processes have two "  fd[0] ANDs  fd[1]", and the two processes can write and read the same through their fd A pipeline file realizes cross-process communication.

The pipe can only be written at one end and read at the other end, so the above mode is easy to cause confusion, because the parent process and the child process can both write and read at the same time. So, in order to avoid this situation, the usual approach is:

  • The parent process closes the read fd[0], and only keeps the written fd[1];
  • The child process closes the written fd[1], and only keeps the read fd[0];

So if two-way communication is required, two pipes should be created.

Up to this point, we have only analyzed the use of pipes for communication between the parent process and the child process, but this is not the case in our shell.

When executing A | Bcommands in the shell  , process A and process B are both child processes created by the shell. There is no parent-child relationship between A and B, and their parent processes are both shells.

So, in the shell, |connecting multiple commands together through the " "anonymous pipe, actually creates multiple child processes, so when we write shell scripts, we can use one pipe to get things done, don’t use one more pipe. , This can reduce the system overhead of creating a child process.

We can know that for an anonymous pipe, its communication range is a process with a parent-child relationship . Because the pipeline has no entity, that is, there is no pipeline file, only the parent process fd file descriptor can be copied through fork to achieve the purpose of communication.

In addition, for named pipes, it can communicate with each other among unrelated processes . Because of the command pipeline, a device file of type pipeline is created in advance. As long as the device file is used in the process, it can communicate with each other.

Regardless of whether it is an anonymous pipe or a named pipe, the data written by the process is cached in the kernel. When another process reads the data, it is naturally obtained from the kernel. At the same time, the communication data follows the first-in first-out principle and does not support lseek and the like. File positioning operation.

2. Message Queue

As mentioned earlier, the communication method of the pipeline is inefficient, so the pipeline is not suitable for frequent exchange of data between processes.

For this problem, the communication mode of the message queue can be solved. For example, if process A wants to send a message to process B, process A can return the data normally after putting the data in the corresponding message queue, and process B can read the data when needed. The same is true for process B to send messages to process A.

Next, the message queue is a linked list of messages stored in the kernel . When sending data, it will be divided into independent data units, that is, the message body (data block). The message body is a user-defined data type and the sender of the message. The data type of the message body must be agreed upon with the receiver, so each message body is a storage block of a fixed size, unlike a pipeline which is unformatted byte stream data. If the process reads the message body from the message queue, the kernel will delete the message body.

The message queue life cycle follows the kernel. If the message queue is not released or the operating system is not shut down, the message queue will always exist. The life cycle of the anonymous pipe mentioned above is established with the creation of the process and destroyed with the end of the process.

With the message model, the communication between two processes is just like sending an e-mail. You can send one and I will reply. You can communicate frequently.

However, there are two shortcomings in the mail communication method. One is that the communication is not timely, and the other is that the attachment size is limited . This is also the problem of insufficient communication in the message queue.

Message queues are not suitable for the transmission of relatively large data , because each message body in the kernel has a maximum length limit, and the total length of all message bodies contained in all queues also has an upper limit. In the Linux kernel, there will be two macro definitions  MSGMAX and  MSGMNB, in bytes, they respectively define the maximum length of a message and the maximum length of a queue.

In the message queue communication process, there is a data copy overhead between the user mode and the kernel mode , because when a process writes data to the message queue in the kernel, the process of copying data from the user mode to the kernel mode will occur, and the same is true for another process When reading message data in the kernel, a process of copying data from the kernel mode to the user mode occurs.

3. Shared memory

In the process of reading and writing the message queue, there will be a message copy process between the user mode and the kernel mode. The shared memory approach solves this problem very well.

Modern operating systems use virtual memory technology for memory management, that is, each process has its own independent virtual memory space, and the virtual memory of different processes is mapped to different physical memories. Therefore, even if the virtual addresses of process A and process B are the same, they are actually accessing different physical memory addresses, which does not affect the addition, deletion, and modification of data.

The mechanism of shared memory is to take out a virtual address space and map it to the same physical memory . In this way, the things written by this process can be seen by another process immediately, and there is no need to copy them to copy or pass them back and forth, which greatly improves the speed of inter-process communication.

4. Semaphore

Using the shared memory communication method brings a new problem, that is, if multiple processes modify the same shared memory at the same time, it is likely to conflict. For example, if two processes both write an address at the same time, the process that writes first will find that the content is overwritten by others.

In order to prevent data confusion caused by multi-process competition for shared resources, a protection mechanism is needed so that shared resources can only be accessed by one process at any time. It just so happens that the semaphore realizes this protection mechanism.

The semaphore is actually an integer counter, which is mainly used to achieve mutual exclusion and synchronization between processes, not to cache data for inter-process communication .

The semaphore represents the number of resources, and there are two atomic operations to control the semaphore:

  • One is the  P operation , this operation will subtract -1 from the semaphore. After the subtraction, if the semaphore <0, it indicates that the resource has been occupied and the process needs to be blocked and waiting; after the subtraction, if the semaphore >= 0, it indicates that there is still There are resources available, and the process can continue to execute normally.
  • The other is the  V operation . This operation adds 1 to the semaphore. If the semaphore <= 0 after the addition, it indicates that there is currently a blocking process, so the process will be awakened to run; after the addition, if the semaphore is> 0, it means that there is no blocking process;

The P operation is used before entering the shared resource, and the V operation is used after leaving the shared resource. These two operations must appear in pairs.

Next, for example, if we want to make two processes mutually exclusive access to shared memory, we can initialize the semaphore as  1.

The specific process is as follows:

  • Process A performs the P operation before accessing the shared memory. Since the initial value of the semaphore is 1, the semaphore becomes 0 after the P operation is performed by the process A, indicating that the shared resources are available, so the process A can access the shared memory.
  • If at this time, process B also wants to access the shared memory and performs the P operation, the resultant semaphore becomes -1, which means that the critical resource has been occupied, and therefore the process B is blocked.
  • Until process A finishes accessing the shared memory, the V operation will be executed to restore the semaphore to 0, and then the blocked thread B will be awakened, so that the process B can access the shared memory, and finally after the shared memory access is completed, the V operation will be executed To restore the semaphore to its initial value of 1.

It can be found that when the signal is initialized to  1, it represents a mutually exclusive semaphore . It can ensure that only one process is accessing the shared memory at any time, which protects the shared memory very well.

In addition, in multi-processes, each process is not necessarily executed sequentially. They are basically advancing at an independent and unpredictable speed, but sometimes we hope that multiple processes can cooperate closely to achieve one Common task.

For example, process A is responsible for producing data, while process B is responsible for reading data. The two processes cooperate and depend on each other. Process A must produce data before process B can read the data, so the execution is Sequential.

Then at this time, you can use the semaphore to achieve multi-process synchronization, we can initialize the semaphore as  0.

Specific process:

  • If the process B is executed before the process A, then when the P operation is executed, since the initial value of the semaphore is 0, the semaphore will become -1, which means that the process A has not produced data, so the process B will block and wait;
  • Then, when the process A finishes producing the data, the V operation is executed, which will make the semaphore become 0, and then the process B blocked in the P operation will be awakened;
  • Finally, after process B is awakened, it means that process A has produced data, so process B can read data normally.

It can be found that the signal is initialized to  0, which means it is a synchronous semaphore , which can ensure that process A should be executed before process B.

5. Signal

The inter-process communication mentioned above is the working mode under the normal state. For the working mode under abnormal conditions, it is necessary to use "signals" to notify the process.

Although the names of signals and semaphores are 66.66% similar, their uses are completely different, just like the difference between Java and JavaScript.

In the Linux operating system, in response to various events, dozens of signals are provided, which represent different meanings. We can kill -l view all the signals through the  command:

$ kill -l
 1) SIGHUP       2) SIGINT       3) SIGQUIT      4) SIGILL       5) SIGTRAP
 6) SIGABRT      7) SIGBUS       8) SIGFPE       9) SIGKILL     10) SIGUSR1
11) SIGSEGV     12) SIGUSR2     13) SIGPIPE     14) SIGALRM     15) SIGTERM
16) SIGSTKFLT   17) SIGCHLD     18) SIGCONT     19) SIGSTOP     20) SIGTSTP
21) SIGTTIN     22) SIGTTOU     23) SIGURG      24) SIGXCPU     25) SIGXFSZ
26) SIGVTALRM   27) SIGPROF     28) SIGWINCH    29) SIGIO       30) SIGPWR
31) SIGSYS      34) SIGRTMIN    35) SIGRTMIN+1  36) SIGRTMIN+2  37) SIGRTMIN+3
38) SIGRTMIN+4  39) SIGRTMIN+5  40) SIGRTMIN+6  41) SIGRTMIN+7  42) SIGRTMIN+8
43) SIGRTMIN+9  44) SIGRTMIN+10 45) SIGRTMIN+11 46) SIGRTMIN+12 47) SIGRTMIN+13
48) SIGRTMIN+14 49) SIGRTMIN+15 50) SIGRTMAX-14 51) SIGRTMAX-13 52) SIGRTMAX-12
53) SIGRTMAX-11 54) SIGRTMAX-10 55) SIGRTMAX-9  56) SIGRTMAX-8  57) SIGRTMAX-7
58) SIGRTMAX-6  59) SIGRTMAX-5  60) SIGRTMAX-4  61) SIGRTMAX-3  62) SIGRTMAX-2
63) SIGRTMAX-1  64) SIGRTMAX

For a process running in a shell terminal, we can send a signal to the process when we enter certain key combinations through the keyboard. E.g

  • Ctrl+C generates a  SIGINT signal, which means that the process is terminated;
  • Ctrl+Z generates a  SIGTSTP signal, which means that the process is stopped, but it has not yet ended;

If the process is running in the background, you can  kill send a signal to the process by way of commands, but you need to know the PID number of the running process, for example:

  • kill -9 1050 means to send a SIGKILL signal to the process with PID 1050  to terminate the process immediately;

Therefore, the sources of signal events mainly include hardware sources (such as keyboard Cltr+C) and software sources (such as the kill command).

Signal is the only asynchronous communication mechanism in the inter-process communication mechanism , because a signal can be sent to a process at any time. Once a signal is generated, we have the following types of signal processing methods for user processes.

1. Perform the default operation . Linux provides a default operation for each signal. For example, the SIGTERM signal in the above list means terminating the process. Core means Core Dump, that is, after terminating the process, the running state of the current process is saved in a file through Core Dump, which is convenient for programmers to analyze where the problem is afterwards.

2. Capture the signal . We can define a signal processing function for the signal. When a signal occurs, we execute the corresponding signal processing function.

3. Ignore the signal . When we don't want to process some signal, we can ignore the signal and do nothing. There are two signals that the application process cannot catch and ignore, that is,  SIGKILL and  SEGSTOP, they are used to interrupt or end a process at any time.

6. socket

The aforementioned pipes, message queues, shared memory, semaphores, and signals all carry out inter-process communication on the same host. If you want to communicate between processes on different hosts across the network, you need Socket communication.

In fact, Socket communication can not only communicate between processes of different hosts across the network, but also communicate between processes on the same host.

Let's take a look at the system call to create a socket:

int socket(int domain, int type, int protocal)

The three parameters represent:

  • The domain parameter is used to specify the protocol family, such as AF_INET for IPV4, AF_INET6 for IPV6, AF_LOCAL/AF_UNIX for this machine;
  • The type parameter is used to specify communication characteristics. For example, SOCK_STREAM means byte stream, corresponding to TCP, SOCK_DGRAM means datagram, corresponding to UDP, SOCK_RAW means raw socket;
  • The protocal parameter was originally used to specify the communication protocol, but it is basically abandoned now. Because the protocol has been specified by the first two parameters, protocol is generally written as 0 at present;

Depending on the type of socket created, the way of communication is also different:

  • Realize TCP byte stream communication: The socket type is AF_INET and SOCK_STREAM;
  • Realize UDP datagram communication: the socket type is AF_INET and SOCK_DGRAM;
  • Realize local inter-process communication: "local byte stream socket" types are AF_LOCAL and SOCK_STREAM, "local datagram socket" types are AF_LOCAL and SOCK_DGRAM. In addition, AF_UNIX and AF_LOCAL are equivalent, so AF_UNIX also belongs to the local socket;

Next, briefly talk about these three communication programming modes.

Socket programming model for TCP protocol communication

  • The server and the client are initialized  socket, and the file descriptor is obtained;
  • The server call  bindwill be bound to the IP address and port;
  • The server calls  listenand monitors;
  • The server calls  accept, waiting for the client to connect;
  • Client call  connect, initiate a connection request to the server's address and port;
  • The server  accept returns socket the file descriptor used for transmission  ;
  • The client calls  write to write data; the server calls to  read read data;
  • When the client disconnects, it will call it  close, then read when the server  reads the data, it will read it  EOF. After the data is processed, the server will call  closeit to indicate that the connection is closed.

It should be noted here that accept when the server is called  , if the connection is successful, a socket that has been connected will be returned, which is then used to transmit data.

Therefore, the monitoring socket and the socket that are actually used to transmit data are " two " sockets, one is called the monitoring socket , and the other is called the completed connection socket .

After a successful connection is established, both parties begin to read and write data through the read and write functions, just like writing to a file stream.

Socket programming model for UDP protocol communication

UDP is not connected, so there is no need for a three-way handshake, and there is no need to call listen and connect like TCP. However, the interaction of UDP still requires an IP address and port number, so bind is also required.

For UDP, there is no need to maintain a connection, so there is no so-called sender and receiver, and there is no concept of client and server. As long as there is a socket, multiple machines can communicate at will, so each UDP All sockets need bind.

In addition, when calling sendto and recvfrom each time you communicate, you must pass in the IP address and port of the target host.

Socket programming model for local inter-process communication

The local socket is used in the scenario of inter-process communication on the same host :

  • The programming interface of the local socket is consistent with the programming interface of IPv4 and IPv6 sockets, and can support two protocols of "byte stream" and "datagram";
  • The implementation efficiency of local sockets is much higher than that of IPv4 and IPv6 byte stream and datagram sockets;

For local byte stream sockets, the socket types are AF_LOCAL and SOCK_STREAM.

For local datagram sockets, the socket types are AF_LOCAL and SOCK_DGRAM.

When binding the local byte stream socket and the local datagram socket, unlike TCP and UDP, they are not bound to an IP address and port, but to a local file . This is the biggest difference between them.

7. Summary

Since the user space of each process is independent and cannot access each other, the kernel space is needed to realize inter-process communication. The reason is simple, each process shares a kernel space.

The Linux kernel provides many ways to communicate between processes. The simplest way is pipes. Pipes are divided into "anonymous pipes" and "named pipes".

As the name implies, anonymous pipes have no name identification. Anonymous pipes are special files that only exist in memory and do not exist in the file system. The |vertical line " " in the shell command is an anonymous pipe. The communication data is an unformatted stream and its size is limited. , The method of communication is one-way , and data can only flow in one direction. If two-way communication is required, two pipes need to be created. Then, anonymous pipes can only be used for inter-process communication with a parent-child relationship. The life cycle of anonymous pipes It is established as the process is created, and disappears as the process terminates.

Named pipes break through the communication restriction that anonymous pipes can only be between related processes. Because of the premise of using named pipes, a device file of type p needs to be created in the file system, then unrelated processes can be carried out through this device file. Communication. In addition, regardless of whether it is an anonymous pipe or a named pipe, the data written by the process is cached in the kernel . When another process reads the data, it is naturally obtained from the kernel. At the same time, the communication data follows the first-in-first-out principle and does not support lseek. The file location operation of the class.

The message queue overcomes the problem that the data of the pipeline communication is an unformatted byte stream. The message queue is actually stored in the "message linked list" of the kernel. The message body of the message queue is a data type that can be customized by the user. When sending data, It will be divided into independent message bodies. Of course, when receiving data, it must be consistent with the data type of the message body sent by the sender, so as to ensure that the data read is correct. The communication speed of the message queue is not the most timely. After all, each data writing and reading need to go through the copy process between the user mode and the kernel mode.

Shared memory can solve the overhead caused by the data copy process between the user mode and the kernel mode in the message queue communication. It directly allocates a shared space, and each process can directly access it , just as fast and convenient as accessing the process's own space. Need to fall into kernel mode or system call, greatly improve the speed of communication, enjoy the name of the fastest way of communication between processes. However, convenient and efficient shared memory communication brings new problems, and multi-process competition for the same shared resource will cause data confusion.

Then, semaphores are needed to protect shared resources to ensure that only one process can access shared resources at any time. This method is mutually exclusive access. The semaphore can not only achieve mutual exclusion of access, but also achieve synchronization between processes. The semaphore is actually a counter, which represents the number of resources, and its value can be controlled by two atomic operations, namely  P operation and V Operation .

The name very similar to the semaphore is called a signal . Although the two names are similar, their functions are not the same at all. Signals are the only asynchronous communication mechanism in the inter-process communication mechanism . Signals can directly interact between the application process and the kernel. The kernel can also use signals to notify the user space process of which system events have occurred. The source of signal events is mainly from hardware sources. (Such as keyboard Cltr+C) and software source (such as kill command), once a signal occurs, the process has three ways to respond to the signal: 1. Perform the default operation, 2. Catch the signal, 3. Ignore the signal . There are two signals that cannot be caught and ignored by the application process, that is,  SIGKILL sum  SEGSTOP, which is for the convenience of us to end or stop a process at any time.

The communication mechanisms mentioned above all work on the same host. If you want to communicate with processes of different hosts, you need Socket communication . Socket is actually not only used for communication between different host processes, but also for communication between local host processes. According to the type of Socket created, it can be divided into three common communication methods, one is the communication method based on the TCP protocol, and the other is the communication method based on the TCP protocol. It is a communication method based on the UDP protocol, and the other is a local inter-process communication method.

The above is the main mechanism of inter-process communication. You may ask, what about the way of communication between threads?

The threads in the same process are all sharing process resources. As long as it is a shared variable, inter-thread communication can be achieved, such as global variables. Therefore, the focus is not on the communication method between threads, but the multi-threaded competition for shared resources. The problem, semaphores can also achieve mutual exclusion and synchronization between threads:

  • The mutual exclusion method can ensure that only one thread accesses shared resources at any time;
  • The synchronization method can ensure that thread A should be executed before thread B;

Guess you like

Origin blog.csdn.net/www_dong/article/details/114005428