Linux Interprocess Communication (Pipeline, Named Pipeline, Message Queue, Semaphore, Shared Memory, Socket)

What is a process?
In Linux system, process is the basic process to manage transactions. Processes have their own independent processing environment and system resources. The whole life of a process can be simply divided into three states:

Ready state:
The process has all the conditions for execution and is waiting to allocate CPU processing time.
Execution status:
The process is running on CPU.
Wait state:
A state in which a process is temporarily unable to execute because it does not have some execution conditions.

Interprocess Communication Concept
A process is an independent resource allocation unit. The resources between different processes are independent and can not directly access the resources of another process in one process. Therefore, different processes need to transfer the interactive state of information, and so on, so they need inter-process communication. Common interprocess communications in LINUX are as follows:
1. pipe
2. Named Pipeline
3. Message queue
4. semaphore
5. Shared memory
6. socket

1-5 are all communication between processes of the same host. Number 6 is the communication between different host (network) processes.

The Conduit
Pipeline is also called nameless pipeline. Pipeline is an ancient form of IPC communication. As its name implies, two processes that need to communicate are at both ends of the pipeline. Pipeline is a special type of file that exists in the buffer of the kernel. Pipeline has the following characteristics:
1. Half duplex, data can not be uploaded in two sections, data can only flow in one direction.
2. Pipes are not ordinary files, they do not belong to a file system, but only exist in memory.
3. Pipeline has no name and can only communicate between related parent-child processes.

pid_t fork(void);
The fork() function generates a copy of the parent process, which inherits the address space of the entire process from the parent process.
Return value
When completed successfully, fork() returns 0 to the child process and the process ID of the child process to the parent process. Both processes should continue to execute from the fork() function. Otherwise, -1 returns to the parent process, does not create a child process, and sets errno to indicate an error.

int pipe(int fildes[2]);
The pipe() function should create a pipe and put two file descriptors in the parameters fildes[0] and fildes[1], each of which refers to the file description opened by the pipe reader and writer.
Return value
After successful completion, return 0; otherwise, return - 1 and set errno to indicate an error, do not assign any file descriptors, and do not modify the contents of fildes.

The result of fork function execution
For example, a pipe() function is used to create a pipeline and communicate through an anonymous pipeline:

#include <stdio.h>
#include <unistd.h>//pipe , fork
#include <sys/types.h>
#include <string.h>//memset

int main(int argc, char const *argv[])
{
	
	int fd_pipe[2];//Descriptor, 0 for reading and 1 for writing
	pid_t pid = 0;
	char buf[128] = "minger";
	int n = 0;
	if (pipe(fd_pipe) < 0)
	{
		perror("pipe");
		return -1;
	}

	pid = fork();
	if ( pid < 0)
	{
		perror("fork");
		return -1;
	}

	else if (pid > 0) //Parent process
	{
		close(fd_pipe[1]);//Close the Writing End of the Pipeline
		memset(buf,0,sizeof(buf));
		n = read(fd_pipe[0],buf,sizeof(buf)); //Read data from pipes
		printf("Read %d from the pipe: %s\n",n,buf);
	}

	else //Child process
	{
		close(fd_pipe[0]);//Close the reading end of the pipe
		write(fd_pipe[1], buf, strlen(buf)); //Write data to the pipeline
	}
	return 0;
}

Compilation results

In the program, anonymous pipeline is used to realize process communication. The child process writes a string to the pipe, and the parent process reads the string from the pipe.

name pipes
Named pipelines differ from pipelines in that they allow unrelated processes to communicate and unrelated processes to exchange data.

int mkfifo(const char *path, mode_t mode);
The mkfifo utility should create FIFO special files specified by operands in the specified order.
Parameters:
Path: File operands are used as path parameters.
mode: mode parameter

Write a small example to test, name pipe communication, create two. c files, read process and write process.

Reading process

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

int main(int argc, char const *argv[])
{
	int fd_read;
	char buf[128] = {0};

	if (mkfifo("FIFO",S_IRUSR|S_IWUSR) != 0); //0666, 0777
	{
		perror("mkfifo");
		//return -1;
	}
	printf("before open\n");
	fd_read = open("FIFO",O_RDONLY); //Open in read-only mode
	if (fd_read < 0)
	{
		perror("open");
		return-1;
	}
	printf("after open\n");
	memset(buf,0,sizeof(buf));
	read(fd_read,buf,sizeof(buf));
	printf("read from FIFO buf: %s\n",buf);


	return 0;
}


