Interprocess communication - pipes, message queues, shared memory

 
  

 
 

The essence of inter-process communication is to allow two unrelated processes to see the same resource. This resource is a file provided by the operating system.


The purpose of inter-process communication :

1. Data transfer: A process needs to send its data to another process.

2. Resource sharing: The same resources are shared among multiple processes.

3. Notify events: A process needs to send a message to another (group) process to notify them that some kind of event has occurred. (Notify parent process when process terminates)

4. Process control: Some processes want to completely control the execution of another process. At this time, the controlling process hopes to be able to intercept all exceptions of another process and be able to know its state changes in time.

Here I mainly introduce three methods of inter-process communication: pipes, message queues, and shared memory

Pipe : Create a child process through fork(), the parent and child process share resources, and close the read/write end to achieve one-way transmission.

  Pipes are divided into anonymous pipes and named pipes.

anonymous pipe

#include<unistd.h>
Function: Create an unnamed pipe
int pipe(int fd[2]);

fd: Array of file descriptors. fd[0] represents the read side, and fd[1] represents the write side. The output of fd[1] is the input of fd[1].

Usually, a process will first call pipe to create a pipe, and then call fork to create a child process, thereby creating an IPC channel from the parent process to the child process.

What to do after the fork depends on the direction of data flow we want. For the pipe from the parent process to the child process, if the parent process writes and the child process reads, the parent process closes the read end fd[0], and the child process closes the write end fd[1].

For one end of the pipe to be closed , two rules apply:

(1). When read (read) a pipe whose write end has been closed, after all data has been read, read returns 0, indicating the end of the file.

(2). If write (write) a pipe whose read end has been closed, the signal SIGPIPE is generated. If the signal is ignored or caught and returned from its handler, write returns -1 and errno is set to EPIPE.


Features : 1. Can only be used for communication between processes with affinity.

           2. Pipes are transmitted based on byte streams when communicating.

           3. The life cycle of the pipeline varies with the process.

           4. The pipeline has its own synchronization mechanism (processes access critical resources in a certain order).

            5. Half-duplex communication (one-way communication).

The following is an example of reading data from the keyboard, writing to the pipe, reading the pipe, and writing to the screen.

#include<stdio.h>
#include<stdlib.h>
#include<unistd.h>
#include<string.h>

intmain()
{
  int fds[2];
  char buf[100];
  int len;

  if(pipe(fds) == -1)
    perror("make pipe"),exit(1);

  while(fgets(buf,100,stdin)){
     len = strlen(buf);

     if(write(fds[1],buf,len)!=len){
        perror("write to pipe");
        break;
    }
   memset(buf,0x00,sizeof(buf));

   if((len = read(fds[0],buf,100))==-1){
       perror("read from pipe");
       break;
     }
    if(write(1,buf,len)!=len) {
       perror("write to stdout");
       break;
     }
  }
}



Named Pipes :

If we want to exchange data between unrelated processes, it can be done using FIFO files. It's called a named pipe.

Named pipes can be created from the command line.

$ mkfifo filename
It can also be created from within the program.
int mkfifo(const char* filename,mode_t mode);

The biggest difference between anonymous pipes and named pipes is that anonymous pipes must be related to communicate, while named pipes can communicate between two unrelated pipes.

#include<stdio.h>
#include<stdlib.h>
#include<unistd.h>
#include<string.h>
#include<errno.h>

#define ERR_EXIT(m)
do
{
  perror(m);
  exit(EXIT_FAILURE);
}while(0)

int main(int argc,char* argv[])
{
  mkfifo("tp",0644);
  int info;
  infd = open("abc",O_RDONLY);
  if(infd = =1) ERR_EXIT("open");

  int outfd;
  outfd = open("tp",O_ERONLY);
  if(outfd == -1)   ERR_EXIT("open");

  char buf[1024];
  int n;
  while((n = read(infd,buf,1024))>0){
     write(outfd,buf,n);
  }

 close(infd);
 close(outfd);
 return 0;
}

The above figure is an example of reading a file and writing to a named pipe:

message queue:

A message queue provides a way to send a block of data with a data type from one process to another.

A message queue is a linked list of messages, stored in the kernel, identified by a message queue identifier.

