Linux interprocess communication

Introduction to interprocess communication

Concept of interprocess communication

Process communication refers to the transmission of data (exchange of information) between processes

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 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.

Development of interprocess communication

  • The Conduit
  • System V interprocess communication
  • POSIX interprocess communication

Interprocess communication classification

The Conduit

  • Anonymous Pipe
  • name pipes

System V IPC

  • System V message queue
  • System V shared memory
  • System V semaphore

POSIX IPC

  • Message queue
  • Shared memory
  • Semaphore
  • mutex
  • Conditional variable
  • Read write lock

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 "pipeline"“

Example: count the number of users logged in on the ECS

After running, who and wc become two processes. Who processes type data into the "pipeline" through standard output, and wc processes read data from the "pipeline" through standard input.


The who command is used to view the login users of the current ECS (one user is displayed in one line), and wc -l is used to count the current number of lines.

Anonymous Pipe

pipe function

Role: create anonymous pipes
Function prototype:

int pipe(int pipefd[2]);

The parameter of the pipe function is an output parameter. The array pipefd is used to return two file descriptors pointing to the read and write ends of the pipe:
pipefd[0] represents the file descriptor at the reading end of the pipeline
pipefd[1] represents the file descriptor of the write end of the pipeline

The pipe function returns 0 when the call is successful and - 1 when the call fails.

To use anonymous pipes

  1. The parent process calls the pipe function to create a pipe.

2. The parent process creates a child process.


3. The parent process closes the write side and the child process closes the read side.


be careful:

  1. The pipeline can only conduct one-way communication. Therefore, after the parent process creates the child process, it is necessary to confirm who reads and writes the parent-child process, and then close the corresponding read-write end.
  2. Data written from the write side of the pipeline is buffered by the kernel until it is read from the read side of the pipeline.

From the perspective of file descriptor - deep understanding of pipeline

  1. The parent process calls the pipe function to create a pipe.

  1. The parent process creates a child process.

  1. The parent process closes the write side and the child process closes the read side.

Code demonstration

#include<stdio.h>
#include<unistd.h>
#include<sys/wait.h>
#include<string.h>
#include<stdlib.h>

int main()
{
        int pipe_fd[2]={0};

        if(pipe(pipe_fd)<0){//Creating anonymous pipes using pipe
               perror("pipe");
               return 1;
        }

	      pid_t id=fork();
         if(id<0){
              perror("fork");
              return 2;
	     }
         else if(id==0){ // write24                 // child
 	          close(pipe_fd[0]); // The child process closes the reader 
 	          
	          const char *msg="hello parent,I am child\n";
            int count=5;
            while(count)
            {
            			//The child process writes data to the pipeline
                  write(pipe_fd[1],msg,strlen(msg));
                  sleep(1);
                  count--;
	        }
	         
           close(pipe_fd[1]);//After the subprocess is written, close the file
           exit(0);
     	}
       else{  // read41                
    	 // parent
       close(pipe_fd[1]); //The parent process closes the write side
       char buffer[64];
       //The parent process reads data from the pipe
       while(1){
            buffer[0]=0;
            ssize_t size=read(pipe_fd[0],buffer,sizeof(buffer)-1);
             if(size > 0){
                    buffer[size]=0;
                    printf("parent get message from child# %s",buffer);
              }
              else if(size==0){
                  printf("pipe file close , child quit!\n");
                  break;
	         	}
             else{
			      break;
          		}
      	 }

          int status=0;
          if(waitpid(id,&status,0)>0){
                  printf("child quit,wait success!\n");
         }
          close(pipe_fd[0]);
  	 }
     return 0;
}

Operation results:

Pipeline read / write rules

  1. When no data is readable

O_NONBLOCK disable: the read call is blocked, that is, the process suspends execution until data arrives.
O_NONBLOCK enable: the read call returns - 1, and the errno value is EAGAIN.

  1. When the pipe is full

O_NONBLOCK disable: the write call blocks until a process reads the data
O_NONBLOCK enable: the call returns - 1, and the errno value is EAGAIN

  1. If the file descriptors corresponding to all pipeline write ends are closed, read returns 0
  2. If the file descriptors corresponding to all pipeline readers are closed, the write operation will generate a signal SIGPIPE, which may cause the write process to exit
  3. When the amount of data to be written is not greater than pipe_ When buf, linux will ensure the atomicity of writing.
  4. When the amount of data to be written is greater than pipe_ When buf, linux will no longer guarantee the atomicity of writing.

Pipeline characteristics

  1. It 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 streaming services

