Analyzing Linux interprocess communication

Purpose of interprocess communication

Data transfer: one process needs to send its data to another process
Resource sharing: multiple processes share the same resources.
Notification event: a process needs to send a message to another process or group of processes to notify it (them) of an event (such as notifying the parent process when the process terminates).
Process control: some processes want to fully control the execution of another process (such as the Debug process). At this time, the control process wants to be able to intercept all traps and exceptions of another process and know its state changes in time

Communication classification

Inter process communication is divided into:
1. Pipeline
2.System V interprocess communication
3.POSIX interprocess communication

The Conduit

What is a pipe?

Pipeline is the oldest form of interprocess communication in Unix.
We call a data flow from one process to another a "pipe"“
For example: who | wc -l command

The who process outputs the data to the pipeline through standard output. The wc-l process reads the data from the pipeline through standard input, and then the user can see the data through standard output.

Anonymous Pipe

Principle of anonymous pipeline

Anonymous pipeline: only applicable to blood related processes


Process communication: let two processes see the same file resource. The child process takes the parent process as the template. The two processes can see the same file resource in physical memory and can read and write this file.
Note: this resource is maintained by the operating system. When the parent-child process writes, the data in this file buffer will not be copied on write.

pipe function

pipe function functions: creating anonymous pipes

Function prototype:

int pipe(int fd[2]);

parameter

fd: file descriptor array, where fd[0] represents the read side and fd[1] represents the write side

Return value

0 is returned for success and error code is returned for failure

Let's fork a child process, let the child process write and the parent process read.

  1 #include<stdio.h>                                                                                                   
  2 #include<unistd.h>
  3 #include<string.h>
  4 
  5 int main()
  6 {
  7   int pipefd[2]={ 0 };
  8   pipe(pipefd);
  9 
 10   pid_t id = fork();
 11 
 12   if(id == 0)
 13   {
 14     //child write
 15     close(pipefd[0]);
 16     const char* msg="I am child.....!\n";
 17     while(1)
 18     {
 19       write(pipefd[1],msg,strlen(msg));
 20 
 21     }
 22   }
 23   else
 24   {
 25     //parent read  
 26     close(pipefd[1]);
 27     char buffer[64];
 28     while(1)
 29     {
 30       ssize_t s = read(pipefd[0],buffer,sizeof(buffer)-1);
 31       if(s > 0)
 32       {
 33         buffer[s] = 0;
 34         printf("parrent get message:%s\n",buffer);
 35       }
 36     }
 37    
 38   }
 39   return 0;
 40 } 


The parent process has been reading.

1. Let's let the subprocess write once every 2 seconds

Look at the results:

Our parent process is an endless cycle and will read all the time, but it reads along with the rhythm of the child process. The conclusion is as follows:

1. If the writer does not close the file descriptor and does not write for a long time, the reader will enter the blocking state for a period of time

2. Let the parent process sleep this time


This time it's the child process's turn to follow the rhythm of the parent process.

2. When the writing conditions are not met during actual writing, the writing end will be blocked. From the above, it can be concluded that if the read or write conditions of the pipeline are not met, the read and write end will be blocked.

