┗ | ` O ‘| ┛ ao ~ ~ ollie add to the oil to ~ ~ ~ ~ you also move!!!!!!

Interprocess communication is a difficult problem, it can be found everywhere, interview, open source libraries, business components, function modules, today to the white people popular science

Binder principle is easy to understand after reading this article

Learning materials

Kernel space is shared between processes

Video:

  • Wheat academy _Linux interprocess communication
  • Dark horse – Linux interprocess communication

Teaching Guidance:

  1. First look at the Wheat college, wheat college teachers will lead you to learn all IPC except MMAP, easy to understand
  2. The main point here is to say that mMAP function, inside the video name is wrong, we watch the order on the line

Linux file system

Before we say anything else, we must first look at the Linux File system.

In Linux, only the kernel has the authority to manipulate the hardware directly, so all operations intended to manipulate the hardware are forwarded to the kernel and then returned to the user process

In the case of File, process A wants to open A File called open(“./ myfile.txt “). This operation is transferred to the kernel via the system service API. The kernel generates A File structure in user space with A buffer inside the File structure. It’s used to store the contents of files, and you read and write files by reading and writing buffers in the kernel

Linux 7 file types:

The file type symbol Corresponding methods
Common file _ open()
Directory file d mkdir()
Link to the file l ln-s
Pipeline file p mkfifo()/pipe()
Socket file s
Character device file c
Block device file

Among them: named pipes, character devices, block devices, sockets only file nodes, do not take up disk space

Why can’t processes communicate directly with each other

Because each process has its own separate virtual memory address, even if the virtual memory address 0110 is the same in process A and process B, the physical address of the cardboard box is different. In fact, the data is different

Physical addresses are considered to be separated by virtual memory addresses, which are not accessible to each other, and data is not visible between processes for security purposes, protecting some processes from doing bad things, stealing, tampering, and other processes’ data

How do processes communicate with each other

Consider the case where two processes open the same File

Q: Do two processes open the same file at the same time and get different file descriptors?

A: When two processes open the same File, each process gets a different File descriptor. The File descriptor is unique to each process. But there is only one copy of the same File mapped to the buffer in the kernel

In this way, the buffer becomes A bridge. Process A writes data to the buffer, and process B can read the data from the buffer. Interprocess communication is realized by using the mechanism of File

So a lot of people say that Linux process communication is based on the idea of file IO, kernel space is shared by all processes, which is the main premise of all process communication technology, there must be an intermediary that everyone can access, or it is really impossible to achieve communication between mutually isolated objects

The Communication modes of Linux processes are as follows:

  • The pipe
    • Anonymous pipe
    • Named pipe
  • file
  • signal
  • The Shared memory
  • The message queue
  • The Socket Socket
  • mmap

Note Socket, said before are 2 processes in the same kernel communication, and Socket refers to 2 processes in the communication through 2 kernels, Socket 2 end, client, server have their respective grid kernel, IPC is implemented between 2 kernels, the essence of the operation kernel file structure

Let’s look at some other explanations:

When the open() function opens or creates or opens a file, the kernel space creates a buffer. With this buffer, the user space can read and write data into the buffer through the write() and read() functions. The close() function frees the buffer. All IPC methods in Linux are based on IO files, but the open function has a different form

Difference between process communication and thread communication:

  • Interprocess communication:This is not possible in user space and can only be communicated through the Linux kernel
  • Interthread communication:You can do it in user space, you can do it in global variables

Some of the most commonly used IPC communications are:

  • The pipe-> This operation is the simplest, do not need to do their own data synchronization
  • signal-> The system has the lowest overhead, but can only send messages specified by the system. It is generally used with other IPC methods, especially shared memory
  • The Shared memory-> To transfer data between processes, you need signal assistance
  • Socket-> most secure, use this to ensure data integrity

Characteristic difference:

  • Pipes and queues do not need to synchronize themselves
  • Files, shared memory, and Mmap need to be synchronized by themselves

The pipe

A pipe is a special file, essentially a queue, FIFE first-in, first-out algorithm, the left side of the queue is the write() function to write data, the right side of the queue is the read() function to read data, when there is no data read process will be blocked, into a light sleep state

The pipeline object exists in the kernel space to store data in queues, takes the file structure in user space as the anchor point, and transforms the cache of files in the kernel space as the pipeline, so as to realize the communication between two processes, one process writes, the other process reads.

This model looks like the producer and consumer model introduced in the Linux synchronization mechanism. There are two semaphores at the two ends of the queue respectively to control write and read, so as to achieve exclusive operation of write and read

Features:

  • The process ends, space is freed, and the pipe no longer exists
  • Things in the pipeline, read the pipeline to delete
  • If there is nothing to read in the pipe, it will block
  • There is no more space in the pipe to write to, the write operation will block, and the process will fall into a shallow sleep

The data flow is: process A user space -> kernel space -> process B user space, data in memory through two copies

1. Anonymous pipes

Create an unnamed pipe using the: pipe() function

  • Parameter: Need to pass a 2 size int array, which contains 2 file description parent, one for write, one for read
  • Return value: 0 success, -1 failure
int file[2];
char value[] = "hello world!";
char readBuffer[128] = {0};

// Create a pipe
int buffer = pipe(file);
if( buffer < 0) {// Failed to create a pipe
    return - 1;
}
// Write data to the pipe
int result = write(file[0],value,sizeof(value));
if( result==- 1) {// Write failed
}

// Create a child process
int pid = fork();

if( pid==0) {// Here is the child process, reading the data in the pipe
	int result = read(file[1],readBuffer,sizeof(readBuffer));
    if( result==- 1) {// Failed to read}}Copy the code

The size of the pipe is 64K, which can be viewed with the command: ulimit -a, but also said 4K, anyway must be a multiple of 4K a page

Disadvantages:

  • File [0],file[1],file[1],file[0],file[1],file[0],file[1

2. Named pipes

The unnamed pipe described above cannot implement communication between unrelated processes. Naturally, there is a way to implement communication between unrelated processes, which is named pipe

Create a named pipe using the: mkfifo() function

  • Parameters: file name and file access rights
  • Return value: 0 success, -1 failure
char value[] = "hello world!";
char readBuffer[128] = {0};

// Create a named pipe
int buffer = mkfifo("./myfifo");
if( buffer<0) {// Failed to create
    return - 1;
}

// Open the file
int file = open("./myfifo",O_WRONLY);
if( file<0) {// Failed to open file
    return - 1;
}

// Write data
write(file,value,sizeof(value));

// Read data
read(file,readBuffer,sizeof(readBuffer))
Copy the code

Features:

  • Does not occupy disk space
  • The mkfifo() function only creates file nodes, not create pipes in the kernel
  • Only if the open() function opens this file will the pipe be created in the kernel

The named pipe can actually see the file node because it doesn’t take up disk space, so the file size is 0

signal

The kernel receives the signal and forwards it to the specified process. The process processes the signal according to the default Liunx defined behavior, Hanler, or custom processing of the signal. The signal is executed immediately. Plan the tasks you are performing

For those of you who read the previous section on operating system principles, you should know that interrupts are the only way for a process to switch from user memory to the kernel process. For different interrupt types, the system packages a number of system call apis for user processes to use. Software can also send soft interrupts, known as 80 interrupts

Signals use soft interrupts. Linux defines 64 signals:We can realize inter-process communication by sending signals to the formulation process, which is very clever. The overall signal looks like the formulation task of The Android Handle sending system

Send a signal

  • The kill () function

    • Parameters:
      • PID Indicates the ID of the process that receives signals
      • SIG signal ID
    • Return value: 0 success, -1 failure
  • The raise() function -> can only be sent to its own process

    • Parameters:
      • SIG signal ID
    • Return value: 0 success, -1 failure
  • The alarm() function -> can only send to itself, and can only send timed messages

    • Parameters:
      • Second Delay time
    • Return value: 0 success, -1 failure

The raise() function is equivalent to raise(getPid (),SIG). The raise() function is the same as raise(getpid(),SIG).

Pause (),sleep(), pause(), pause(),sleep(

Kill, raise and WaitPID are generally used in combination to realize the communication and control between father and son processes. The code is very simple and I will not write it

The keyboard is a hardware interrupt, the driver, and then the driver sends a SIGINT signal

Customize received signals

  • Signal () function -> After receiving a signal, you can do whatever you want instead of the default operation

    • Parameters:
      • SIG signal ID
      • Handler Custom processing functions: SIG_IGN ignores signals and SIG_DFL performs default processing
    • Return value: 0 success, -1 failure

Write a code for you to experience:


void handler(int sig){
	// Custom processing
}

// Register custom signal handling methods
signal(9,handler);

// Create a child process
int pid = fork();
if( pid<0) {// The child sends a signal to the parent
	kill(getppid(),9);
}
Copy the code

Linux signals are very simple to use, just like the Java Thread control method. They can be used to send messages between processes, telling peers to do their work

The Shared memory

Create a cache in kernel space, and then a cache in user space, and then implement memory mapping between the two, that is, the physical memory address allocated by the kernel cache is given to the user program. Both processes use the same shared memory object, so that both processes have an address that actually points to the same physical address. Memory operations of either process affect the other process

Note that shared memory has no memory copy problem, which is what we always say: 0 copy. The whole process, all operations are done on a piece of physical memory, the kernel plays a bridging role

Features:

  • The communication between parent and child processes can be realized, and the communication between unrelated processes can be realized
  • Shared memory requires the user to handle the synchronization problem, unlike pipes that have already handled it for you, here you have to do it yourself, so the implementation logic is complicated, and synchronization is prone to write errors and problems
  • Once a shared memory object is created, it remains in the kernel until it is deleted
  • A shared memory object is not like a pipe in that the data remains in shared memory after being read

Use steps: ->

  • Parent-child process communication:
    • 1. Shmget () creates shared memory objects in kernel space
    • 2. Shmat () creates a space in user space and implements memory address mapping of shared memory objects in kernel space
  • Unassociated process communication:
    • 1. Key =ftok(filename) Generates a key, which actually creates a file and uses the cache of the file in the kernel as a shared memory object
    • 2 ShMGET (key) Indicates the file association between shared memory objects
    • 3 Shmat () creates a space in user space and implements memory address mapping for shared memory objects in kernel space

Command:

  • Ipcs - m:View all shared memory objects in the system
  • Ipcrm -m id:Delete the specified shared memory object

Shmget () function – >

* Function: Creates a shared memory object in the kernel. * Parameters: * key * IPC_PRIVATE: parent-child process communication mode * key= Ftok (filename) : Unrelated process communication mode * size * flag Operation permission * Returned value: ID, -1 FailedCopy the code

Ftok () function – >

* Function: generate a file and return a key for later use as a shared memory object * parameter: * filepath: file after * key: write random * Return value: key, -1 failedCopy the code

Shmat () function – >

* Parameters: * shmid: ID of the shared memory * addr: start address of the user space, NULL The system automatically allocates the user space address * shmFlag: operation permission, 0 read-write, SHM_RDONLY read-only * Returned value: NULL failedCopy the code

SHMDT () function – >

Parameter: * addr: user space address * Returned value: -1 FailedCopy the code

SHMCTL () function – >

Parameters: * SHmid: Shared memory ID * CMD: tag * IPC_STAT get attribute * IPC_SET Set attribute * IPC_RMID Delete object * shm_dt: Struct, the data obtained by options 1 and 2 is written to this parameter * return value: -1 failedCopy the code

The fgets () function – >

* Write data from keyboard * Parameters: * shmid: shared memory ID * size: file size * stdin: from hard disk * Returned value: -1 FailedCopy the code

Memcpy () function – >

* Write data * Parameters: * shmid: SHARED memory ID * value: data * size: size * Returned value: -1 FailedCopy the code

The printf () function – >

* Shmid: Shared memory ID * Return value:Copy the code

The following example is not strictly synchronous and is executed in the order that the sender runs first and the receiver runs later

Parent-child process communication

The parent process is constantly writing data from the keyboard, the child process is constantly reading data, and the middle two processes are notified by the kill() signal

In the communication between parent and child processes, one shared memory object is generally used for one-way data transmission, and two shared memory objects are used for two-way data transmission

// Custom signal processing, wake up and do nothing, let the process continue to execute
void hand(int sig){
	return 1;
}

char *buffer;
int shmid = shmget(IPC_PRIVATE,128,ipc_create | 0777);
if(shmid<0) {// Failed to create
    return - 1;
}

int cid = fork();
if(cid>0) {// The parent process executes
    // Map shared memory
    buffer = (char *)shmat(shmid,NULL.0);
    // Register signal processing
    signal(2,hand);
	while(1) {// Loop the keyboard to write, and then inform the receiver to read
    	fgets(buffer,128.stdin);
        // Send a signal to inform the receiver that I am finished and you can read
        kill(cid,1);
        // Pause yourselfpause(); }}if(cid==0) {// The child executes
    // Map shared memory
    buffer = (char *)shmat(shmid,NULL.0);
    // Register signal processing
    signal(1,hand);
	while(1){
    	pause();
    	/ / read cycle
    	printf(buffer);
        // I'm finished, you can write
        kill(getppid(),2); }}// Free up user space
shmdt(buffer);
// Free up kernel space
shmctl(shmid,IPC_RMID,null);
Copy the code

No relational process communication

-> < p style = “max-width: 100%; clear: both; min-height: 1pt;

  1. The sender first writes its PID to the shared memory and waits
  2. As soon as the receiver starts up, it takes the PID out of the shared memory, and then writes its own PID into it, notifying the other side with the PID
  3. The sender wakes up and gets the PID of the opposite process from shared memory, which can realize signal communication

The sender

struct bufffer{
	int pid;
    char buffer[124];
}

int oid;

// Custom signal processing, wake up and do nothing, let the process continue to execute
void hand(int sig){
	return 1;
}

int key = ftok("./a.c"."a");
if(key<0) {// Failed to create
    return - 1;
}

int shmid = shmget(key,128,ipc_create | 0777);
if(shmid<0) {// Failed to create
    return - 1;
}

// Map shared memory
struct buffer = (struct *)shmat(shmid,NULL.0);
if(buffer == null){
	// Mapping failed
    return - 1;
}

// Register signal processing
signal(2,hand);

// Write your PID to shared memory first
buffer->buffer = getpid();
// Then wait
pasue();
// Wake up and get the other end of the PID, so you can use the PID control
oid = buffer->pid;

while(1) {// Loop the keyboard to write, and then inform the receiver to read
	fgets(buffer->buffer,128.stdin);
	// Send a signal to inform the receiver that I am finished and you can readThe kill (oid,1);
 	// Pause yourself
	pause();
}

// Free up user space
shmdt(buffer);
// Free up kernel space
shmctl(shmid,IPC_RMID,null);
Copy the code

The receiving end

struct bufffer{
	int pid;
    char buffer[124];
}

int oid;

// Custom signal processing, wake up and do nothing, let the process continue to execute
void hand(int sig){
	return 1;
}

int key = ftok("./a.c"."a");
if(key<0) {// Failed to create
    return - 1;
}

int shmid = shmget(key,128,ipc_create | 0777);
if(shmid<0) {// Failed to create
    return - 1;
}

// Map shared memory
struct buffer = (struct *)shmat(shmid,NULL.0);
if(buffer == null){
	// Mapping failed
    return - 1;
}

// Register signal processing
signal(1,hand);

// Get the pid of the other process in the share
oid = buffer->buffer;
// Add your own pid
buffer->buffer = getpid();
// Send a notification to inform the other side to take pid
kill(oid,2);

while(1){
    pause();
    / / read cycle
    printf(buffer->buffer);
    // I'm finished, you can write
    kill(oid,2);
}

// Free up user space
shmdt(buffer);
// Free up kernel space
shmctl(shmid,IPC_RMID,null);
Copy the code

As you can see, shared memory has a good performance of 0 copy, but the data control needs to be done by ourselves, and the synchronization logic is complex, so it is easy to write wrong

The queue

A queue is a queue created in the kernel, which can realize communication between parent and child processes and unrelated processes. The API use is similar to shared memory, but the difference is the function name is different

Features:

  • Communication between parent and child processes and unrelated processes can be realized
  • Queues can store multiple types of messages in the form of struct structures
  • Queues are already synchronized, read blocked, and write blocked

The above mentioned pipe is also a queue, but the pipe queue has restrictions, must be FIFO, and the queue data can only be stored on one end of the write, the other end of the storage, but the queue can be both read and write

Queue to achieve two-way communication, the general is to use the thread, process within a thread read, a thread write, so that 2 can not delay

Command:

  • Ipcm - q:Query all queue objects

Msgget () function – >

Parameter: * key * IPC_PRIVATE: parent-child process communication mode * key= Ftok (filename) : Unrelated process communication mode * flag Operation permission * Returned value: ID, -1 FailedCopy the code

MSGCTL () function – >

Parameters: * msgid: queue ID * CMD: tag * IPC_STAT get attribute * IPC_SET Set attribute * IPC_RMID Delete object * shm_dt: Struct, the data obtained by options 1 and 2 is written to this parameter * return value: -1 failedCopy the code

Magsnd () function – >

Parameter: * msgid: queue ID * value: data structure * size: number of bytes in the body of the structure * flag: * IPC_NOWAIT: non-blocking, the function will return * 0 even after the message is sent: blocking, the function will return * after the message is sent: -1 failedCopy the code

Queue message structure ->

Struct msgbuf{long type: message type char buf[XXX]; Message body}Copy the code

Magrcv () function – >

Parameters: * msgid: queue ID * msgbuf: receive data buffer * size: message body size * msgType: Message type * 0: the first data in the queue * >0: the first matched data in the queue * <0: * flag: * IPC_NOWAIT: non-blocking mode. The function will return even before the message is sentCopy the code

Irrelevant queue communication

  • The sender:
struct msgbuf{
	long type;
	char data[128];
}

struct buffer;
// Set the message type
buffer.type = 100;

int key = ftok( "./a.c"."a" );
int msgid = msgget(key,IPC_CREAT | 0777);

while(1) {// Clear the local cache each time
	memset(buffer.data,0.128);
    // Loop to read data from the keyboard
	fget(buffer.data,128.stdin);
    // Send data to the message queue
	msgsnd(msgid,(void *)buffer,strlen(buffer.data));    
}

msgctl(msgid,IPC_RMID,0);
Copy the code
  • The receiver:
struct msgbuf{
	long type;
	char data[128];
}

struct buffer;

int key = ftok( "./a.c"."a" );
int msgid = msgget(key,IPC_CREAT | 0777);

while(1) {// Clean up the local cache first
	memset(buffer.data,0.128);
    // Read data from the queue
	msgrcv(msgid,(void *)buffer,100.0);    
}

msgctl(msgid,IPC_RMID,0);
Copy the code

Queues are much easier to use than shared memory because they don’t require complex synchronization control, but they don’t have the performance advantage of copying memory twice

light

Semaphore is a set of semaphore, the actual use is also a semaphore operation signal, etc

Command:

  • Ipcs -s:View all lights

The code is too lazy to write, similar to the above, the trouble is that there are two structures in the method, each time the semaphore operation in the semaphore needs to wrap the semaphore into a structure, modify the data and then write to the semaphore

MMAP shared storage

Using the mmap() function and the memory mapping mechanism, process communication can also be achieved

Shared Storage ->

This is the mmap() function, swap. Shared storage itself refers to files. When a file is opened, the system creates a buffer in the kernel space for the user to cache the data read and written to the file. All reads and writes to the file are interactions with the cache in the kernel space. Shared storage further allows the user process to map the memory address of the buffer in the kernel space of the file, creating a memory space in the user space that points to the physical memory where the kernel buffer resides, so that any operation on the user space is an operation on the kernel buffer. This virtually saves one copy of data from user space to kernel space

If multiple processes are achieved with the same file Shared storage, process A user space buffer, process B user space buffer, the kernel file buffer points to the same piece of physical memory, then the process A in his writing of the buffer in user space is to process the buffer B write, don’t need to pipe, the data go through: A user space -> kernel -> B user space, 2 memory copy operations. Just like shared memory, zero copy

Two memory copies

With pipes and shared memory you can understand that MMAP allows you to manipulate file data in the same way that you can manipulate memory addresses. You can only use write and read files, but memory addresses have too many functions

File descriptors and MMAP mapping areas are stored in user-space high-level kernel-state PCBS

Features:

  • You need to synchronize the data yourself

Mmap () function – >

* Add: map area memory address, default Linux kernel specified, pass NULL * leng: map area size * port: File permissions operation * PROT_WRITE PROT_READ * * PROT_READ | PROT_WRITE * PROT_EXEC: executable, generally this is for the use of library functions * PROT_NONE: do not allow access, this is driven with * flag: MAP_SHARED: changes in a mapping area are reflected to disks * MAP_PRIVATE: changes in a mapping area are not reflected to disks, and this mode does not enable inter-process communication * MAP_NONYMOUS: creates anonymous mapping areas * fd: file descriptor * offset: The offset of the mapping file is 4K integer multiple, so it's not necessary to ioso the whole song file to be mapped into it. We can just map a part of the file by ioso. The file starts from offset and leng is so long. Offset =4096, size=len-4096 * Returned value: the first address of the mapping area, -1 failedCopy the code

Munmap () function – >

Parameter: * add: mapping area memory address, default NULL * leng: mapping area size * Returned value: -1 failedCopy the code

Parent-child process communication

As long as the parent process mmap() before fork(), the parent process shares the mapping area because the memory is identical

// Create a file
int file = open( "./testmap", O_REWR|O_CREAT,0777);
// Set the file size
ftruncate(file,128);
// Calculate the file size
int len  = lseek(file,0,);

// Create an Mmap map
char *buffer = mmap(NULL,len,PROC_WRITE|PROC_READ,MAP_SHARED,file,0);
if(buffer==MAP_FAILED){
	// Mapping failed
    return - 1;
}

int pid = fork();
if(pid>0) {// The parent process operates
    // Pointer writes data
    strcpy(buffer,"aaa");
}

if(pid==0) {// Subprocess operation
    / / read the data
    printf(buffer);
}

// Release the mapping area
munmap(buffer,len);
// Close the file
close(file);
Copy the code

An anonymous mapping

Linux provides a macro called MAP_NONYMOUS, so the mmap() function no longer needs to open() the file itself

// Create an Mmap map
char *buffer = mmap(NULL.128,PROC_WRITE|PROC_READ,MAP_SHARED|MAP_NONYMOUS,- 1.0);
if(buffer==MAP_FAILED){
	// Mapping failed
    return - 1;
}

int pid = fork();
if(pid>0) {// The parent process operates
    // Pointer writes data
    strcpy(buffer,"aaa");
}

if(pid==0) {// Subprocess operation
    / / read the data
    printf(buffer);
}

// Release the mapping area
munmap(buffer,128);
Copy the code

Irrelevant process communication

  • The sender
// Create a file
int file = open( "./testmap", O_REWR|O_CREAT,0777);
// Set the file size
ftruncate(file,128);
// Calculate the file size
int len  = lseek(file,0,);

// Create an Mmap map
char *buffer = mmap(NULL,len,PROC_WRITE|PROC_READ,MAP_SHARED,file,0);
if(buffer==MAP_FAILED){
	// Mapping failed
    return - 1;
}

memcpy(buffer,"aaa".6);

// Release the mapping area
munmap(buffer,len);
// Close the file
close(file);
Copy the code
  • The receiving end
// Create a file
int file = open( "./testmap", O_REWR|O_CREAT,0777);
// Set the file size
ftruncate(file,128);
// Calculate the file size
int len  = lseek(file,0,);

// Create an Mmap map
char *buffer = mmap(NULL,len,PROC_WRITE|PROC_READ,MAP_SHARED,file,0);
if(buffer==MAP_FAILED){
	// Mapping failed
    return - 1;
}

printf(buffer);
// Release the mapping area
munmap(buffer,len);
// Close the file
close(file);
Copy the code