For the data written by process A into the pipeline, the amount of data read by process B from the pipeline each time is arbitrary. This is called streaming service, which corresponds to datagram service: the data is clearly divided, and the data is taken according to the message segment.

  1. Generally speaking, the process exits and the pipeline is released, so the life cycle of the pipeline changes with the process
  2. Generally speaking, the kernel will synchronize and mutually exclusive pipeline operations

Synchronization: two or more processes are synchronized during operation and run in A predetermined order. For example, the operation of task A depends on the data generated by task B.
Mutual exclusion: a public resource can only be used by one process at the same time. Multiple processes cannot use public resources at the same time.

  1. 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

Simplex communication: data transmission in simplex mode is unidirectional. In both communication parties, one party is fixed as the sending end and the other party is fixed as the receiving end.
Half duplex: half duplex data transmission means that data can be transmitted in two directions of a signal carrier, but cannot be transmitted at the same time.
Full duplex communication: full duplex communication allows data to be transmitted simultaneously in two directions. Its ability is equivalent to the combination of two simplex communication modes. Full duplex can simultaneously (instantaneously) transmit signals in both directions.

Four special cases of pipeline

  1. If the writer process does not write and the reader process keeps reading, the corresponding reader process will be suspended because there is no data readable in the pipeline. The reader process will not be awakened until there is data in the pipeline.
  2. If the reader process does not read and the writer process keeps writing, then when the pipeline is full, the corresponding writer process will be suspended until the data in the pipeline is read by the reader process, and the writer process will not be awakened.
  3. The writer process closes the writer after writing the data. Then, after reading the data in the pipeline, the reader process will continue to execute the code logic after the process without being suspended.
  4. If the reading end process closes the reading end and the writing end process is still writing data to the pipeline, the operating system will kill the writing end process.

We can use the following code to see what kind of signal the writing segment receives in the fourth case

#include <stdio.h>
#include <unistd.h>
#include <string.h>
#include <stdlib.h>
#include <sys/types.h>
#include <sys/wait.h>
int main()
{
	int fd[2] = { 0 };
	if (pipe(fd) < 0){ //Create anonymous pipe
		perror("pipe");
		return 1;
	}
	pid_t id = fork(); 
	if (id == 0){
		//child
		close(fd[0]); //The child process closes the reader
		//The child process writes data to the pipeline
		const char* msg = "hello father, I am child...";
		int count = 10;
		while (count--){
			write(fd[1], msg, strlen(msg));
			sleep(1);
		}
		close(fd[1]); //Close write end
		exit(0);
	}
	//father
	close(fd[1]); //The parent process closes the write side
	close(fd[0]); //The parent process closes the reader directly (causing the child process to be killed by the operating system)
	int status = 0;
	waitpid(id, &status, 0);
	printf("child get signal:%d\n", status & 0x7F); //The signal received by the printing subprocess
	return 0;
}

The writer receives a 13 signal

We can check the meaning of the signal through the kill -l command

Pipe size

The capacity of the pipeline is limited, so how much data can the pipeline store at the same time?

Method 1:

Use the ulimit -a command to view the current resource limit settings.


According to the display, the maximum capacity of the pipeline is 512 × 8 = 4096 bytes.

Method 2:

You can constantly write data to the pipeline to see how much data can be written at most

#include <unistd.h>
#include <stdio.h>
#include <stdlib.h>
#include <sys/wait.h>
int main()
{
	int fd[2] = { 0 };
	if (pipe(fd) < 0){ //Create anonymous pipe
		perror("pipe");
		return 1;
	}
	pid_t id = fork();
	if (id == 0){
		//child 
		close(fd[0]); //The child process closes the reader
		char c = 'a';
		int count = 0;
		//The subprocess keeps writing, one byte at a time
		while (1){
			write(fd[1], &c, 1);
			count++;
			printf("%d\n", count); //Number of bytes currently printed and written
		}
		close(fd[1]);
		exit(0);
	}
	//father
	close(fd[1]); //The parent process closes the write side

	waitpid(id, NULL, 0);
	close(fd[0]);
	return 0;
}

Operation results:

Different operating systems have different pipe sizes, and everything is subject to reality.

name pipes

  • One limitation of pipeline application is that it can only communicate between processes with a common ancestor (kinship).
  • If we want to exchange data between unrelated processes, we can do this by using FIFO files, which are often called named pipes. Named pipeline is a special type of file. Two processes open the same pipeline file through the file name of the named pipeline. At this time, the two processes see the same resource and can communicate.