Writing process

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

int main(int argc, char const *argv[])
{
	int fd_write;
	char buf[128] = "minger";

	if (mkfifo("FIFO",S_IRUSR|S_IWUSR) != 0); // 0666,0777
	{
		perror("mkfifo");
		//return -1;
	}

	fd_write = open("FIFO",O_WRONLY); //Open in read-only mode
	if (fd_write < 0)
	{
		perror("open");
		return-1;
	}

	write(fd_write,buf,strlen(buf));
	printf("Write from FIFO buf: %s\n",buf);


	return 0;
}

Open two terminals, one terminal runs the write process first, and then the read process. The compiled results are as follows:


The results show that two unrelated processes can communicate through FIFO. Under the terminal, you can check whether the FIFO file is a named pipeline file through ls-alh; if p begins with a pipeline.

Message queue

Message queues are linked lists of messages stored in the kernel. A message queue is identified by an identifier (queue ID).

Characteristics of message queues
1. Messages in message queues are typed and formatted
2. Message queue can realize random query of messages. Messages need not be read in FIFO order. It can be read by message type.
3. Each message queue has an identifier for the message queue. The identifier for the message queue is unique throughout the system.
4. Message queue allows one or more processes to write or read messages to it.

Function prototype

Create message queues
int msgget(key_t key, int msgflg);
Functions:
Create or open message queues.
Parameters:
key:IPC key value
msgfig: Identify whether the message queue exists. And (msgflg & IPC_CREAT) is non-zero.
IPC_CREAT: Is there a message queue created?
Return value
After successful completion, msgget() returns a non-negative integer, the message queue identifier.
Otherwise, it will return - 1 and indicate an error.

send message
int msgsnd(int msqid, const void *msgp, size_t msgsz, int msgflg);
Functions:
Add message queue
Parameters:
msqid: identifier of message queue
msgp: address of message queue to be sent
msgsz: number of bytes in message queue body
Msgflg: If (msgflg & IPC_NOWAIT) is non-zero, no message is sent, and the calling thread should return immediately.
If (msgflg & IPC_NOWAIT) is 0, the calling thread will suspend execution.

Return value
After successful completion, msgsnd() returns 0;
Otherwise, no message will be sent, msgsnd() will return - 1 and set errno to indicate an error.

Message format of message queue
struct mymsg {
Long mtype; // message type.
Char Mtext [1]; // message text.
};
Structural member mtype is a non-zero positive-length type that can be used by the receiving process for message selection.
The structure member mtext is any text that is msgsz bytes in length. The parameters msgsz range from 0 to the maximum set by the system.

receive messages
ssize_t msgrcv(int msqid, void *msgp, size_t msgsz, long msgtyp,int msgflg);
Functions:
Read message
Parameters:
msqid: Identifier of message queue.
msgp: The address where the message structure is stored.
msgsz: The number of bytes in the message body.
msgtyp: Message type, there are several types:
If msgtyp is 0, the first message on the queue is received.
If msgtyp is greater than 0, the first message of type msgtyp is received.
If msgtyp is less than 0, the first message of the lowest type is received, which is less than or equal to the absolute value of msgtyp.

Msgflg: If (msgflg & IPC_NOWAIT) is non-zero, the calling thread should return immediately with a return value of -1;
If (msgflg & IPC_NOWAIT) is 0, the calling thread will suspend execution.

Return value
After successful completion, msgrcv() returns a value equal to the number of bytes actually placed in the buffer mtext.
Otherwise, no messages will be received, and msgrcv() will return - 1 and set errno to indicate an error.

Control of message queue
int msgctl(int msqid, int cmd, struct msqid_ds *buf);
Functions:
Control message queues, such as modifying the properties of message queues, or deleting message queues.
Parameters:
msqid: identifier of message queue
cmd: control function of function
Address of buf: msqid_ds data type
Return value
After successful completion, msgctl() returns 0.
Otherwise, it will return - 1 and indicate an error.

Get IPC keys
key_t ftok(const char *path, int id);
Functions:
Get the unique IPC key value
Parameters:
Path: path name
ID: project ID
Return value
After successful completion, ftok() returns a key.
Otherwise, ftok() will return (key_t)-1, indicating an error.

