- Interprocess communication mode
- The introduction
- The Shared memory
- A semaphore
- How semaphores work
- Understanding semaphore
- The pipe
- Anonymous pipe
- A named pipe
- The message queue
- What is a message queue?
- The characteristics of
- signal
- About the signal
- The characteristics of
- The socket
- conclusion
Articles have been included in my Repository: Java Learning Notes and free book sharing
Interprocess communication mode
The introduction
In an operating system, a process can be understood as a running activity of a collection of computer resources, which is an instance of an executing program. Conceptually, a process has its own virtual CPU and virtual address space, and each process is independent of the other, which raises the question — how do processes communicate with each other?
Since processes are independent of each other and do not have any means to communicate directly, we need the operating system to assist them. For example, if A and B are independent and cannot communicate with each other, they can use A third party C if they want to communicate. For example, A gives information to C, and C forwards the information to B. This is the main idea of inter-process communication — sharing resources.
An important issue to address here is how to avoid contention, that is, multiple processes accessing the resources of a critical section at the same time.
The Shared memory
Shared memory is one of the simplest ways to communicate between processes. Shared memory allows two or more processes to access the same memory block. When one process changes the contents of the address, other processes are aware of the change.
You might think, why don’t I just create a file, and then the process will be accessible?
Yes, but this approach has several drawbacks:
- To access a file, you need to get into a system call, go from user mode to kernel mode, and then execute kernel instructions. This is very inefficient and out of the user’s control.
- Direct access to disk is very slow, hundreds of times slower than access to memory. In a sense, this is shared disk not shared memory.
Under Linux with the method of Shared memory to process is complete access to a Shared resource, it will be a disk file is copied to the memory, and create a virtual address to the memory mapping, as if the resource is in the process space, then we can operate it as a local variable to operate them, as the actual written to disk will be completed by the system to choose the best way, For example, the operating system may batch add sort, which greatly improves I/O speed.
As shown in the figure above, a process maps shared memory to its own virtual address space. A process accesses the shared process as if it were accessing its own virtual memory, which is very fast.
The shared memory model should be easy to understand: a shared resource file is created in physical memory and the process binds the shared memory to its own virtual memory.
One of the problems to solve is how to bind the same shared memory to its own virtual memory. Considering that using malloc in different processes allocates free memory sequentially rather than the same memory, how to solve this problem?
H > and #include
headers. There are several SHM functions:
-
Shmget function: The ftok() function obtains the resource identifier (IPC key) of the file to be shared. This resource identifier is used as a parameter to obtain the unique identifier ID of the shared memory area.
The ftok() function identifies system IPC resources, such as shared resources here, message queues below, and pipes…… All belong to IPC resources.
IPC: Inter-process [Communication](baike.baidu.com/item/ Communication/20394231), IPC refers to the data interaction between two processes.
-
Shmat function: establishes the mapping from shared memory to process-independent space through the identifier obtained by the shmget function.
-
SHMDT function: Release mapping.
Since we mainly work in Java/Go development positions, the number of times we have to deal with these C functions in Linux system may be 0. Therefore, I will not describe the specific use methods of functions in detail in learning, but we can search by Bing when we use them.
Through the above functions, each independent process with a shared memory identifier can establish a mapping between virtual addresses and physical addresses, and each virtual address will be translated into a physical address pointing to the shared area, thus achieving access to the shared memory.
A similar implementation uses the Mmap function, which is usually a direct map to disk — so it’s not shared memory, it’s very large, but it’s slow to access. Shmat, on the other hand, typically stores resources in memory to create mappings that are fast to access but small to store.
Shared memory is of the highest efficiency, compared with other several ways because of the need for multiple replication, directly to memory operations, to note, however, the operating system does not guarantee any concurrency issues, such as two processes change the same piece of memory area at the same time, as you and your friends online editing the same document of the same title, which can lead to some bad results, So we need to use semaphores or other means to accomplish synchronization.
A semaphore
Semaphore is a method first proposed by Dijkstra to solve the problem of synchronization of different execution threads. Process and thread abstraction are almost the same, so semaphore can also be used for synchronization of inter-process communication.
How semaphores work
The semaphore s is a global variable with a non-negative integer value, implemented by two special atomic operations called P and V:
-
P(s) : If s is greater than zero, it is subtracted by 1, and immediately returned, and the process continues. ; If it has a value of zero, execution of the process is suspended and s becomes non-zero again.
-
V(s) : the V operation increases the value of s by 1. If any process is waiting until s becomes non-zero, the V operation restarts one of these waiting processes (randomly), which then performs the P operation to reset S to zero, while the other waiting processes continue to wait.
Understanding semaphore
It is important to understand that the semaphore is not used to transport resources, but to protect shared resources. The representation of the semaphore S is the maximum number of processes allowed to access the resource at the same time. It is a global variable. Consider the simple example above: two processes modify at the same time and cause errors. We do not consider the reader but only the writer process. In this example, sharing resources allows at most one process to modify resources, so we initialize s to 1.
At the beginning, A writes resources first. At this time, A calls P(s) and subtracts S by one. At this time, S = 0, A enters the shared area to work.
At this time, process B also wants to enter the shared area to modify resources. It calls P(s) and finds that s is 0, so it suspends the process and joins the waiting queue.
When A is finished, V(s) is called. It finds that S is 0 and detects that the wait queue is not empty, so it wakes up A waiting process randomly and increases S by 1, which wakes up B.
B wakes up and continues to perform P. At this time, S is not 0. B successfully sets S to 0 and enters the workspace.
At this point C wants to enter the workspace……
You can see that only one process can access the shared resource at any time. This is what the semaphore does. It controls the maximum number of processes that can enter the shared area, depending on the value of initialization s. After that, the P operation is called before entering the shared area, and the V operation is called after exiting the shared area. This is the idea of semaphore.
There are no direct P&V functions in Linux. Instead, we need to encapsulate them according to the following basic SEM families:
- Semget: Initializes or gets a semaphore. This function takes the return value of ftok() and the initial value of S. It binds the global count variable S to a shared resource identified by ftok and returns a uniquely identified semaphore group ID.
- Semop: This function takes the semaphore group ID returned by the above function, along with some other parameters. Depending on the parameters, it will do some operations on the global count variable S bound to the semaphore group ID. The P&V operation is based on this.
- Semctl: this function takes the semaphore group ID returned by the above function and some other parameters. It controls the semaphore information, such as deleting the semaphore.
The pipe
As the name suggests, a pipe is like a pipe in life. One end sends it, the other receives it. The two sides need not know each other, just the pipe.
Pipes are one of the most basic interprocess communication mechanisms. Pipes are created by the PIPE function: Calling the pipe function creates a buffer in the kernel for interprocess communication. This buffer is called a pipe and has a read and write end. Pipes are classified into anonymous pipes and named pipes.
Anonymous pipe
The anonymous pipe is created using the pipe function, which takes an array of ints of length 2 and returns 1 or 0 for success or failure:
int pipe(int fd[2])
This function opens two file descriptors, a read-side file and a writer-side file, and stores them in fd[0] and fd[1], respectively. Write and read functions can then be called as arguments to write or read. Note that fd[0] can only be used to read files, while fd[1] can only be used to write files.
You may be wondering, how does this work? Other processes do not know about this pipeline because processes are independent and cannot see what one process does.
Yes, the ‘other’ process does not know, but its children do! The child processes copy the memory space of the parent process. This means that the parent process is still independent, but at this point, all of their information is equal. So the child process also knows about the global pipe and also has two file descriptors hooked to the pipe, so anonymous pipes can only communicate between related processes.
It should also be noted that the anonymous pipe is implemented internally by a circular queue, which can only be implemented from write to read. Due to design technical problems, the pipe is designed to be half duplex, and the read descriptor must be closed if one party wants to write and the write descriptor must be closed if one party wants to read. Therefore, we say that a pipe message can only be delivered in one direction.
Note that the pipe is clogged, and how clogged depends on whether the read/write process closes the file descriptor. If the read pipe is empty, assuming that the write port is not completely closed, then the operating system will assume that there is still data to read, at this point the read process will be blocked until new data or write port is closed; If the pipe is empty and the write port is closed, the operating system thinks there is nothing left to read and exits, returning 0.
When the write port is writing to the pipe, if the pipe is full, if the read end is not closed, the write end will be blocked; If the read end is closed, the operating system will consider the write pipe meaningless because no one is receiving it, so it will send a termination signal to cause the process
The inner part of the pipe is managed by the kernel, which ensures that data will not have concurrency problems under the condition of half-duplex.
A named pipe
With anonymous pipes in mind, named pipes are easy to understand. In the introduction of anonymous pipes, we said that other processes are unaware of the existence of pipes and file descriptors, so anonymous pipes are only suitable for related processes. Named pipes solve this problem perfectly — pipes now have a unique name and can be accessed by any process.
Note that the operating system will be pipeline as an abstract file, but the pipe is not ordinary files, piping exists in the kernel space and not placed in the disk (named pipe on the file system has an identifier, there is no data block), access speed faster, but the smaller storage capacity, the pipe is temporary and is along with the process, when the process destroyed, all ports automatically shut down, The operating system treats all IO abstractions as files. For example, the network is also a file. This means that we can manipulate pipes in any file way.
On Linux, the mkFIFo function creates the file name to be specified, and other processes can call the open method to open the particular file and perform write and read operations.
Note that most named pipes are the same as anonymous pipes except that they work for any process.
The message queue
What is a message queue?
Message queue which is also called a message queue, also called a mailbox, is a kind of communication mechanism of Linux, this communication will be broken up into a mechanism to transfer data, an independent data block, also known as the message body, can define the type and data in the message body, overcome the defects unformatted carrying byte stream (after receiving the void * can now know its original format make) :
struct msgbuf {
long mtype; /* Message type */
char mtext[1]; /* Message body */
};
Copy the code
Like a pipe, it has the disadvantage that the maximum length of each message is capped and the entire message queue is also limited in length.
The kernel maintains a data structure struct IPC_perm for each IPC object, which has Pointers to the head and tail of the linked list, ensuring that every insertion and removal is O(1) time complexity.
1. msgget
** Creates or accesses a message queue
Prototype:
#include <sys/types.h> #include <sys/ipc.h> #include <sys/msg.h> int msgget(key_t key, int msgflag); Copy the code
Parameter: key: The name of a message queue, generated with ftok(). The message queue is a PIC resource. The key identifies the message queue. Msgflag: there are two options IPC_CREAT and IPC_EXCL. Use IPC_CREAT alone. If the message queue does not exist, it will be created. Using IPC_EXCL alone makes no sense; Both are used at the same time. If the message queue does not exist, it is created, and if it does, an error is returned.
Return value: A non-negative integer, the message queue identifier, is returned on success, and -1 on failure
2. MSGCTL function: message queue control function
Prototype:
#include <sys/types.h> #include <sys/ipc.h> #include <sys/msg.h> int msgctl(int msqid, int cmd, struct msqid_ds *buf); Copy the code
Parameters: msqID: message queue id returned by the msgget function CMD: There are three optional values, here we use IPC_RMID
- IPC_STAT sets the data in the MSqID_DS structure to the current associated value of the message queue
- IPC_SET sets the current associated value of the message queue to the value given in the MSQID_DS data structure, provided that the process has sufficient permissions
- IPC_RMID Deletes the message queue
Return value: 0 on success, -1 on failure
3. MSGSND ** Function: ** Adds a message to the message queue
Prototype:
#include <sys/types.h> #include <sys/ipc.h> #include <sys/msg.h> int msgsnd(int msqid, const void *msgp, size_t msgsz, int msgflg); Copy the code
Parameter: msgid: message queue id returned by the msgget function MSGP: pointer to the message to be sent msgze: length of the message pointed to by MSGP (excluding long int of message type) MSGFLG: default 0
Return value: 0 on success, -1 on failure
4. MSGRCV function: Receives messages from a message queue
Ssize_t MSGRCV (int MSqID, void * MSGP, size_t MSGSZ, long MSgtyp, int MSGFLG);
** Parameters: ** Same as MSGSND
** Returns the number of characters actually placed in the receive buffer on success, or -1 on failure
The characteristics of
- Unlike pipes, message queues have a kernel-dependent lifetime and are not destroyed with process destruction, requiring our display calling interface to be deleted or to be deleted using a command.
- Message queues can communicate in both directions.
- Overcome the pipe can only carry the format byte stream disadvantage.
signal
About the signal
One process can send a signal to another. A signal is a message that can be used to inform a process group that a certain type of event has been sent. The processes in the process group can take handlers to handle the event.
The unistd.h header defines the constants in the figure. When you type CTRL + C on the shell command line, the kernel sends SIGINT to each process in the foreground group, terminating the process.
We can see that the above is only 30 signal, so the operating system will be for each process to maintain an int type variable sig, use of 30 representative if there is a corresponding signal events, each process and an int type variable block, and the corresponding sig, said its 30 blockage corresponding signal (not the invocation handler). If multiple identical signals are arriving at the same time, the excess signals are normally stored in a waiting queue.
We need to understand what a process group is. Each process belongs to a process group, and multiple processes can belong to the same group. Each process has a process ID, called PID, and each process group has a process group ID, called pGID. By default, a process and its children belong to the same process group.
Software (such as detection of keyboard input is hardware) can use the kill function to send signals, kill function takes two parameters, process ID and signal type, it will send the signal type to the corresponding process, if the PID is 0, then it will be sent to all processes belonging to its own process group.
The receiver can use the signal function to add a handler to the corresponding event, which will be called once the event has occurred and if not blocked.
Linux has a complete set of functions for handling signal mechanisms.
The characteristics of
- Signal is a kind of simulation of interrupt mechanism on software level, and it is a kind of asynchronous communication mode.
- Signals can be used to directly interact with user-space processes and kernel processes, and can be used by kernel processes to notify user-space processes of system events.
- If the process is not currently running, the signal is stored by the kernel until the process resumes execution. If a signal is set to block by a process, the signal is delayed until its blocking is canceled.
- Signals have a well-defined life cycle, first generating the signal, then storing the signal until the kernel can send it, and finally processing the signal appropriately once the kernel is idle.
- Handlers can be interrupted by another handler, so this can cause concurrency problems, so ** code in handlers should be thread-safe, ** usually by setting block bitmaps to block all signals.
The socket
Socket is used to communicate with different hosts in the network. It is mostly used between clients and servers. Under Linux, there are also a series of C language functions, such as Socket, connect, bind, LISTEN and accept. It is better to analyze the socket source code in Java.
conclusion
We may never use these operations in our careers, but it is important to be aware of how processes communicate with each other as a learning experience for operating systems.
Interview for these methods we do not need to master to a very deep degree, but we must talk about what communication methods, these methods have what characteristics, applicable to what conditions, roughly how to operate, can say these, basic enough to let the interviewer is very satisfied with you.