be careful:

  1. Ordinary files are difficult to communicate. The named pipeline here is a special type of file.
  2. Named pipes, like anonymous pipes, are memory files, except that named pipes have a simple image on disk, but the size of this image is always 0, because both named pipes and anonymous pipes will not refresh communication data to disk.

Create named pipe

We can use the mkfifo command to create a named pipe.


Using this named pipeline file, the communication between two processes can be realized.

In one process (process A), we use shell script to write A string to the named pipe every second, and in another process (process B), we use cat command to read from the named pipe.


When the reading process of the pipeline exits, it is meaningless for the writing process to write data to the pipeline. At this time, the writing process will be killed by the operating system. After we terminate the reader process, because the circular script executed by the writer is executed by the command-line interpreter bash, bash will be killed by the operating system and our ECS will exit.

Create named pipes in process

The mkfifo function is required to create named pipes in the process

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

Parameters:

pathname

  • If pathname is given as a path, the named pipe file is created under the pathname path.
  • If pathname is given in the form of file name, the named pipe file will be created in the current path by default.

mode

Default permissions for creating named pipe files.

Return value:

If the creation is successful, return 0. If the creation fails, return - 1.

Using named pipe to realize the communication of different files

Let's write a server and client program first

Server code

#include<stdio.h>
#include<sys/types.h>
#include<sys/stat.h>
#include<fcntl.h>
#include<unistd.h>

#define FIFO "./fifo"
int main()
{
        int ret=mkfifo(FIFO,0644); // Create a named pipe file
        if(ret<0){
                perror("mkfilo");
                return 1;
        }

        int fd=open(FIFO,O_RDONLY);//Open the named pipe file read-only
        if(fd<0){
                perror("open");
                return 2;
        }

        char buffer[128];
        while(1){
                buffer[0]=0;
                //Read information from the named pipe into the buffer
                ssize_t s=read(fd,buffer,sizeof(buffer)-1);
                if(s>0){
                        buffer[s]=0;
                        printf("client# %s\n",buffer); / / output the information sent by the client
                }
                else if(s==0){
                        printf("client quit...\n");
                        break;
                }
                else{
                        break;
                }

        }
        close(fd);//Close the named pipe file
        return 0;
}

Client code

#include<stdio.h>
#include<string.h>
#include<sys/types.h>
#include<sys/stat.h>
#include<fcntl.h>
#include<unistd.h>

#define FIFO "./fifo"9 
int main()
{
        int fd=open(FIFO,O_WRONLY);
        if(fd<0){
                perror("open");
                return 2;
        }

        char buffer[128];

        while(1){
                printf("Please Enter# ");
                fflush(stdout);
                buffer[0]=0;
                ssize_t s=read(0,buffer,sizeof(buffer)-1);
                if(s>0){
                        buffer[s]=0;
                        write(fd,buffer,strlen(buffer));
                }
                else if(s==0){
                        printf("client quit...\n");
                        break;
                }
                else{
                        break;
                }

        }
        return 0;
 }

When the server and client code are running, we can input information from the client, and then the server can receive it.

system V shared memory

There are three communication modes provided by system V IPC:

  1. system V shared memory
  2. system V message queue
  3. system V semaphore

System V shared memory and system V message queue are designed to transmit data, while System V semaphores are designed to ensure synchronization and mutual exclusion between processes.

Basic principles of shared memory

Shared memory in order to let different processes see the same resource, apply for a piece of memory space in physical memory, and then map this memory space with the page tables of each process. Open up a space in the process virtual address space and fill the virtual address into the corresponding position of each page table, so as to establish a corresponding mapping relationship between the virtual address and the physical address, so that different processes can see the same physical memory, that is, shared memory.


Shared memory data structure

There may be a large number of processes communicating in the system, so there may be a large number of shared memory in the system, so the operating system must manage it. Therefore, in addition to opening up space in the memory, the system must maintain relevant kernel data structures for the 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

shmget function

Function: create shared memory
Function prototype:

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

Return value:

  • The successful call returns the identification code of the shared memory segment
  • Call failed, return - 1

Parameter Description:

  • The first parameter, key, indicates the unique identifier of the shared memory to be created in the system (similar to hash table).
  • The second parameter, size, indicates the size of the shared memory to be created. (4KB integer multiple is recommended)
  • The third parameter, shmflg, indicates how shared memory is created.

Note: the shared memory allocated by the system is allocated by 4KB integer times. If the size of the created shared memory is not 4KB integer times, it will cause a waste of space.

