Interprocess communication

1, Mode of interprocess communication

Interprocess communication (IPC) mainly includes the following:

1.The Conduit
2.FIFO
3.Message queue
4.Shared memory
5.signal
6.Semaphore
7.Socket( socket)

2, Pipeline

Pipes usually refer to nameless pipes, which have the following characteristics:
① Half duplex, with fixed read and write ends, data can only flow in one direction, and the data in the pipeline disappears when it is read.

② It can only be used for communication between related processes (parent-child process and brother process). If process A closes the read side, process B must close the write side.

③ The pipeline is not an ordinary file, does not belong to any other file system, and only exists in memory. The pipeline can be regarded as a special file. Ordinary read, write and other functions can be used to read and write it.

④ When the parent-child process exits, the pipeline disappears.
schematic diagram:

Function prototype:

#include <unistd.h>
int pipe(int pipefd[2]);    //fd[0] is the read side and fd[1] is the write side

Return value:
Success: 0
Failed: - 1
example:

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

int main()
{
        int fd[2];   //File descriptor
        pid_t pid;
        char buf[128];

        if(pipe(fd) == -1){                //Create pipe
                printf("pipe error!\n");
                exit(-1);
        }

        pid = fork();        //Create child process
        if(pid == 0){
                close(fd[1]);     //Close write side
                read(fd[0],buf,128);
                printf("message form Parent process: %s\n",buf);
        }else if(pid > 0){
                close(fd[0]);     //Close the reader
                write(fd[1],"hello!",6);
        }else{
                printf("fork error!\n");
                exit(-1);
        }

        return 0;
}

2, FIFO

FIFIO, also known as named pipe, is a file type. It has the following characteristics:

① FIFO can exchange data between unrelated processes, which is different from anonymous pipes.

② FIFO has a pathname associated with it. It exists in the file system in the form of a special device file.

③ FIFO capacity is limited, and data reading follows "first in first out".
Function prototype:

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

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

Return value:
Success: 0
Failed: - 1
example:
Read end: fifo_read.c

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

int main()
{
        int fd;
        char buf[128];

		//Create FIFO
        if(mkfifo("./fifo",0600) == -1 && errno != EEXIST){
                perror("error:");
        }

		//Open FIFO
        fd = open("./fifo",O_RDONLY);

		//receive messages
        while(1){
                read(fd,buf,128);
                printf("message form write: %s\n",buf);
        }
        close(fd);

        return 0;
}

Write side: fifo_write.c

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

int main()
{
        int fd;

		//Open FIFO
        fd = open("./fifo",O_WRONLY);

		//send message
        while(1){
                write(fd,"hello!",6);
                sleep(1);
        }

        close(fd);

        return 0;
}

3, Message queue

Message queue is a linked list of messages stored in the kernel. Each message queue has its own identifier. Message queue has the following characteristics:
① Message queues are record oriented, where messages have a specific format and a specific priority.

② Message queuing is independent of sending and receiving processes. When a process terminates, the message queue and its contents will not be deleted.

③ Message queue can realize random query of messages. Messages do not have to be read in first in first out order, but can also be read according to the type of message.
Function prototype:

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

// Create or open a message queue: the queue ID is returned successfully, and - 1 is returned for failure
int msgget(key_t key, int flag);
// Send a message (write information to the message queue): 0 for success and - 1 for failure
int msgsnd(int msqid, const void *msgp, size_t msgsz, int msgflg);
// Reading message: the length of message data is returned successfully, and - 1 is returned for failure
int msgrcv(int msqid, void *msgp, size_t msgsz, long msgtyp,int msgflg);
// Control message queue: 0 for success and - 1 for failure
int msgctl(int msqid, int cmd, struct msqid_ds *buf);

//When the system establishes IPC communication (message queue, semaphore and shared memory), an ID value must be specified. Usually, the ID value is obtained through ftok function.
key_t ftok(const char *pathname, int proj_id);

Parameter Description:

key: k Is 0(IPC_PRIVATE)When, a new message queue is established; when it is a 32-bit integer greater than 0, it depends on the parameter flag To determine the action. This value is usually required to come from ftok Returned IPC Key value

flag: IPC_CREAT,If the key value and key If there are equal message queues, a new message queue will be created; if there is such a message queue, the identifier of the message queue will be returned

msqid: Message queue I identifier 

