IPC interprocess communication(1)

Table of contents

1.1 Why communication

1.2 Why can communicate

2.1 The structure of inter-process communication mechanism

2.2 Types of Interprocess Communication Mechanisms

2.3 Interface design of inter-process communication mechanism

3.1 SysV shared memory

3.2 POSIX shared memory

3.3 Shared memory mapping

3.4 Android ION

3.5 dma-buf heaps

3.6 Anonymous pipes

3.7 Named Pipes

3.8 SysV message queue

3.9 POSIX message queues

3.10 Sockets

3.11 Android Binder

3.12 Signal mechanism

3.13 Pseudo-terminals

IPC

Socket mode

MQ mode

file method

Shared memory

RMI way


1. The essence of interprocess communication

What is interprocess communication?

Why is there inter-process communication?

Why can inter-process communication?

1.1 Why communication

Let's take people as an analogy first. There are two reasons why people communicate with each other. First of all, it is because you need to communicate with the other party. If you don't want to talk to the other party, then you definitely don't need to communicate. The second is because of the space isolation. If you two are together, the other party will stand in front of you, and you can just speak directly if you have something to say, and there is no need for communication. At this time, you have to call or send a WeChat message to the other party, doesn't it seem very strange and inexplicable. If the two of you are not together and you still have something to communicate, this is the time to communicate. The way of communication is a bit of beacon fire, sending pigeons, writing letters, sending telegrams, making phone calls, sending WeChat, etc. What kind of communication method to adopt is related to your needs, the size of the communication volume, and whether it can be realized objectively.

Similarly, why is there inter-process communication in the software system? First of all, it is because of this requirement in the software, for example, some tasks are completed by multiple processes, or one process has a service request for another process, or there is a message to be provided to the other party. Secondly, because there is isolation between processes, each process has its own independent user space and cannot see each other, so communication is required.

1.2 Why can communicate

Why can we communicate? That's because the kernel space is shared. Although N processes have N user spaces, there is only one kernel space. Although the user spaces are completely isolated, the user space and the kernel space are not completely isolated. There are system calls on this channel to communicate with each other. So the two user spaces can communicate through the bridge of the kernel space.

Let's explain it with the help of a picture.

Although this figure is about process scheduling, you can also see from this figure why processes need to communicate, because processes are separated by space, and there is no way for them to exchange information. Fortunately, they are all connected to the kernel. Although they cannot access the kernel at will, there is still the door of system calls. Processes can communicate with the kernel through some special system calls to achieve communication with other processes. The purpose of process communication.

Second, the framework of inter-process communication 

Through the description in the previous chapter, we understand why inter-processes communicate and why they can communicate. Now let's take a look at how to implement inter-process communication mechanisms.

2.1 The structure of inter-process communication mechanism

The inter-process communication mechanism must consist of two parts, one is the communication center that exists in the kernel space, and the other is the communication interface that exists in the user space. The relationship between the two is like the relationship between the post office and stationery, the relationship between the base station and the mobile phone . The communication center provides the communication mechanism, and the communication interface provides the usage method. We use the communication interface to let the communication center help us establish a communication channel or transfer communication information.

Let's draw a picture to see the basic structure of the inter-process communication mechanism.

2.2 Types of Interprocess Communication Mechanisms

There are two types of inter-process communication mechanisms, one is the matchmaker style, which matches the two of you, and then it doesn’t matter, you two talk by yourself, and the other is the nanny style, which keeps talking in the middle. These two modes are called shared memory and message passing in computer terms. In the shared memory interprocess communication, after the communication center establishes the communication channel, it is no longer in charge, and the communication between the two parties does not need the assistance of the communication center. Message-passing inter-process communication. After the communication center establishes a communication channel, each communication requires the assistance of the communication center. Shared memory inter-process communication, since the transfer of communication information does not require the assistance of the communication center, the two sides of the communication also need inter-process synchronization to ensure the consistency of data reading and writing, so as to avoid stepping on data or reading garbage data. Message-passing inter-process communication does not require inter-process synchronization because the communication information is transmitted through the communication center. Message-passing interprocess communication can be divided into two types, bounded messages and unbounded messages. Unbounded messages are byte streams, and they are sent one by one. It depends on the process itself to design how to distinguish the boundaries of the message. The sending and receiving of interprocess communication with boundary messages is based on the message as the basic unit.