Note: the first parameter key passed into shmget function needs to be obtained by ftok function

ftok function prototype

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

The ftok function is used to combine an existing pathname with an integer identifier proj_id is converted into a key value, called IPC key value. When using shmget function to obtain shared memory, this key value will be filled into the data structure maintaining shared memory. Note that the file specified by pathname must exist and be accessible.

shmflg

The third parameter shmflg of shmget function is passed in. There are two common combinations:

  1. IPC_CREAT: if there is no shared memory with the same key value in the kernel, a new one will be created. If there is, the shared memory will be returned directly.
  2. IPC_CREAT | IPC_EXCL: if there is no shared memory with the same key value in the kernel, a new one will be created. If there is, an error will be reported directly.

Use of shmget function

We use the shmget function to create shared memory, and use the ipcs command to view relevant information.

The ipcs instruction can view information about interprocess communication

We can add options after ipcs to view the information of specified communication facilities

  • -q: Lists information about message queuing.
  • -m: Lists information about shared memory.
  • -s: List semaphore related information.

example
We can use ipcs -m to view information about shared memory.

The meaning of each column of information output by ipcs command is as follows:

titlemeaning
keyThe system distinguishes the unique identification of each shared memory
shmidShared memory d
ownerOwner of shared memory
permsPermissions for shared memory
bytesSize of shared memory
nattchNumber of processes associated with shared memory
statusStatus of shared memory

The shmget function creates shared memory

comm.h file

#pragma once

#include<stdio.h>

#define PATH_NAME "/home/nzb/lesson20"
#define PROJ_ID 0x6666

#define SIZE 4097

server.c Documents

#include"comm.h"
#include<sys/ipc.h>
#include<sys/shm.h>

int main()
{
        key_t k=ftok(PATH_NAME,PROJ_ID);
        if(k<0){
                perror("ftok");
                return 1;
        }
        printf("key:%x\n",k);

        int shmid=shmget(k,SIZE,IPC_CREAT | IPC_EXCL);// The shared memory does not exist, and an error is reported when > is created
        if(shmid<0){
                perror("shmget");
                return 2;
        }
        printf("shmid:%d\n",shmid);

        return 0;
}

Operation results

From the figure, we can see that after the server process exits, the newly created shared memory is not deleted because
All ipc resources follow the kernel, not the process

So how to release ipc resources?

  1. When the process exits, call the function to release, or delete with an instruction.
  2. Restart the operating system.


So how to delete in the process, we need to introduce shmctl function here.

shmctl function

Function: delete shared memory

Function prototype:

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

Parameters:

  • The first parameter shmid represents the shared memory identification code returned by shmget.
  • The second parameter cmd indicates the action to be taken (there are three values).
  • The third parameter buf refers to a data structure that holds the mode state and access rights of shared memory.

Return value:

  • shmctl is deleted successfully, and 0 is returned.
  • shmctl deletion failed, return - 1.

The second parameter of shmctl function is passed in the following three common options:

commandexplain
IPC_STATSet shmid_ Set in DS structure as the current association value of shared memory
IPC_SETOn the premise that the process has sufficient permissions, set the current association value of shared memory to shmid_ The value given in the DS data structure
IPC_RMIDDelete shared memory segment

Example:
Create shared memory in the following code and release it after 2 seconds.

#include"comm.h"
#include<sys/ipc.h>
#include<sys/shm.h>
#include<unistd.h>

int main()
{
        // Create key
        key_t k=ftok(PATH_NAME,PROJ_ID);
        if(k<0){
                perror("ftok");
                return 1;
        }
        printf("key:%x\n",k);

        // Request shared memory
        int shmid=shmget(k,SIZE,IPC_CREAT | IPC_EXCL);// The shared memory does not exist, and an error is reported
        if(shmid<0){
                perror("shmget");
                return 2;
        }
        printf("shmid:%d\n",shmid);

        sleep(2);
        // Free shared memory
        shmctl(shmid,IPC_RMID,NULL);
        printf("delete shm!\n");
        return 0;
}

When the program is running, we can use the following monitoring script to pay attention to the resource allocation of shared memory at all times:

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

Operation results

shmat and shmdt functions

shmat function

Function: connect shared memory to process address space

Function prototype:

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

Parameters:

  • The first parameter shmid is the shared memory ID
  • The second parameter shmaddr specifies that the shared memory is mapped to an address in the process address space. It is usually set to NULL, which means that the kernel decides an appropriate address location by itself.
  • The third parameter shmflg represents some properties set when associating shared memory.