msgp: The message sent to the queue, a structure, including type and content.

msgsz: The size of the message to be sent, excluding the 4 bytes occupied by the message type,Namely mtext Length of

msgtyp: >0 When receiving type is equal to msgtyp News of

cmd: IPC_STAT: hold msgid_ds The data in the structure is set as the current association value of the message queue, that is, it is overwritten with the current association value of the message queue msgid_ds Value of.
	 IPC_SET: If the process has sufficient permissions, set the current association value of the message queue to msgid_ds Value given in structure
	 IPC_RMID: Delete message queue

buf: buf Is pointing msgid_ds Structure, which points to the structure of message queue mode and access rights

fname: Self specified file name(The file must exist and be accessible)

proj_id: Self set sub serial number

example:
msgsend.c:

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

//Message structure
struct msg
{
        long mtype;
        char mtext[128];
};

int main()
{
        int msgID;
        key_t key;
        struct msg sendBuf = {888,"This is server!"};
        struct msg readBuf;

		//Get key value
        key = ftok(".",6);
		
		//Create / open message queue
        msgID = msgget(key,IPC_CREAT|0777);
        if(msgID == -1){
                printf("error!\n");
                exit(-1);
        }

		//Send message of type 888
        msgsnd(msgID,&sendBuf,sizeof(sendBuf.mtext),0);

		//Receive messages of type 999
        msgrcv(msgID,&readBuf,sizeof(readBuf.mtext),999,0);
        printf("message form client: %s\n",readBuf.mtext);

		//Delete message queue
        msgctl(msgID,IPC_RMID,NULL);

        return 0;
}

msgread.c:

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

//Message structure
struct msg
{
        long mtype;
        char mtext[128];
};

int main()
{
        int msgID;
        key_t key;
        struct msg sendBuf = {999,"I get you!"};
        struct msg readBuf;
		
		//Get key value
        key = ftok(".",6);

		//Open / create message queue
        msgID = msgget(key,IPC_CREAT|0777);
        if(msgID == -1){
                printf("error!\n");
                exit(-1);
        }

		//Receive information of type 888
        msgrcv(msgID,&readBuf,sizeof(readBuf.mtext),888,0);
        printf("message form server: %s\n",readBuf.mtext);

		//Send a message of type 999
        msgsnd(msgID,&sendBuf,sizeof(sendBuf.mtext),0);

		//Delete message queue
        msgctl(msgID,IPC_RMID,NULL);

        return 0;
}

4, Shared memory

Shared memory refers to two or more processes sharing a given storage area. It has the following characteristics:

① Shared memory is the fastest IPC (interprocess communication) because processes access memory directly.

② Because multiple processes can operate at the same time, synchronization is required.

③ Semaphores + shared memory are usually operated together. Semaphores are used to synchronize access to shared memory.
Related commands:
Under unix/linux, there are often some problems because the shared memory, semaphore, queue and other shared information are not clear.
The command to view the memory of shared information is ipcs [-m|-s|-q].

-m  List shared memory
-s  List shared semaphores
-q  List shared queues

The clear command is the ipcrm [-m|-s|-q] id number.

-m  Delete shared memory	
-s  Delete shared semaphore
-q  Delete shared queue

Function prototype:

  #include <sys/ipc.h>
  #include <sys/shm.h>
1 
2 // Create or obtain a shared memory: the shared memory ID is returned successfully, and - 1 is returned for failure
3 int shmget(key_t key, size_t size, int flag);
4 // Connect shared memory to the address space of the current process: a pointer to shared memory is returned successfully, and - 1 is returned for failure
5 void *shmat(int shm_id, const void *addr, int flag);
6 // Disconnect from shared memory: 0 for success and - 1 for failure
7 int shmdt(void *addr); 
8 // Information about controlling shared memory: 0 for success and - 1 for failure
9 int shmctl(int shm_id, int cmd, struct shmid_ds *buf);

example:
shm_write.c:

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

int main()
{
        int shmID;
        char *shmaddr;
        key_t key;

		//Get key value
        key = ftok(".",5);

		//Create shared memory
        shmID = shmget(key,1024,IPC_CREAT|0666);
        if(shmID == -1){
                printf("error!\n");
                exit(-1);
        }

		//Mount shared memory
        shmaddr = shmat(shmID,0,0);   //Two zeros are subject to the system default allocation

		//Write message
        strcpy(shmaddr,"hello!");

		//Disconnect from shared memory
        shmdt(shmaddr);

		//Sleep for 10 seconds and delete the shared memory prematurely anyway
        sleep(10);

		//Delete shared memory
        shmctl(shmID,IPC_RMID,NULL);

        return 0;
}