2.3 Interface design of inter-process communication mechanism

According to the relationship between the two parties in the communication, the communication types can be divided into symmetric communication and asymmetric communication. The relationship between the two parties in symmetric communication is equal, while the relationship between the two parties in asymmetric communication is not equal, which may be order execution relationship, customer service relationship, production and consumption relationship, etc. This relationship is a logical relationship between the two communicating parties, not a feature of the inter-process communication mechanism itself. Message-passing interprocess communication is generally used for asymmetric communication, and shared memory interprocess communication is generally used for symmetric communication, and can also be used for asymmetric communication.

The inter-process communication mechanism generally needs to implement the following three types of interfaces, but some mechanisms do not necessarily require all three types of interfaces to be implemented.

1. How to establish a communication channel and who will establish a communication channel.

2. How the latter finds and joins this communication channel.
3. How to use the communication channel.

For symmetric communication, it doesn't matter who establishes the communication channel, as long as one person establishes it, and the latter directly joins the communication channel. For asymmetric communication, generally the server and consumer establish a communication channel, and the client and producer join the communication channel. How to establish a channel? Different inter-process communication mechanisms have different interfaces to create channels. This will be discussed in the next chapter. How does the latter find and join the communication channel established by the former? Generally, the two parties find the channel handle through the channel name agreed in advance, and join the communication channel through the channel handle. But some pass the channel handle to the other party through inheritance, some pass the channel handle through other inter-process communication mechanisms, and some directly find the channel through the channel name without the channel handle. How to use the channel? For message-passing interprocess communication, special interfaces are generally provided. For shared memory interprocess communication, this interface does not need to be provided, because accessing shared memory is just like accessing ordinary memory.

3. Introduction to inter-process communication mechanism

Before we have a basic understanding of the nature and framework of inter-process communication, let's briefly introduce all inter-process communication mechanisms in Linux. Let's look at the general picture first.

Let's take a brief look at this picture first. First of all, in terms of categories, inter-process communication methods can be divided into three categories, message passing, shared memory, and inter-process synchronization. Why is there inter-process synchronization here? Inter-process synchronization is to synchronize the reading and writing of shared memory by two processes. Inter-process synchronization can also be regarded as passing information between two processes, so inter-process synchronization is also included in inter-process communication.

It can be seen that the shared memory mechanism is less than the message passing mechanism, so we will first introduce the shared memory mechanism. The principle of shared memory interprocess communication is very simple, that is, by modifying the page table, a part of the virtual memory of the two virtual process spaces corresponds to the same physical memory. Although the principle is the same, how to implement it and how to design the interface have produced many different shared memory interprocess communication mechanisms.

3.1 SysV shared memory

SysV shared memory is a very old shared memory method that existed in the early days of UNIX. The method of creating shared memory in SysV shared memory is to use the interface shmget, which has three parameters, namely key, size, and flag. Among them, key is an integer, which is the name of the communication channel, and the two processes must agree on the key in advance. Size represents the size of the shared memory. Flag is used to indicate the behavior of creation. flag IPC_CREAT means that if the communication channel exists, it will be obtained directly, and if it does not exist, it will be created. If there is no IPC_CREAT, it means only get but not create. If IPC_EXCL is added, it means that it is only created, and if it has been created by others, it will return failure. What shmget returns is the id of the shared memory, which represents the handle of the communication channel. Then use the handle of the communication channel to map the underlying physical memory to the process space through the shmat interface. The return value of the function is a pointer mapped to the virtual memory space of the process, and then this memory can be read and written like ordinary memory. After the task is completed, the channel can be released through the shmdt interface. Note that this only releases the communication channel of the process, but does not release the underlying physical memory. To release the underlying physical memory, you need to use the interface shmctl() and select the IPC_RMID operation.

3.2 POSIX shared memory