Return value:

  • The shmat call succeeds, and returns the starting address mapped from the shared memory to the process address space.
  • shmat call failed and returned (void*)-1.

There are three common options that shmflg passes in

optioneffect
SHM_RDONLYRead only after associating shared memory
SHM_RNDIf shmaddr is not NULL, the associated address will be automatically adjusted downward to an integer multiple of SHMLBA. Formula: shmaddr-(shmaddr%SHMLBA)
0The default is read-write permission

shmdt function

Function: cancel the association between shared memory and process address space

Function prototype:

int shmdt(const void *shmaddr);

parameter

  • The starting address of the shared memory to be decorrelated, that is, the starting address obtained when the shmat function is called.

Return value

  • The shmdt call succeeds and returns 0.
  • The shmdt call failed and returned - 1.

Use demo

#include"comm.h"
#include<sys/ipc.h>
#include<sys/shm.h>
#include<unistd.h>
int main()
{
        // Create key
        key_t k=ftok(PATH_NAME,PROJ_ID);
        if(k<0){
                perror("ftok");
                return 1;
        }
        printf("key:%x\n",k);
        // Request shared memory
        int shmid=shmget(k,SIZE,IPC_CREAT | IPC_EXCL | 0644);// Shared memory does not exist. Create > error
        if(shmid<0){
                perror("shmget");
                return 2;
        }
        printf("shmid:%d\n",shmid);
        sleep(1);
        // Associate the current process with shared memory
        char* start=(char*)shmat(shmid,NULL,0);
        printf("server already attach on shared memory!\n");
        // You can use shared memory to communicate
        sleep(1);
        // Disassociate the current shared memory
        shmdt(start);
        printf("server already dattch off shared memory!\n");
        sleep(1);
        // Free shared memory
        shmctl(shmid,IPC_RMID,NULL);
        printf("delete shm!\n");
        return 0;
}

Operation results

be careful:

The shmat function connects the shared memory to the process address space. The shared memory needs to have corresponding permissions. When we use the shmget function to create the shared memory, we need to set the permissions after the shared memory is created at its third parameter. The permission setting rules are the same as those for setting file permissions.

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

Communication with shared memory

After knowing the related functions of shared memory, we can try to realize the communication between shared memory

Header file comm.h

#pragma once

#include<stdio.h>

#define PATH_NAME "/home/nzb/lesson20"
#define PROJ_ID 0x6666

#define SIZE 4097

server.c Documents

#include"comm.h"
#include<sys/ipc.h>
#include<sys/shm.h>
#include<unistd.h>
int main()
{
        // Create key
        key_t k=ftok(PATH_NAME,PROJ_ID);
        if(k<0){
                perror("ftok");
                return 1;
        }
        printf("key:%x\n",k);
        // Request shared memory
        int shmid=shmget(k,SIZE,IPC_CREAT | IPC_EXCL | 0644);// Shared memory does not exist. Create > error
        if(shmid<0){
                perror("shmget");
                return 2;
        }
        printf("shmid:%d\n",shmid);
        // Associate the current process with shared memory
        char* start=(char*)shmat(shmid,NULL,0);
        printf("server already attach on shared memory!\n");
        // You can use shared memory to communicate
  		while (1){
				printf("%s\n", start);
				sleep(1);
		}
        // Disassociate the current shared memory
        shmdt(start);
        printf("server already dattch off shared memory!\n");

        // Free shared memory
        shmctl(shmid,IPC_RMID,NULL);
        printf("delete shm!\n");
        return 0;
}

client.c Documents

#include"comm.h"
#include<sys/ipc.h>
#include<sys/shm.h>
#include<sys/types.h>
#include<unistd.h>

int main()
{       
        // Get the same key
        key_t k=ftok(PATH_NAME,PROJ_ID);
        if(k<0){
                perror("ftok");
                return 1;
        }
        printf("%x\n",k);        
        //You don't need to create shm to get shared memory
        int shmid=shmget(k,SIZE,IPC_CREAT);
        if(shmid<0){
                perror("shmget");
                return 2;
             }  
        // Associated shared memory
        char* start=(char*)shmat(shmid,NULL,0);
		//The client keeps writing data to the shared memory
		int i = 0;
		while (1){
			start[i] = 'A' + i;
			i++;
			sleep(1);
		}                  
        // Decorrelation
        shmdt(start):               
        return 0;
}

Operation results

Keywords: Linux Operation & Maintenance

Added by voidstate on Thu, 03 Mar 2022 21:21:51 +0200