shm_read.c:

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

int main()
{
        int shmID;
        char *shmaddr;
        key_t key;

        key = ftok(".",5);

		//Open shared memory
        shmID = shmget(key,1024,0);
        if(shmID == -1){
                printf("error!\n");
                exit(-1);
        }

        shmaddr = shmat(shmID,0,0);

		//Read message
        printf("txt: %s",shmaddr);

        shmdt(shmaddr);

        return 0;
}

5, Semaphore

  • 1. Signal
    Signal number and name:
    Use the kill -l command to view the signal of linux system. Kill has a special application for signal 0.

      1) SIGHUP       2) SIGINT       3) SIGQUIT      4) SIGILL       5) SIGTRAP
      6) SIGABRT      7) SIGBUS       8) SIGFPE       9) SIGKILL     10) SIGUSR1
     11) SIGSEGV     12) SIGUSR2     13) SIGPIPE     14) SIGALRM     15) SIGTERM
     16) SIGSTKFLT   17) SIGCHLD     18) SIGCONT     19) SIGSTOP     20) SIGTSTP
     21) SIGTTIN     22) SIGTTOU     23) SIGURG      24) SIGXCPU     25) SIGXFSZ
     26) SIGVTALRM   27) SIGPROF     28) SIGWINCH    29) SIGIO       30) SIGPWR
     31) SIGSYS      34) SIGRTMIN    35) SIGRTMIN+1  36) SIGRTMIN+2  37) SIGRTMIN+3
     38) SIGRTMIN+4  39) SIGRTMIN+5  40) SIGRTMIN+6  41) SIGRTMIN+7  42) SIGRTMIN+8
     43) SIGRTMIN+9  44) SIGRTMIN+10 45) SIGRTMIN+11 46) SIGRTMIN+12 47) SIGRTMIN+13
     48) SIGRTMIN+14 49) SIGRTMIN+15 50) SIGRTMAX-14 51) SIGRTMAX-13 52) SIGRTMAX-12
     53) SIGRTMAX-11 54) SIGRTMAX-10 55) SIGRTMAX-9  56) SIGRTMAX-8  57) SIGRTMAX-7
     58) SIGRTMAX-6  59) SIGRTMAX-5  60) SIGRTMAX-4  61) SIGRTMAX-3  62) SIGRTMAX-2
     63) SIGRTMAX-1	64) SIGRTMAX	
    

    Signal processing method:
    Ignore signal (except SIGKILL and SIGSTOP), capture signal and system default action
    Registration of (receiving end) signal processing function:

     signal
     sigaction
    

    (transmitting end) signal transmitting function:

     kill                       //Matched with signal
     sigqueue                   //Matched with sigaction
    

    Function prototype:

    #include <signal.h>
    typedef void (*sighandler_t)(int);
    sighandler_t signal(int signum, sighandler_t handler);
    
    #include <sys/types.h>
    #include <signal.h>
    int kill(pid_t pid, int sig);
    
    #include <signal.h>
    int sigaction(int signum, const struct sigaction *act, struct sigaction *oldact);  //The second oldact is used to back up the original signal
    
    struct sigaction {
    void       (*sa_handler)(int); //The signal processing program does not accept additional data. SIG_IGN is ignored and SIG_DFL is the default action
    void       (*sa_sigaction)(int, siginfo_t *, void *); //The signal processing program can accept additional data and is used in conjunction with sigqueue. When void * is empty, no data is received and non empty data is available.
    sigset_t   sa_mask;//For the signal set of blocking keyword, the signal can be added to the signal blocking word before calling the capture function, and the signal capture function returns to the original value before returning.
    int        sa_flags;//Affecting the behavior of the signal, SA_SIGINFO indicates that the data can be accepted
     };
    //Only one of sa_handler and sa_sigaction can be selected
    
     siginfo_t {
               int      si_signo;    /* Signal number */
               int      si_errno;    /* An errno value */
               int      si_code;     /* Signal code */
               int      si_trapno;   /* Trap number that caused
                                        hardware-generated signal
                                        (unused on most architectures) */
               pid_t    si_pid;      /* Sending process ID */
               uid_t    si_uid;      /* Real user ID of sending process */
               int      si_status;   /* Exit value or signal */
               clock_t  si_utime;    /* User time consumed */
               clock_t  si_stime;    /* System time consumed */
               sigval_t si_value;    /* Signal value */
               int      si_int;      /* POSIX.1b signal */
               void    *si_ptr;      /* POSIX.1b signal */
               int      si_overrun;  /* Timer overrun count; POSIX.1b timers */
               int      si_timerid;  /* Timer ID; POSIX.1b timers */
               void    *si_addr;     /* Memory location which caused fault */
               int      si_band;     /* Band event */
               int      si_fd;       /* File descriptor */
    }
    
    #include <signal.h>
    int sigqueue(pid_t pid, int sig, const union sigval value);
    
    union sigval {
    int   sival_int;
    void *sival_ptr;
    };
    

    Example (primary):
    Receiving end:

    #include <stdio.h>
    #include <signal.h>
    
    //Signal processing function
    void handler(int signum)
    {
        switch(signum){
                case 2:
                        printf("SIGINT  signum: %d\n",signum);
                        break;
                case 9:
                        printf("SIGKILL  signum: %d\n",signum);
                        break;
                case 10:
                        printf("SIGWSR1  signum: %d\n",signum);
                        break;
        }
    }
    
    int main()
    {
    	//Register signal processing function
        signal(SIGINT,handler);
        signal(SIGUSR1,handler);
        signal(SIGKILL,handler);
    
    
        while(1);
      	return 0;
    }
    

    Sender:

    #include <sys/types.h>
    #include <signal.h>
    #include <stdio.h>
    
    int main(int argc,char **argv)
    {
        int signum;
        int pid;
        char cmd[128];
    
        signum = atoi(argv[1]);
        pid = atoi(argv[2]);
    
    	//Both kill and system functions can be used to send signals
    //  kill(pid,signum);
        sprintf(cmd,"kill -%d %d",signum,pid);
        system(cmd);
    
        return 0;
    }
    

    Example (Advanced):
    Receiving end:

    #include <stdio.h>
    #include <signal.h>
    #include <sys/types.h>
    #include <unistd.h>
    
    //Signal processing function
    void handler(int signum, siginfo_t *info, void *text)
    {
    	//Textnon empty indicates there is data
        if(text != NULL){
        		//Two ways to get information
                printf("data:%d\n",info->si_int);
                printf("data:%d\n",info->si_value.sival_int);
                printf("Date From PID:%d\n", info->si_pid);
        }
    
    }
    
    int main()
    {
        struct sigaction act;
    
        act.sa_flags = SA_SIGINFO;
        act.sa_sigaction = handler;
    
        printf("PID: %d\n",getpid());
    
        sigaction(SIGUSR1,&act,NULL);
    
        while(1);
        return 0;
    }
    

    Sender:

    #include <stdio.h>
    #include <signal.h>
    
    int main(int argc,char **argv)
    {
        int signum;
        int pid;
        union sigval value;
    
        signum = atoi(argv[1]);
        pid = atoi(argv[2]);
    
    	//Information carried by signal
        value.sival_int = 666;
    
    	//Send signal
        sigqueue(pid,signum,value);
    
    
        return 0;
    }
    
  • 2. Semaphore
    The semaphore is different from the IPC structure already introduced. It is a counter. Semaphores are used to control the process's use of critical resources (resources that are only allowed to be used by one process at a time are called critical resources), and realize mutual exclusion and synchronization between processes, rather than storing inter process communication data. Semaphores have the following characteristics:
    ① Semaphores are used for inter process synchronization. To transfer data between processes, they need to be combined with shared memory.
    ② Semaphores are based on the PV operation of the operating system, and the operation of the program on semaphores is atomic operation.
    ③ The PV operation of each semaphore is not limited to adding or subtracting 1 from the semaphore value, but can add or subtract any positive integer.
    ④ Semaphores can also be semaphore groups
    Function prototype

    1 #include <sys/sem.h>
    2 // Create or obtain a semaphore group: if the semaphore set ID is returned successfully, if it fails, return - 1
    3 int semget(key_t key, int num_sems, int sem_flags);
    4 // Operate the semaphore group and change the semaphore value: 0 is returned for success and - 1 is returned for failure
    5 int semop(int semid, struct sembuf semoparray[], size_t numops);  
    6 // Relevant information of control semaphore
    7 int semctl(int semid, int sem_num, int cmd, ...);
    
    1 struct sembuf 
    2 {
    3     short sem_num; // Corresponding sequence number in semaphore group, 0 ~ sem_nums-1
    4     short sem_op;  // The change of semaphore value in one operation
    5     short sem_flg; // IPC_NOWAIT, SEM_UNDO
    6 }
    
    1 union semun {
               int              val;    /* Value for SETVAL */
               struct semid_ds *buf;    /* Buffer for IPC_STAT, IPC_SET */
               unsigned short  *array;  /* Array for GETALL, SETALL */
               struct seminfo  *__buf;  /* Buffer for IPC_INFO
                                           (Linux-specific) */
           };
    
    

    example:

    #include <stdio.h>
    #include <sys/types.h>
    #include <sys/ipc.h>
    #include <sys/sem.h>
    #include <stdlib.h>
    
    // Used for semctl initialization
    union semun {
        int val;                 /* Value for SETVAL */
        struct semid_ds *buf;    /* Buffer for IPC_STAT, IPC_SET */
        unsigned short *array;   /* Array for GETALL, SETALL */
        struct seminfo *__buf;   /* Buffer for IPC_INFO
                                           (Linux-specific) */
    };
    
    //p operation to obtain resources
    void semP(int id)
    {
        struct sembuf p;
        p.sem_num = 0;
        p.sem_op = -1;
        p.sem_flg = SEM_UNDO;
        semop(id,&p,1);
    }
    
    //V operation to release resources
    void semV(int id)
    {
        struct sembuf v;
        v.sem_num = 0;
        v.sem_op = 1;
        v.sem_flg = SEM_UNDO;
        semop(id,&v,1);
    }
    
    int main()
    {
        int semID;
        key_t key;
    
    	//Get key value
        key = ftok(".",6);
        if(key == -1){
                printf("ftok error!\n");
                exit(-1);
        }
    
    	//Create a semaphore set. 1 means there is only one semaphore
        semID = semget(key,1,IPC_CREAT|0666);
    
    	//Initialize the semaphore, set the value of the 0th semaphore to initsem
        union semun initsem;
        initsem.val = 0;
        semctl(semID,0,SETVAL,initsem);
    
        int pid = fork();
        if(pid == 0){
        		printf("Subprocess run!\n");
                semV(semID);                   //V operation to release resources
        }else if(pid > 0){
                semP(semID);                   //P operation to obtain resources. There are no resources before the sub process runs
                printf("Parent process run!\n");
                semV(semID);                   //V operation to release resources
                semctl(semID,0,IPC_RMID);      //Delete semaphore
        }else{
                printf("fork error!\n");
                exit(-1);
        }
    
        return 0;
    }
    