I believe that everyone has doubts about the previous description. If an integer is used as the name of the communication channel, wouldn’t it be easy to choose the right one, and it would be confusing! And if someone maliciously guesses and uses your key, there is nothing you can do. In response to this problem, POSIX has designed a new shared memory solution called POSIX shared memory, which solves this problem well. POSIX shared memory uses the interface shm_open to create a shared memory communication channel handle. Its parameters are the same as open, but it does not create a disk file. In this way, we use a path name as the name of the communication channel, which is much better than an integer key, and it is easy to name and not easy to repeat. And its parameters are the same as open, so its third parameter mode can specify permissions, which is more secure. The second parameter of shm_open is the same as the second parameter of open. You can specify flag O_CREAT O_EXCL. These two flags can achieve the same effect as the previous shmget. You can choose whether to only join the existing channel or insist on yourself Create a channel yourself, or join if you already have one. shm_open returns an fd, which is the handle of the communication channel. With this fd, we can set the size of the shared memory through the interface ftruncate. After getting the channel handle, the way we join the channel is not to use a dedicated method, but to use the existing interface of the system, using shared mmap, which is very different from SysV shared memory. After mmap, we added the channel, and its return value is a pointer to the virtual memory space of the process, and we can operate it like ordinary memory.

3.3 Shared memory mapping

The system call mmap is not specifically used for inter-process communication, it is used for memory mapping. Its mapping source can be a file or anonymous (that is, there is no source, directly allocate memory and initialize it to 0). Its mapping can be private or shared. There are four ways to combine the mapping source and the mapping method. When we use the shared mapping method, it can be used for inter-process communication. For shared file mapping, the purpose of shared memory can be achieved by mapping the same file between two processes. The file name is the name of the communication channel, and the name is directly added to the channel without a channel handle. For shared anonymous mapping, memory is shared between parent and child processes after fork. After fork, the memory between the parent and child processes is originally COW (copy-on-write), which means that the memory will not be shared between the parent and child processes, but the shared anonymously mapped part will not be COW, but shared between the parent and child processes. Memory, which achieves the effect of shared memory. This method has neither a channel name nor a channel handle, and the channel is obtained directly through inheritance. Both methods of releasing shared memory use the munmap function.

3.4 Android ION

Many blogs will introduce that ION is a memory allocation manager, which is neither right nor wrong. Looking at ION alone, it is indeed a memory allocation manager, but we can't just look at ION, we have to look at it together with dma-buf. Dma-buf is neither dma nor buffer, it is a buffer sharing framework, the focus is on sharing. The Dma-buf framework implements a memory sharing scheme between processes and between processes and the kernel. But it is just a framework, and it does not have the ability to allocate memory. ION implements the memory allocation management function based on the dma-buf framework, so ION and dma-buf should be regarded as a whole and a shared memory mechanism. ION is different from ordinary shared memory mechanisms in that it can not only share memory between processes, but also share memory between processes and the kernel. When ION shares memory between processes, one party creates an fd through the ioctl ALLOC command of /dev/ion. This fd is the channel handle, and communication can be performed by mmaping this fd. This is a bit similar to the POSIX shared memory model, the difference is how the other party gets this fd. POSIX shared memory is to open the same file name through shm_open to get the handle of the same channel (the handle value is not necessarily the same, but the underlying corresponding channel is the same). ION transfers fd to another process through Binder. Binder makes special treatment for fd. The fd received by the other party and its own fd may not necessarily have the same value, but the underlying corresponding things are the same. It is meaningless to pass the value of fd directly to a process. There are two cases of sharing memory between ION and the kernel driver. One is that the kernel driver creates the underlying physical memory and then packs it into an fd, which is passed to the process through some system calls. The process can perform mmap on this fd. communicated. Another situation is that the process creates the fd of the communication channel, and then passes it to the kernel driver through some system calls, and the kernel driver finds its corresponding physical memory according to the fd.

There are many different heaps in ION. The physical memory area and method allocated by each heap are different. You can select a different heap by specifying a flag when using the ION interface.

3.5 dma-buf heaps