3. Turn off the subprocess file descriptor

   12   if(id == 0)
   13   {
   14     //child write
   15     close(pipefd[0]);
   16     const char* msg="I am child.....!\n";
   17     int count = 0;
   18     while(1)
   19     {
   20       write(pipefd[1],msg,strlen(msg));
   21       printf("CHILD:%d\n",count++);
   22       if(5 == count)
   23       {
   24         close(pipefd[1]);//Turn off writing
   25         break;
   26       }                                                                                                           
   27     }
   28     exit(2);

Redirect the results to log Txt:

3. The writer does not write and turns off the file descriptor. After reading the data, the reader returns 0, indicating that it will read to the end of the file.

Let the child process read all the time, turn off the reading end of the parent process and read it for a period of time

 1 #include<stdio.h>                                                                                              
  2 #include<unistd.h>
  3 #include<string.h>
  4 #include<stdlib.h>
  5 int main()
  6 {
  7   int pipefd[2]={ 0 };
  8   pipe(pipefd);
  9 
 10   pid_t id = fork();
 11 
 12   if(id == 0)
 13   {
 14     //child write
 15     close(pipefd[0]);
 16     const char* msg="I am child.....!\n";
 17     int count = 0;
 18     while(1)
 19     {
 20       write(pipefd[1],msg,strlen(msg));
 21       printf("CHILD:%d\n",count++);
 22     }
 23   }
 24   else
 25   {
 26     //parent read  
 27     close(pipefd[1]);
 28     char buffer[64];
 29     int count = 0;
 30     while(1)
 31     {
 32       ssize_t s = read(pipefd[0],buffer,sizeof(buffer)-1);
 33       if(s > 0)
 34       {
 35         buffer[s] = 0;
 36         printf("parrent get message:%s\n",buffer);
 37         sleep(1);
 38       }
 39       if(count++ == 5)
 40       {
 41         close(pipefd[0]);//Close the reader
 42       }
 43     }
 44    
 45   }
 46   return 0;                                                                                                    
 47 }

Start monitoring: while:; do ps axj | head -1 && ps axj|grep pipe |grep -v grep; echo “###########”; sleep 1; done


The child process has become a zombie.

4. The reader is closed, the writer process is still writing, and may be killed by the OS in the future. You don't read it. It's meaningless for me to write. Writing is a waste of resources, and the OS will never allow inefficiency and waste of space.
Prove:

 25   else
 26   {
 27     //parent read  
 28     close(pipefd[1]);
 29     char buffer[64];
 30     int count = 0;
 31     while(1)
 32     {
 33       ssize_t s = read(pipefd[0],buffer,sizeof(buffer)-1);
 34       if(s > 0)
 35       {
 36         buffer[s] = 0;
 37         printf("parrent get message:%s\n",buffer);
 38         sleep(1);
 39       }
 40       if(count++ == 5)
 41       {
 42         close(pipefd[0]);
 43         break;
 44       }
 45     }
 46      int status = 0;
 47      waitpid(id,&status,0);
 48      printf("chiidl get signal:%d\n",status & 0x7F);
 49   }
 50   return 0;
 51 } 

The sub process codes above are the same. See the results:

The subprocess was killed by signal 13. So let's not write like this. If we don't write, close the writing end, and then close the reading end when the reading end is finished.

fork shared pipeline principle


Before fork, their file descriptors are on. After fork, turn off their unused file descriptors.

Understanding pipeline from the perspective of file descriptor

1. Create pipeline for parent process:

2. Parent process creates child process

3. The parent process closes fd[1], and the child process closes fd[0], which can also be interchanged.

Pipeline characteristics

1. A pipeline can only be used for communication between processes with common ancestors (processes with kinship); usually, a pipeline is created by a process, and then the process calls fork. After that, the pipeline can be applied between parent and child processes.
2. Pipeline provides flow service
3. Generally speaking, the process exits and the pipeline is released, so the pipeline life cycle changes with the process
4. Generally speaking, the kernel will synchronize and mutually exclusive pipeline operations
5. The pipeline is half duplex, and the data can only flow in one direction; When both parties need to communicate, two pipelines need to be established

name pipes

If we want to exchange data between unrelated processes, we can use FIFO files to do this, which is often called named pipes. Named pipes are a special type of file.

The difference between anonymous pipes and named pipes

Anonymous pipes are created and opened by the pipe function.
The named pipe is created by the mkfifo function and opened with open
The only difference between FIFO (named pipe) and pipe (anonymous pipe) is that they are created and opened in different ways, but once these tasks are completed, they have the same semantics.

Named pipe open rule

If the current open operation is to open FIFO for reading
O_NONBLOCK disable: blocks the FIFO until a corresponding process opens it for writing
O_NONBLOCK enable: returns success immediately
If the current open operation is to open FIFO for writing
O_NONBLOCK disable: blocks until a corresponding process opens the FIFO for reading
O_NONBLOCK enable: failure is returned immediately with error code ENXIO

Named pipe instance

Function prototype:

int mkfifo(const char *filename,mode_t mode);

With the foundation of anonymous pipes, it becomes easier to look at named pipes. Named pipeline allows two unrelated processes to realize communication, and directly write two programs to realize passage. A client writes information and a server reads information.