The life cycle of message queues varies with the kernel.

Message queue function:

Function: Create and return a message queue
prototype:
int msgget(key_t key,int msgflg);
parameter:
key: the name of a message queue
msgflg: consists of nine permission flags.
Return value: Successfully returns a non-negative integer, that is, the identification code of the message queue. Returns -1 on failure.
Function: control function of message queue (add, delete, check, change)
prototype:
int msgctl(int msqid,int cmd,struct msqid_ds* buf)
parameter:
msqid: The message queue identification code returned by the msgget function
cmd: The action to be taken. There are three available.
Return value: 0 for success, -1 for failure

Function: Receive messages from a message queue
ssize_t msgrcv(int msqid,void* msgp,sizt_t msgsz,long msgtyp,int msgflg);
return value:
Returns the number of characters actually put into the receive buffer on success, -1 on failure.

Example code:

makefile:

.PHONY:all

all:client server

client:comm.c client.c
        gcc -o $@ $^
server:comm.c server.c
        gcc -o $@ $^

.PHONY:clean
clean:
        rm -f client server

comm.h:

#ifndef __COM_H__
#define __COM_H__

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

#define PATHNAME "."
#define PROJ_ID 0x6666

#define SERVER_TYPE 1
#define CLIENT_TYPE 2

struct msgbuf{
  long mtype;
  char mtext[1024];
};

int createMsgQueue();
int getMsgQueue();
int destroyQueue(int msgid);
int sendMsg(int msgid,int who,char* msg);
int recvMsg(int msgid,int recvType,char out[]);

#endif

comm.c:

#include"comm.h"

int commMsgQueue(int flags)
{
  key_t _key = ftok(".",0x6666);
  if(_key < 0 ){
    perror("ftok");
    return -1;
  }

  //int msgid = msgget(_key,IPC_CREAT|IPC_EXCL);
    int msgid = msgget(_key,flags);
    if(msgid < 0){
    perror("msgget");
  }
 return msgid;
}


int createMsgQueue()
{
  return commMsgQueue(IPC_CREAT|IPC_EXCL|0666);
}
int getMsgQueue()
{
  return commMsgQueue(IPC_CREAT);
}


int destroyMsgQueue(int msgid)
{
  if(msgctl(msgid,IPC_RMID,NULL) < 0){
    perror("msgctl");
    return -1;
  }
 return 0;
}


int sendMsg(int msgid,int who,char* msg)
{
 struct msgbuf buf;
 buf.mtype = who;
 strcpy(buf.mtext,msg);
 if(msgsnd(msgid,(void*)&buf,sizeof(buf.mtext),0) < 0){
     perror("msgsnd");
     return -1;
  }
return 0;
}


int recvMsg(int msgid,int recvType,char out[])
{
  struct msgbuf buf;
  if(msgrcv(msgid,(void*)&buf,sizeof(buf.mtext),recvType,0)<0){
     perror("msgrcv");
     return -1;
  }
 strcpy(out,buf.mtext);
 return 0;
}

server.c:

#include"comm.h"