dma-buf heaps is a replacement for ION. Because all heaps in ION correspond to the same device file /dev/ion, different heaps are selected by specifying flags in the interface. Then there is a problem, that is, all the heaps of ION are open to all processes, and it is impossible or not easy to restrict the permissions of different processes. dma-buf heaps just solves this problem. It splits different heaps into different devices, all in the directory /dev/dma_heap/, for example, /dev/dma_heap/system is the default heap. In this way, different file permissions can be set for different heaps, and can also be restricted by selinux, which greatly improves security. Its usage and underlying logic are the same as those of ION, so I won't introduce too much here. It is worth mentioning that dma-buf heaps has been integrated into the standard kernel, and Android is gradually replacing ION.

3.6 Anonymous pipes

What I said above is the shared memory interprocess communication. Let's talk about the message passing interprocess communication. Let's talk about unbounded message-passing interprocess communication first.

Anonymous pipes are the earliest inter-process communication mechanism on UNIX. Its appearance comes from the fact that the early operating systems were all command-line, and we often need multiple commands to cooperate to complete a task. For example, ls -ef | grep process-name, the output of the previous command in this command should be used as the input of the following command, and the | vertical line in the middle is called the pipe symbol, which means that the data is passed from front to back like a pipe. So how is the logic of this pipe symbol realized in the program? It is realized through anonymous pipes. When the Shell executes the command, it first forks out a subprocess A, then parses the command in the subprocess A, and finds that the command needs to execute two programs, which are connected through a pipeline. So use the anonymous pipeline creation interface int pipe(int fd[2]), this interface receives an array of double int elements as a parameter. After the interface is executed, two fds are returned, fd[0] is the read-end fd, and fd[1] is the write-end interface. Then fork A to generate process B, so that process B also inherits two fds. It is meaningless to have two fds for both A and B, so process A close(fd[0]) and process B close(fd[1]). Then process A executes exec ("ls -l"), and then process B executes exec ("grep process-name"), so that process A can output data through fd[1], and process B can read data through fd[0] . This achieves the purpose of inter-process communication. The anonymous pipe creates a communication handle through the parent process of the communication parties, and then passes it to the child process through fork. Both parent and child processes pass messages through file IO. Since file IO is used, all reads and writes are byte streams, and there is no message boundary. If the process wants to determine the message boundary, it needs to find a way to determine the boundary of each message. For example, each newline character represents a message, or every time it encounters the string AAAAAA, it represents a new message.

3.7 Named Pipes

We can see that although anonymous pipes are very useful, they have a big flaw, that is, they can only be used between parent-child processes or relative processes, because the channel handle fd must be passed. Is there a way to expand the use of anonymous pipes? Yes, create named pipes. After the pipe has a name, other processes can find the channel handle through the name and join the channel. The usage of named pipes is to first use the mkfifo command to create a file in the file system. This file is a real file, but it is not a regular file, but a fifo type file. After having this file, writers on both sides of the communication can use the normal open interface to open the file in O_WRONLY mode, and readers can use the open interface to open the file in O_RDONLY mode. Then the read and write parties can read and write through their respective fd pipelines. Named pipes are created differently than anonymous pipes, but the message passing is the same. Anonymous pipes are also unbounded messages, and the principle is the same as that of anonymous pipes.

3.8 SysV message queue

A SysV message queue is a bounded message-passing interprocess communication. Its channel creation logic is similar to SysV shared memory. The creation interface is msgget, which has two parameters key and flag. Key is an integer and is the channel name. There are two flags. Flag IPC_CREAT means that if the communication channel exists, it will be obtained directly, and if it does not exist, it will be created. If there is no IPC_CREAT, it will only be obtained but not created. If the flag IPC_EXCL is added, it means that it is only created, and if it has been created by others, it will return failure. What msgget returns is the id of the message queue, which is the handle of the channel. Then you can send and receive messages through the interfaces msgsnd and msgrcv, one can only send or receive one message. After the communication is completed, the message queue can be destroyed through the IPC_RMID operation of the interface msgctl.

3.9 POSIX message queues

The problems of SysV message queue and SysV shared memory are the same, so POSIX message queue was designed. The creation interface of POSIX message queue is mq_open, and its parameters are similar to open. Use a string type name as the channel name. There is also a flag parameter that is the same as the flag parameter mentioned above, which can specify whether to create a channel or join an existing channel. The return value is called the message queue descriptor, which is the channel handle. Then you can send and receive messages through the interfaces mq_send and mq_receive. When the communication is completed, the channel can be closed through the interface mq_close. The underlying channel is only deleted if all processes have closed the channel.