With these function prototypes, we will write a simple example of message queuing: one task is responsible for sending messages, the other task is responsible for receiving messages, creating sub-processes through ftok() to achieve multi-tasks.

message_read.c

#include <stdio.h>
#include <sys/types.h>
#include <unistd.h>
#include <sys/ipc.h>
#include <sys/msg.h>
#include <string.h>
#include <stdlib.h>

typedef struct msg
{
	long mtype; //Message type
	char ntext[128];//Message text
}MSG;

int main(int argc, char const *argv[])
{
	key_t key;
	int msgqid;
	MSG my_msg;
	

	if ((key = ftok(".",2019)) == -1)
	{
		perror("ftok");
		exit(-1);

	}
	printf("message queue key: %d\n",key );

	if ((msgqid = msgget(key,IPC_CREAT|0666)) == -1)//Open message queue
	{
		perror("message");
		exit(-1);
	}
	printf("msgqid: %d\n",msgqid);//Print message queue ID

	msgrcv(msgqid,&my_msg,sizeof(my_msg.ntext),111,0);
	printf("my_msg.ntext= %s\n",my_msg.ntext);
	msgctl(msgqid,IPC_RMID,NULL);//Delete message queues indicated by msgqid


	return 0;
}

message_write.c

#include <stdio.h>
#include <sys/types.h>
#include <unistd.h>
#include <sys/ipc.h>
#include <sys/msg.h>
#include <string.h>
#include <stdlib.h>

typedef struct msg
{
	long mtype; //Message type
	char ntext[128];//Message text
}MSG;

int main(int argc, char const *argv[])
{
	key_t key;
	int msgqid;
	MSG my_msg;
	

	if ((key = ftok(".",2019)) == -1)
	{
		perror("ftok");
		exit(-1);

	}
	printf("message queue key: %d\n",key );

	if ((msgqid = msgget(key,IPC_CREAT|0666)) == -1)//Open message queue
	{
		perror("message");
		exit(-1);
	}
	printf("msgqid: %d\n",msgqid);//Print message queue ID
	my_msg.mtype = 111;
	strcpy(my_msg.ntext,"hello minger");
	msgsnd(msgqid,&my_msg,sizeof(my_msg.ntext),0);
	

	return 0;
}

When the compilation started, it did not run. Look at the message queue ipcs-q and show that the number of bytes used is 0 and the message is 0.

Compilation results

Semaphore

A semaphore is a count that is used when multiple processes need to access shared data. Used for synchronization and mutual exclusion between processes or threads. Then with the help of semaphores, such things can be done.
Process and characteristics
Operating system P is locking, V is unlocking.
If the semaphore value is greater than 0, the resource is available and subtracted by 1, indicating that it is currently in use.
If the semaphore value is 0, the process hibernates until the semaphore value is greater than 0

Function prototype

Signal Initialization
int sem_init(sem_t *sem, int pshared, unsigned value);
Functions:
Create a semaphore and initialize its value.
Parameters:
sem: Address of semaphore
Pshared: If pshared=0, semaphores are shared between threads; pshared is not equal to 0, semaphores are shared between processes.
Value: The initial value of the semaphore;
Return value
After successful completion, the sem_init() function initializes the semaphore in the SEM and returns 0.
Otherwise, it will return - 1 and indicate an error.

p-operation of signal
int sem_wait(sem_t *sem);
Functions:
The semaphore is locked by performing a semaphore lock operation on the SEM reference semaphore. If the semaphore value is currently 0, the calling thread will not return to sem_wait() from the call until the semaphore is locked or the call is interrupted by the signal.
Parameters:
sem: Address of semaphore
Return value:
Successfully return 0, otherwise return - 1;

Semaphore V operation
int sem_post(sem_t *sem);
Functions:
The semaphore is unlocked by performing a semaphore unlocking operation on the sem reference semaphore. Add the value of the semaphore to 1 and issue a wake-up to unlock the semaphore.
parameter
sem: Address of semaphore
Return value:
If successful, the sem_post() function returns 0.
Otherwise, the function returns - 1, indicating an error.

Mutual exclusion of semaphores

#include <stdio.h> 
#include <pthread.h> 
#include <unistd.h> 
#include <semaphore.h>

sem_t sem;
void display(char *pstr)
{
	sem_wait(&sem); //Lock up
	if (pstr == NULL)
		return ;
	while (*pstr != '\0')
	{
		putchar(*pstr); //Only one character at a time
		fflush(stdout);//Print characters to the screen
		pstr ++;

		sleep(1);
	}
	sem_post(&sem);//Unlock

}