client.c

  1 #include<stdio.h>
  2 #include<unistd.h>
  3 #include<sys/types.h>
  4 #include<sys/stat.h>
  5 #include<fcntl.h>
  6 
  7 #define FIFO_FILE "./fifo" 
  8 
  9 int main()
 10 {
 11 
 12  int fd = open(FIFO_FILE,O_WRONLY);
 13  if(fd >= 0)
 14  {
 15     char buf[64];
 16     while(1)
 17     {
 18       printf("Please enter information:");
 19       fflush(stdout);
 20       ssize_t s = read(0,buf,sizeof(buf)-1);
 21       if(s > 0)
 22       {
 23         buf[s] = 0;
 24         write(fd,buf,s);
 25       }
 26     }
 27  }
 28   return 0;
 29 }                                                                                                               

server.c

  1 #include<stdio.h>                                                                                               
  2 #include<unistd.h>
  3 #include<sys/types.h>
  4 #include<sys/stat.h>
  5 #include<fcntl.h>
  6 #define FIFO_FILE "./fifo"
  7 int  main()
  8 {
  9   umask(0);
 10   if(-1 == mkfifo(FIFO_FILE,0666))
 11   {
 12       perror("mkfifo");
 13       return 1;
 14   }
 15   
 16   int fd = open(FIFO_FILE,O_RDONLY);
 17    if(fd >= 0)
 18    {
 19      char buf[64];
 20      while(1)
 21      {
 22        ssize_t s = read(fd,buf,sizeof(buf) - 1);
 23        if(s > 0)
 24        {
 25           buf[s] = 0;
 26           printf("client# %s",buf);
 27        }
 28        else if(s == 0)
 29        {
 30           printf("client quit,me too\n");
 31           break;
 32        }
 33        else 
 34        {
 35          perror("read");
 36          break;
 37        }
 38      }
 39    }
 40   return 0;
 41 }     

The effects are as follows:

Two unrelated processes can communicate.

system V shared memory

The above pipes pass through files, and system V directly bypasses the file layer in memory.

Shared memory diagram

Schematic diagram of shared memory:

First, open a space in the physical memory and generate the mapping relationship through the page table. Shared memory is the fastest way of process communication.

Shared memory data structure

To manage these memories, the OS first describes them and then organizes them. Look at the data structure describing shared memory:

 struct shmid_ds {
 struct ipc_perm shm_perm; /* operation perms */
 int shm_segsz; /* size of segment (bytes) */
 __kernel_time_t shm_atime; /* last attach time */
 __kernel_time_t shm_dtime; /* last detach time */
 __kernel_time_t shm_ctime; /* last change time */
 __kernel_ipc_pid_t shm_cpid; /* pid of creator */
 __kernel_ipc_pid_t shm_lpid; /* pid of last operator */
 unsigned short shm_nattch; /* no. of current attaches */
 unsigned short shm_unused; /* compatibility */
 void *shm_unused2; /* ditto - used by DIPC */
 void *shm_unused3; /* unused */
};

Shared memory function

Create shared memory

Create a function prototype for shared memory:

int shmget(key_t key, size_t size, int shmflg);

The first parameter: key, the unique identifier of the shared memory
Parameter 2: size of shared memory
The third parameter: represents the permission of the shared memory
Return value: successfully returns a non negative integer, that is, the identification code of the shared memory segment; Failure returned - 1

The third parameter shmflg, commonly used in combination, is IPC_CREAT | IPC_EXECL, if the created shared memory exists, an error will be returned, so the created shared memory is brand new.

First, get a key value. Through the ftok function, it has nothing to do with memory. It determines a unique number through an algorithm,
Function prototype:

key_t ftok(const char *pathname, int proj_id);

Header file:

#include <sys/types.h>
#include <sys/ipc.h>

You can view more details of ftok through the man manual.
The function of ftok function: generate the key type System V IPC key using the ID of the file named by the given pathname (the existing accessible file must be referenced) and the least significant 8 bits of proj_id (must be non-zero).
Return value: a key value is returned on success, and - 1 is returned on failure

com.h