3.10 Sockets

Sockets are divided into network sockets and UNIX local sockets. Network sockets can not only communicate between processes on this machine, but also communicate between different machines. UNIX local sockets can only communicate between processes on this machine. Both are divided into stream sockets and datagram sockets. The former is unbounded message passing interprocess communication, and the latter is bounded message passing interprocess communication. The socket distinguishes the server and the client, the server creates a communication channel, and the client joins the communication channel. The socket interface will not be introduced here. You can find some books or blogs related to network programming to learn.

3.11 Android Binder

Android Binder is RPC developed by Google for Android, and RPC means remote procedure call. RPC is also a kind of inter-process communication, but it is not just inter-process communication. Its use interface is expressed as a function that can transparently call other processes. The communication center of Binder is the Binder driver in the kernel, and its user space interface is a series of ioctl commands to the virtual device /dev/binder. But the process does not directly use these ioctl commands, but uses the libbinder library packaged by Google. The specific details of Binder will not be discussed here. I recommend two learning blogs for everyone:

http://gityuan.com/2015/10/31/binder-prepare/

There are about a dozen articles in this series of blogs, which explain all aspects of Binder in detail.

https://www.jianshu.com/p/adaa1a39a274

This is Binder's advanced learning, there are 3 articles, through some questions and answers, let you have a deeper understanding of binder.

3.12 Signal mechanism

The signal mechanism is a mechanism that existed very early in UNIX. It is a method used by the kernel to handle errors that occur when the program is running, and it is also a method to send some simple and specific messages to the process, so it can also be regarded as a process. inter-communication mechanism. But it is quite special, and its structure is not the same as that of the general inter-process communication mechanism. It does not need to establish a communication channel, because it is not a typical inter-process communication, or its communication channel is naturally established, because it uses pid to specify who to send the message to. Its sending is sent by the kernel or the process is sent through an interface such as kill, and it can be sent to the other party by specifying the pid. The other party can set the signal processing function to receive and process the signal, or not, and the kernel will process it by default. For details of the signal mechanism, please refer to "In-depth Understanding of Linux Signal Mechanism".

3.13 Pseudo-terminals

You may have heard the words terminal, virtual terminal, console, terminal emulator, pseudo-terminal, etc. It is estimated that everyone is as confused about these words as I am, and can't figure out what they mean and the relationship between them. In fact, I don't know much about virtual terminals and consoles, but I still know a lot about terminals, terminal emulators, and pseudo-terminals. Let me explain to you here. In the earliest days, a computer was a mainframe the size of several houses, and ordinary people couldn't afford it at all, and some universities, scientific research institutes, or government agencies could only afford one. Then everyone buys a terminal and connects to this computer to use it. A terminal is a monitor plus a keyboard, but this monitor is not a pixel display, but a character display, and one screen can only display 80x25 characters. The programs at that time were also command-line programs, which received input from the terminal and output the result to the terminal. Specifically, inside the program, fd 0 corresponds to terminal input, and fd 1 corresponds to terminal output. The terminal does not say that what is input by the keyboard is passed to the program intact, but it will do certain preprocessing.

Later, with the continuous development of technology, computers became the computers we use today. Everyone can buy a stand-alone computer, and the monitor has become a pixel monitor, which can display a rich picture. And the mode of many programs has also changed from command line mode to GUI mode. But there are still many programs that are more suitable for execution on the command line, and still retain the command line mode. A GUI program was developed for this system, called a terminal emulator, which is also what we usually call a command line interface or terminal program. It uses a graphical interface to simulate the previous terminal interface, making us look like we are using a terminal, but it is a GUI program itself. How does the terminal emulator run command line programs? It will use the interface of the system to create a pseudo-terminal. The pseudo-terminal is divided into two parts: the master end and the slave end. The emulator takes the master end itself, and the command-line program takes the slave end, so that the command-line program seems to be running in the terminal environment. . The characters we input from the keyboard are first passed to the terminal emulator according to the logic of the GUI program, and then the terminal emulator passes the input to the main end of the pseudo-terminal, and then the pseudo-terminal is processed in the kernel according to the logic of the terminal itself, and then Send to the pseudo-terminal slave so that our command-line program will receive input. The output of the command line program is first sent to the pseudo-terminal slave, then enters the pseudo-terminal in the kernel, and then sends it to the pseudo-terminal master, and then the terminal emulator receives our output, and then it follows the method of the GUI program Draw the output to its window, and we see the output of the program. Therefore, the pseudo-terminal can be regarded as an inter-process communication mechanism between the terminal emulator and the command-line program.