6, Comparison and summary of communication (IPC) modes

  1. Pipeline: the speed is slow and the capacity is limited. Only parent-child processes can communicate. Single pipe one-way communication.
  2. FIFO: any process can communicate, but the speed is slow
  3. Message queue: message queue is a linked list of messages, stored in the kernel and identified by the message queue identifier. It can be processed in a non first in first out manner; Message queue is asynchronous communication; Message queues are limited in size and are usually only used for sending small amounts of data; It is only applicable to interprocess communication of a single host.
  4. Shared memory: it can easily control the capacity. It is the fastest IPC mode. It does not have a synchronization mechanism. It needs to combine semaphores or other methods to achieve synchronization between multiple processes. For example, when a process is writing, another process should pay attention to reading, which is equivalent to thread safety in the thread. Of course, the shared memory area can also be used for inter thread communication, but there is no need. Threads already share a piece of memory for one process.
  5. Signal: the signal is used to inform the receiving process that an event has occurred, but there is little information to carry, which is not suitable for communication that needs to carry data often.
  6. Semaphore: a semaphore is a counter that can be used to control the access of multiple processes to shared resources. It is mainly used as a synchronization means between processes and between different threads in the same process.

Tags: Linux

Posted on Sat, 18 Sep 2021 17:16:59 -0400 by danxavier