void *thread_task1(void *argv)
{
	char *pstr = "abcdef";
	display(pstr);
}

void *thread_task2(void *argv)
{
	char *pstr = "ghijkl";
	display(pstr);
}

int main(int argc, char const *argv[])
{

	pthread_t tid1,tid2;
	if (sem_init(&sem,0,1) != 0)  //0 denotes a thread and 1 denotes an initialization sem value of 1
	{
		perror("sem_init");
		return 0;
	}
	//Thread creation
	if (pthread_create(&tid1,NULL,thread_task1,NULL) != 0)
	{
		perror("pthread_create");
		return 0;
	} 
	if (pthread_create(&tid2,NULL,thread_task2,NULL) != 0)
	{
		perror("pthread_create");
		return 0;
	} 

	//Thread waiting
	pthread_join(tid1,NULL);
	pthread_join(tid2,NULL);
	printf("\nDone\n");
	return 0;
}

Operation result

Threads are used in compilation, so - lpthread is added, so why not abcdefghijk? It depends on the cpu scheduling algorithm.

Synchronization of semaphores

#include <stdio.h> 
#include <pthread.h> 
#include <unistd.h> 
#include <semaphore.h>

sem_t sem1,sem2;
char ch = 'a';


void *pthread_task1(void *argv)
{
	while (1)
	{
		sem_wait(&sem1);
		ch ++;
		sleep(1);
		sem_post(&sem2);
	}
}

void *pthread_task2(void *argv)
{
	while (1)
	{
		sem_wait(&sem2);
		putchar(ch);
		fflush(stdout);
		sem_post(&sem1);
	}
}

int main(int argc, char const *argv[])
{

	pthread_t tid1,tid2;
	if (sem_init(&sem1,0,0) != 0)
	{
		perror("sem_init");
		return 0;
	}

	if (sem_init(&sem2,0,1) != 0)
	{
		perror("sem_init");
		return 0;
	}

	if (pthread_create(&tid1,NULL,pthread_task1,NULL) !=0 )
	{
		perror("pthread_create");
		return 0;
	}

	if (pthread_create(&tid2,NULL,pthread_task2,NULL) !=0 )
	{
		perror("pthread_create");
		return 0;
	}

	pthread_join(tid1,NULL);
	pthread_join(tid2,NULL);
	printf("Done\n");
	
	return 0;
}

Output result

The function of synchronization of semaphores is verified.

Shared memory
Shared memory runs multiple processes sharing a given storage area. Shared memory is the fastest way to share data between processes. When using shared memory, we should pay attention to the mutually exclusive access of multiple processes to a given storage area. We need to use the semaphores used previously to achieve synchronization and mutually exclusion.

Function prototype
Shared Storage Identifier
int shmget(key_t key, size_t size, int shmflg);
Functions:
Create or open a shared memory area
Parameters:
key:IPC key value
size: Length of shared memory segment
shmflg: Shared Memory Permissions for Identifying Functions
IPC_CREAT: Create if it does not exist;
IPC_EXCL: Failure to return if it already exists;

Return value
After successful completion, shmget() returns a non-negative integer, the shared memory identifier.
Otherwise, it will return - 1, indicating an error.

Shared memory mapping
void *shmat(int shmid, const void *shmaddr, int shmflg);
Functions:
Map a shared memory segment to the data segment of the calling process.
The shared memory segment associated with the shared memory identifier specified by shmid is attached to the address space of the calling process.
If shmaddr is a null pointer, add a segment at the first available address selected by the system. If shmaddr is not a null pointer and (shmflg & SHM_RND) is 0, an additional segment is added at the address given by shmaddr.

Parameters:
shmid: Shared memory identifier
shmaddr: Shared memory mapping address, usually using NULL.
shmflg: Access permissions and mapping conditions for shared memory segments.
If (shmflg & SHM_RDONLY) is non-zero and the calling process has read permission, the segment is attached to read; otherwise, if it is 0 and the calling process has read and write permission, the segment is attached to read and write.

Return value
Successful return 0, Failure Return - 1;