IPC

Inter-process communication: InterProcess Communication. Data read and write between different processes.

Socket mode

Such as HTTP, TCP, RPCetc. can realize direct data exchange.

MQ mode

Such as RabbitMQ, RocketMQ, Kafkaetc. can realize indirect data exchange.

file method

Such as Datebase, Fileetc. can realize indirect data exchange.

Shared memory

JavaMappedByteBufferInter -process communication is achieved through shared memory.

The essence is to read and write files. In order to avoid data confusion under high concurrency, FileLockfile locks can be used to lock files.

public class WriteShareMemory {

    public static void main(String[] args) {
        try (RandomAccessFile randomAccessFile = new RandomAccessFile("share.mm", "rw")) {
            FileChannel channel = randomAccessFile.getChannel();
            FileLock lock = channel.lock();

            MappedByteBuffer buffer = channel.map(FileChannel.MapMode.READ_WRITE, 0, 10);
            for (int i = 0; i < 10; i++) {
                buffer.put(i, (byte) i);
                Thread.sleep(2000);
            }
            lock.release();
        } catch (IOException | InterruptedException e) {
            e.printStackTrace();
        }

        System.out.println("写入完成");
    }

}

public class ReadShareMemory {

    public static void main(String[] args) {
        try (RandomAccessFile randomAccessFile = new RandomAccessFile("share.mm", "rw")) {
            FileChannel channel = randomAccessFile.getChannel();
            MappedByteBuffer buffer = channel.map(FileChannel.MapMode.READ_WRITE, 0, 10);

            FileLock lock;

            while (true) {
                lock = channel.tryLock();
                if (lock == null) {
                    Thread.sleep(2000);
                    System.out.println("locked");
                } else {
                    break;
                }
            }

            for (int i = 0; i < 10; i++) {
                System.out.println(buffer.get(i));
            }
            lock.release();
        } catch (IOException | InterruptedException e) {
            e.printStackTrace();
        }

        System.out.println("读取完成");
    }

}

RMI way

Remote Method Invoke: Remote method call. Direct data exchange is possible.

public interface IRemote extends Remote {
    String testMethod(String a, String b) throws RemoteException;
}

public class RemoteBean extends UnicastRemoteObject implements IRemote {
    private static final long serialVersionUID = 3682470187378911418L;

    protected RemoteBean() throws RemoteException {
    }

    @Override
    public String testMethod(String a, String b) {
        return a + b;
    }
}

public class RmiServer {

    public static void main(String[] args) {
        try {
            IRemote iRemote = new RemoteBean();
            LocateRegistry.createRegistry(1099);
            Registry registry = LocateRegistry.getRegistry();
            registry.bind("test", iRemote);
        } catch (Exception e) {
            e.printStackTrace();
        }
    }

}

public class RmiClient {

    public static void main(String[] args) {
        try {
            Registry registry = LocateRegistry.getRegistry("localhost",1099);
            IRemote iRemote = (IRemote) registry.lookup("test");
            String s = iRemote.testMethod("hello", "world");
            System.out.println(s);
        } catch (Exception e) {
            e.printStackTrace();
        }
    }

}

If the interface object is not used, IRemotethe following exception will be thrown, because Javathe dynamic proxy is interface-oriented:

java.lang.ClassCastException: class com.sun.proxy.$Proxy0 cannot be cast to class RemoteBean (com.sun.proxy.$Proxy0 and RemoteBean are in unnamed module of loader 'app')
    at RmiClient.main(RmiClient.java:12)

Guess you like

Origin blog.csdn.net/a1058926697/article/details/131598681