First use the macro to connect the path and proj_ Set the ID

  1  #pragma once                                                                                                             
  2 #define  PATHNAME "/home/ge"
  3 #define PROJ_ID 0x688
    1 #include<stdio.h>
    2 #include"com.h"
    3 #include<sys/ipc.h>
    4 #include<sys/shm.h>
    5 #include<unistd.h>
    6 #include<sys/types.h>
    7 int main()
    8 {
    9 
   10   key_t k = ftok(PATHNAME,PROJ_ID);
   11   printf("key_t:%p\n",k);
   12 
   13   //Create shared memory
   14   int shmid = shmget(k,SIZE,IPC_CREAT|IPC_EXCL);                                                           
   15   if(shmid < 0)
   16   {
   17     perror("shmget");
   18     return 1;
   19   }
   20   printf("shmid:%d\n",shmid);
   21 
   22   return 0;
   23 }

Use the command: ipcs -m to view shared memory information


You can see that the value of k is the same as that of shmid. ipcs-m see the following table for details:

This is creating shared memory.

When the shared memory exists, an error is reported when the program is running, indicating that the memory already exists.
Delete shared memory command:

ipcrm -m shmid


If you delete it manually, it's troublesome. Next, you'll delete the shared memory function.

Free shared memory

Free shared memory function:

int shmctl(int shmid, int cmd, struct shmid_ds *buf)

Parameter Description:
The first parameter: shmind is the return value of the above shmget function
The second parameter: what do you want to do, specific actions
Parameter 3: get or set the data structure of shared memory
Return value:
0 is returned for success and - 1 is returned for failure;

The second parameter is the common command:

We use IPC directly_ Rmid is enough.

    1 #include<stdio.h>
    2 #include"com.h"
    3 #include<sys/ipc.h>
    4 #include<sys/shm.h>
    5 #include<unistd.h>
    6 #include<sys/types.h>
    7 int main()
    8 {
    9 
   10   key_t k = ftok(PATHNAME,PROJ_ID);
   11   printf("key_t k :%p\n",k);
   12 
   13   //Create shared memory
   14   int shmid = shmget(k,SIZE,IPC_CREAT|IPC_EXCL);
   15   if(shmid < 0)
   16   {
   17     perror("shmget");
   18     return 1;
   19   }
   20   printf("shmid:%d\n",shmid);
   21   sleep(3);
   22   //Delete the shared memory and leave the third parameter blank temporarily
   23   shmctl(shmid,IPC_RMID,NULL);
   24   sleep(2);                                                                                              
   25   return 0;
   26 }

After creating the shared memory, we delete it 3 seconds later. At this time, open the monitoring exit and use the monitoring command:

while :;do ipcs -m;echo "#########";sleep 1;done


From the beginning when there is no shared memory to the creation, the shared memory has been released after 3 seconds.

Associated shared memory

We created and deleted shared memory by. Then the next step is to associate shared memory

Function prototype:

 void *shmat(int shmid, const void *shmaddr, int shmflg)

Parameter Description:
The first parameter shmid: shared memory ID
The second parameter: specify the address of the link, usually set to NULL, so that the system can find a suitable location in the virtual memory space
Parameter 3: associated shared memory is the property set
Options for the third parameter:

We can directly pass the third parameter to 0.

    1 #include<stdio.h>
    2 #include"com.h"
    3 #include<sys/ipc.h>
    4 #include<sys/shm.h>
    5 #include<unistd.h>                                                                                                                
    6 #include<sys/types.h>
    7 int main()
    8 {
    9 
   10   key_t k = ftok(PATHNAME,PROJ_ID);
   11   printf("key_t k :%p\n",k);
   12 
   13   //Create shared memory
   14   int shmid = shmget(k,SIZE,IPC_CREAT|IPC_EXCL);
   15   if(shmid < 0)
   16   {
   17     perror("shmget");
   18     return 1;
   19   }
   20   printf("shmid:%d\n",shmid);
   21   sleep(3);
   22  //Associated shared memory
   23  void *str = shmat(shmid,NULL,0);
   24 
   25   //Delete the shared memory and leave the third parameter blank temporarily
   26   shmctl(shmid,IPC_RMID,NULL);
   27   sleep(2);
   28   return 0;
   29 }


When we see that the number of associations is still 0, we use sheget to pass in permissions

int shmid = shmget(k,SIZE,IPC_CREAT|IPC_EXCL|0666);   


At this time, the number of associated shared memory ranges from 0 to 1, and then to delete shared memory.

Shared memory disassociation