intmain()
{
  int msgid = createMsgQueue();

  char buf[1024];
  while(1){
    buf[0] = 0;
    recvMsg(msgid,CLIENT_TYPE,buf);
    printf("client:%s\n",buf);

    printf("please enter:");
    fflush(stdout);
    ssize_t s = read(0,buf,sizeof(buf));
    if(s>0){
        buf[s-1] = 0;
        sendMsg(msgid,SERVER_TYPE,buf);
        //printf("send down,wait recv..\n");
     }
  }
 destroyMsgQueue(msgid);
 return 0;
#include"comm.h"

intmain()
{
  int msgid = createMsgQueue();

  char buf[1024];
  while(1){
    buf[0] = 0;
    recvMsg(msgid,CLIENT_TYPE,buf);
    printf("client:%s\n",buf);

    printf("please enter:");
    fflush(stdout);
    ssize_t s = read(0,buf,sizeof(buf));
    if(s>0){
        buf[s-1] = 0;
        sendMsg(msgid,SERVER_TYPE,buf);
        //printf("send down,wait recv..\n");
     }
  }
 destroyMsgQueue(msgid);
 return 0;
}

client.c:

#include"comm.h"

intmain()
{
  int msgid = getMsgQueue();

  char buf[1024];
  while(1){
    buf[0] = 0;
    printf("please enter:");
    fflush(stdout);
    ssize_t s = read(0,buf,sizeof(buf));
    if(s>0){
        buf[s-1] = 0;
        sendMsg(msgid,CLIENT_TYPE,buf);
        printf("send done,wait recv..\n");
     }
    recvMsg(msgid,SERVER_TYPE,buf);
    printf("server:%s\n",buf);
  }
 return 0;
}


 Shared memory:

Shared memory is the fastest form of IPC. It is to open up a space in physical memory so that two processes can see a common resource. Once such memory is mapped into the address space of the processes that share it, these interprocess data transfers no longer involve the kernel, in other words the processes no longer pass each other's data by executing system calls that enter the kernel.

1. Shared memory is efficient because it avoids copying back and forth.

2. The life cycle follows the kernel and needs to be deleted manually.

3. There is no synchronization and mutual exclusion mechanism for shared memory. To use it, you must implement mutual exclusion and synchronization yourself.

Shared memory functions:

shmget function:
 Function: create shared memory
 prototype:
   int shmget(key_t key,size_t size,int shmflg);
Return value: Successfully returns a non-negative integer, that is, the identification code of the shared memory segment. Returns -1 on failure.

shmat function:
 Function: Connect the shared memory segment to the process address space. g association.
 prototype:
  void* shmat(int shmid,const void* shmaddr,int shmflg);
 return value:
  A pointer to the first section of shared memory is returned on success. Returns -1 on failure.

shmdt:
  Function: Detach the shared memory segment from the current process. disassociate
  prototype:
  int shmdt(const void* shmaddr);
  Return value: 0 for success, -1 for failure.

shmctl function:
  Function: Control shared memory.
  prototype:
  ing shmctl(int shmid,int cmd,struct shmid_ds* buf);
  Return value: 0 for success, -1 for failure.
Example code:

makefile:

.PHONY:all
all:server client

client:client.c comm.c
        gcc -o $@ $^
server:server.c comm.c
        gcc -o $@ $^
.PHONY:clean
clean:
        rm -f client server

comm.h:

#ifndef __COMM_H__
#define __COMM_H__

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


int createShm(int size);
int destroyShm(int shmid);
int getShm(int size);


#endif

comm.c:

#include"comm.h"

int commShm(int size,int flags)
{
  key_t _key = ftok(".",0x6666);
  if(_key < 0 ){
     perror("ftok");
     return -1;
  }
 int shmid = 0;
 if((shmid = shmget(_key,size,flags)) < 0){
     perror("shmget");
     return -2;
  }
 return shmid;
}

int destroyShm(int shmid)
{
  if(shmctl(shmid,IPC_RMID,NULL) < 0){
     perror("shmctl");
     return -1;
  }
 return 0;
}
int createShm(int size)
{
  return commShm(size,IPC_CREAT|IPC_EXCL|0666);
}

int getShm(int size)
{
  return commShm(size,IPC_CREAT);
}

server.c:

#include"comm.h"

intmain()
{
  int shmid = createShm(4096);

  char* addr = shmat(shmid,NULL,0);
  sleep(2);
  int i=0;
  while(i++ < 26){
    printf("client: %s\n",addr);
    sleep(1);
  }
 shmdt(addr);
 sleep(2);
 destroyShm(shmid);
 return 0;
}

client.c:

#include"comm.h"

intmain()
{
  int shmid = getShm(4096);
  sleep(1);
  char* addr = shmat(shmid,NULL,0);
  sleep(2);
  int i=0;
  while(i<26){
    addr[i] = 'A'+i;
    i++;
    addr[i] = 0;
    sleep(1);
  }
 shmdt(addr);
 sleep(2);
 return 0;
}


It is worth noting that ctrl+c terminates the process, and after restarting, you need ipcs -m to view the shared memory. Then use ipcrm -m shmid to delete the shared memory id.

Guess you like

Origin http://43.154.161.224:23101/article/api/json?id=325888535&siteId=291194637