Unmapped shared memory
int shmdt(const void *shmaddr);
Functions:
Separate the shared memory segment from the address space of the calling process at the address specified by shmaddr.
Parameters:
shmaddr: Shared memory mapping address.
Return value:
Successful return to 0;
Otherwise, the shared memory segment will not be separated, and shmdt() will return - 1 to indicate an error.

Shared memory control
int shmctl(int shmid, int cmd, struct shmid_ds *buf);
Functions:
Control of shared memory space
Parameters:
shmid: Shared memory identifier
cmd: Functional control.
IPC_RMID: Delete.
IPC set: set the shmid DS parameter.
IPC_STAT: Save the shmid_ds parameter.
SHM_LOCK: Lock the shared memory segment (superuser).
SHM_UNLOCK: Unlock shared memory segments.
Buf: The address of the shmid_ds data type used to store or modify properties of shared memory.

Return value
Successful return 0, failed return - 1

share_write.c

#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <unistd.h>
#include <sys/types.h>
#include <sys/ipc.h>
#include <sys/shm.h>

#define SIZE 1024*2

int main(int argc, char *argv[])
{
	int shmid;
	int ret;
	key_t key;
	char *shmadd;
	
	if ((key= ftok(".", 2012)) == -1)
	{
		perror("ftok");
	}
	system("ipcs -m");//View shared memory
	

	if ((shmid = shmget(key, SIZE, IPC_CREAT|0666)) < 0) 
	{ 
		perror("shmget"); 
		exit(-1); 
	} 
	
	if ((shmadd = shmat(shmid, NULL, 0)) < 0)
	{
		perror("shmat");
		exit(-1);
	}
	
	printf("data =%s\n", shmadd);
	if ((ret = shmdt(shmadd)) < 0)
	{
		perror("shmdt");
		exit(1);
	}
	else
	{
		printf("deleted shared memory\n");
	}
	shmctl(shmid, IPC_RMID, NULL);
	system("ipcs -m");
	return 0;
}

share_read.c

#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <unistd.h>
#include <sys/types.h>
#include <sys/ipc.h>
#include <sys/shm.h>

#define SIZE 1024*2

int main(int argc, char *argv[])
{
	int shmid;
	int ret;
	key_t key;
	char *shmadd;
	
	if((key = ftok(".", 2012)) == -1)
	{
		perror("ftok");
	}
	
	if ((shmid = shmget(key, SIZE, IPC_CREAT|0666)) < 0) //Create shared memory
	{ 
		perror("shmget"); 
		exit(-1); 
	} 
	
	if((shmadd = shmat(shmid, NULL, 0)) < 0)//mapping
	{
		perror("shmat");
		_exit(-1);
	}
	
	printf("copy data to shared-memory\n");//Copy data to shared memory area
	bzero(shmadd, SIZE);
	strcpy(shmadd, "data in shared memory\n");
	return 0;
}


Output result

socket
Sockets are a communication mechanism by which processes between different hosts can communicate. That is to say, it allows processes that are not on the same computer but are connected to the computer through the network to communicate. Because of this, sockets clearly distinguish the client from the server.

Characteristics of sockets
1. domain
2. types
3. agreement

Domain of socket
Domain specifies the network medium used in socket communication. The most common socket domain is AF_INET (IPv4) or AF_INET6(IPV6);

Types of sockets
Stream socket (SOCK_STREAM):
Stream sockets are used to provide connection-oriented and reliable data transmission services. The service will ensure that data can be sent without error and repetition, and received in sequence. The reason why stream sockets can provide reliable data services is that they use the transmission control protocol, namely TCP protocol.

Datagram Socket (SOCK_DGRAM):
Datagram sockets provide a connectionless service. This service can not guarantee the reliability of data transmission, data may be lost or duplicated in the transmission process, and it can not guarantee that data will be received sequentially. Datagram socket uses UDP protocol to transmit data.

Original socket (SOCK_RAW):
The original socket can read and write IP packets that are not processed by the kernel, while the stream socket can only read the data of TCP protocol, and the datagram socket can only read the data of UDP protocol. Therefore, if you want to access other protocols to send data, you must use the original socket.

summary
This article briefly introduces the common methods of interprocess communication, except socket, there are no examples, the rest are simple examples. Socket should be the most widely used way of inter-process communication. It can be used for communication between different processes between different computers. The socket topic is too big. When we write network programming later, we will discuss this topic again.

Keywords: socket network Linux less

Added by faizanno1 on Sat, 21 Sep 2019 17:22:11 +0300