Function prototype:

int shmdt(const void *shmaddr);

Parameter Description:
shmaddr: pointer returned by shmat
Return value: 0 is returned successfully; Failure returned - 1
Note: separating the shared memory segment from the current process does not mean deleting the shared memory segment

    7 int main()
    8 {
    9 
   10   key_t k = ftok(PATHNAME,PROJ_ID);
   11   printf("key_t k :%p\n",k);
   12 
   13   //Create shared memory
   14   int shmid = shmget(k,SIZE,IPC_CREAT|IPC_EXCL|0666);
   15   if(shmid < 0)
   16   {
   17     perror("shmget");
   18     return 1;
   19   }
   20   printf("shmid:%d\n",shmid);
   21   sleep(3);
   22  //Associated shared memory
   23  void *str = shmat(shmid,NULL,0);
   24  sleep(2);
   25  //Shared memory disassociation
   26   shmdt(str);
   27   sleep(2);                                                                                               
   28   //Delete the shared memory and leave the third parameter blank temporarily
   29   shmctl(shmid,IPC_RMID,NULL);
   30   sleep(2);
   31   return 0;
   32 }

The effects are as follows:

We have completed the process of creating shared memory. Next, let the two processes communicate.

Shared memory for communication

Or use the client and server, write on the client and read on the server.

    //client.c
    1 #include<stdio.h>
    2 #include"com.h"
    3 #include<sys/ipc.h>
    4 #include<sys/shm.h>
    5 #include<unistd.h>
    6 #include<sys/types.h>
    7 int main()
    8 {
    9 
   10   key_t k = ftok(PATHNAME,PROJ_ID);
   11   printf("key_t:%p\n",k);
   12   //Remove IPC from client_ EXL, memory created by the server                                                                                         
   13   int shmid = shmget(k,SIZE,IPC_CREAT);
   14   //Associated shared memory                       
   15   sleep(2);                              
   16   char* str = (char*)shmat(shmid,NULL,0);
   17   int i = 0;                             
   18   while(i<26)        
   19   {                  
   20     str[i] = 'A'+i;  
   21     ++i;             
   22     str[i] = '\0';   
   23     sleep(1);        
   24   }                  
   25   //Decorrelation           
   26   shmdt(str);        
   27   sleep(2);          
   28   return 0;          
   29 }  
//server.c
    1 #include<stdio.h>
    2 #include"com.h"
    3 #include<sys/ipc.h>
    4 #include<sys/shm.h>
    5 #include<unistd.h>
    6 #include<sys/types.h>
    7 int main()
    8 {
    9 
   10   key_t k = ftok(PATHNAME,PROJ_ID);
   11   printf("key_t k :%p\n",k);
   12 
   13   //Create shared memory
   14   int shmid = shmget(k,SIZE,IPC_CREAT|IPC_EXCL|0666);
   15   if(shmid < 0)
   16   {
   17     perror("shmget");
   18     return 1;
   19   }
   20   printf("shmid:%d\n",shmid);
   21  //Associated shared memory
   22  char *str =(char*) shmat(shmid,NULL,0);
   23                                                                                                                                   
   24  while(1)
   25  {
   26    printf("client#:%s\n",str);
   27    sleep(1);
   28  }
   29  //Shared memory disassociation
   30   shmdt(str);
   31   //Delete the shared memory and leave the third parameter blank temporarily
   32   shmctl(shmid,IPC_RMID,NULL);
   33   return 0;
   34 }

The effects are as follows:

Two processes have completed communication.
be careful:

I terminated the client, but the server is still writing. I need ctrl+c to terminate the server. explain:

The underlying shared memory does not provide synchronization to mutual exclusion

Difference between shared memory and pipeline

Pipeline communication diagram:

1. The server copies the input file to the temporary buffer first
2. When writing information to the pipeline
3. The user copies the information from the pipeline to the buffer
4. Finally, copy the information to the output file
Pipeline communication was copied 4 times
Share save:

2 copies of shared memory communication.
1. Input files to shared memory
2. From shared memory to output file
Pipes have mutual exclusion and synchronization, and shared memory is not provided.

Keywords: Linux Operation & Maintenance server

Added by djr587 on Tue, 28 Dec 2021 11:02:17 +0200