Linux system application programming (3) inter-process communication

Linux system application programming (3) inter-process communication

1. What is the IPC mechanism? / What is inter-process communication?

When processes in the operating system are executed concurrently, they can be independent or cooperative. Independent means that the process does not share data with other processes when running. Collaborative means that the process can share data with other processes and can affect other processes or be affected by other processes. This process of data exchange between collaborative processes is called an inter-process communication mechanism, or IPC. The main methods of inter-process communication in Linux include: ① pipes (anonymous pipes and named pipes), ② message queues, ③ shared memory, ④ signals, ⑤ Socket, etc.
(PS:《Unix环境高级编程(第2版)》中,还介绍了Streams这一种方式,Steams和Socket可用于不同主机间的进程通信)

2. Methods of inter-process communication (stand-alone communication)

1. Pipeline
(1) Anonymous pipe

Insert image description here

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

int main(){
    
    

    int pipefd[2];
    char *writeBuffer = "parents write to child";
    char *readBuffer = (char *)malloc(128);

    int retn = pipe(pipefd);
    if(retn == -1){
    
    
        printf("error:%d\n",errno);
        perror("pipe");
    }
    printf("create pipe succeed\n");

    pid_t pid = fork();
    if(pid == -1){
    
    
        perror("fork");
        exit(-1);
    }

    if(pid > 0){
    
        //parents process:write
        close(pipefd[0]);   //close read fd
        write(pipefd[1],writeBuffer,strlen(writeBuffer));
        wait(NULL);
    }else{
    
              //child process:read
        close(pipefd[1]);   //close write fd
        read(pipefd[0],readBuffer, strlen(writeBuffer));
        printf("child read: %s\n",readBuffer);
    }
    return 0;
}
(2) Named pipe

Insert image description here

2.Message queue
(1) Understanding of message queue

Message queue is an inter-process communication mechanism that allows communication and synchronization between different processes by sharing messages. In Linux, message queues are managed and maintained by the kernel, and each message queue has a unique identifier (that is, queue ID).
<通过(2)消息队列的使用,可以进一步知道>:When msgsnd() is called to send a message, the message is added to the end of the message queue. When msgrcv is called, if the type of message received is not specified, the "first in, first out" rule of the queue is followed, that is, at this time The received message is the first message in the queue; if you specify the message type to be received, you can choose to receive the corresponding message, whether the message is at the end of the queue or at the head of the queue. It can also be seen from here that the structure of the message queue is actually It is a chain structure in the kernel. Each node is a message, and the nodes are arranged in the order of the queue.

(2) Use of message queue

Insert image description here
Insert image description here

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

typedef struct Msg{
    
    
    long msgType;       //消息类型(一个正整数来表示)
    char msgText[128];  //消息内容
}Msg;

int main(){
    
    

    key_t key = ftok(".",'a');	//ftok()获取消息队列的键值key
    int msgid = msgget(key,IPC_CREAT|0600);	// 打开/创建消息队列

    /* 准备发送的两条类型不同的消息:一条以当前pid号表示消息类型,一条以2022标识 */
    Msg msgSendType1 = {
    
    getpid(),"Child send message(1) to Parents"};
    Msg msgSendType2 = {
    
    2022,"Child send message(2) to Parents"};

    Msg *msgRcv = (Msg *)malloc(sizeof(Msg));
    if(msgid == -1){
    
    
        perror("msgget");
        exit(-1);
    }

    /* 创建进程:父进程接收消息;子进程发送消息 */
    int pid = fork();	
    if(pid == -1){
    
    
        perror("fork");
        exit(-1);
    }

    if(pid == 0){
    
      	 /* 子进程发送消息,0表示发送完成再返回 */
        int nsend1 = msgsnd(msgid,&msgSendType1,strlen(msgSendType1.msgText),0);
        int nsend2 = msgsnd(msgid,&msgSendType2,strlen(msgSendType2.msgText),0);
        if(nsend1 == -1 || nsend2 == -1)perror("msgsnd");
        printf("child send done\n");
    }else{
    
         	    /* 父进程接收消息,只接受消息类型为2022的那一条 */
        msgrcv(msgid,msgRcv,sizeof(msgRcv->msgText) ,2022,0);
        printf("Parents process received: message type=%ld\n%s\n",msgRcv->msgType,msgRcv->msgText);
        wait(NULL);
    }
    //msgctl(msgid,IPC_RMID,NULL);
    return 0;
}

Insert image description here

3. Shared memory

Shared memory is an efficient way of inter-process communication. By applying for a piece of memory space from the kernel, the process maps it to its own virtual memory space. Multiple processes can read and write their own virtual memory to share it. Read and write shared data in memory to realize data sharing between processes. In this process, allowing multiple processes to operate on shared data may cause thread safety issues similar to Java, so a synchronization mechanism is needed to ensure the security of shared data.
<同步机制可用信号量,见文末>

#include "ipcAll.h"

#define SHM_SIZE 4096	//共享内存的大小

int main(){
    
    
    /* 获取key */
    key_t key = ftok(".",'a');

    /* 获取共享内存,权限0666 */
    int shmid = shmget(key,SHM_SIZE,IPC_CREAT|0666);
    if(shmid == -1){
    
    
        perror("shmget");
        exit(-1);
    }
    printf("share memory get succeed\n");

    /* 共享内存映射到shmAddr */
    char *shmAddr = NULL;
    shmAddr = (char *)shmat(shmid,NULL,0);
    if(shmAddr == NULL){
    
    
        perror("shmat");
        exit(-2);
    }else{
    
    
        printf("share memory map succeed\n");
    }

    /* 通过shmAddr读写共享内存 */
    printf("write shared memory:");
    fgets(shmAddr,128,stdin);
    printf("read shared memory:%s\n",shmAddr);

    /* 取消映射 */
    shmdt(shmAddr);

    /* 移除共享内存 */
    shmctl(shmid,IPC_RMID,NULL);
    system("ipcs -m");  //查看共享内存
    return 0;
}
4. Signal
(1) Understanding of signals

Signals are actually soft interrupts of the Linux system, which provide Linux with a method of handling asynchronous communication events. Signals can be sent by the kernel or process. The principle is to use the interrupt mechanism of the process to interrupt the execution of the current process when the signal arrives. , and then execute the signal processing function. Each signal is uniquely identified by an id. The process has three processing methods when receiving a signal: ① ignore, ② capture response, ③ default action

(2) Use of signals

Insert image description here

▲The use of sending signals and signal processing kill() and signal() ——(omitted)

▲Send signals and carry messages using sigqueue() and sigaction()

Insert image description here

  • siginfo_t structure and sigval union:

Insert image description here

#include "ipcAll.h"

void sig10Handler(int signum, siginfo_t * siginfo, void *infoRetn){
    
    
    int shmid = shmget(ftok(".",'a'),2048,IPC_CREAT|0666);
    siginfo->si_value.sival_ptr = shmat(shmid,NULL,0);
    if(infoRetn != NULL) {
    
    
        printf("Received signal %d from %d\n", siginfo->si_signo, siginfo->si_pid);
        printf("Received message: <%s> form %d\n", (char *) (siginfo->si_value.sival_ptr),siginfo->si_pid);
    }
}

int main(){
    
    

    struct sigaction act;
    sigemptyset(&act.sa_mask);
    act.sa_flags = SA_SIGINFO;
    act.sa_sigaction = sig10Handler;

    sigaction(SIGUSR1,&act,NULL);
    printf("%d:waiting signal...\n",getpid());
    while (1){
    
    
        sleep(1);
    }
    return 0;
}
#include "ipcAll.h"

int main(int argc,char **argv){
    
    
    if(argc < 3){
    
    
        printf("Usage: %s [signum] [pid]\n",argv[0]);
        exit(-1);
    }
    int signum = atoi(argv[1]);
    pid_t pid = atoi(argv[2]);

    /* 创建共享内存 */
    int shmid = shmget(ftok(".",'a'),2048,IPC_CREAT|0666);
    union sigval val = {
    
    
            .sival_ptr = shmat(shmid,NULL,0)    //将val.sival_ptr执行共享内存
    };

    /* 往val.sival_ptr写数据,再通过sigqueue发送信号并携带数据,发送到另一进程*/
    while (1) {
    
    
        printf("please input the message (\"exit\" to quit):");
        scanf("%s",(char *)val.sival_ptr);
        if (sigqueue(pid, signum, val) == -1) {
    
         //发送
            printf("Send signal %s error\n", argv[1]);
        }
        if (strstr(val.sival_ptr, "exit") != NULL) {
    
        //exit退出,取消共享内存映射,删除
            shmdt(val.sival_ptr);
            shmctl(shmid, IPC_RMID, NULL);
            break;
        }
    }
    return 0;
}

Insert image description here

5. Signal amount
(1) Understanding of semaphores

Semaphore in Linux is a synchronization mechanism used to coordinate multiple processes' access to shared resources. It is essentially a counter that limits the number of accesses to a shared resource and guarantees that only one process can access the resource at the same time. The basic operations of a semaphore include three primitives: creating a semaphore (semget), destroying a semaphore (semctl) and semaphore PV operations. Among them, the P operation is used to obtain (apply for) the semaphore, and the V operation is used to release (return) the semaphore.

It was <3.共享内存>mentioned that Java-like thread safety issues may occur when multiple processes operate shared resources. That is, when multiple processes access and modify shared resources at the same time, race conditions occur without synchronization, leading to data inconsistency and other issues. For example the following code:

  • Classic synchronization problem: multi-window ticketing (C language version, Java thread version, see article: Part 9 Java Multithreading )

(PS:这里fork()出两个进程充当售票窗口,同时售卖100张票,这100张票在共享内存中,两个进程共享此资源,代码加入一丢丢面向对象思想

#include "ipcAll.h"

/* 结构体:售票窗口"类" */
typedef struct {
    
    
    int *ticketNum;  //窗口余票数
    char *windowsName;      //窗口名字
    void (*sell) (int *,char *);    //函数指针:窗口售票"方法"
}Window;

/* 售票方法 */
void ticketSell(int *tickNum,char *windowName){
    
    
    static int cnt = 1;
    if(*tickNum > 0){
    
    
        //usleep(50*1000);   //50ms 加入睡眠,为了看到出现错票的概率
        (*tickNum)--;
        printf("(%d)windoes: %s sell ticket,now ticket sum: %d\n",cnt++,windowName,*tickNum);
    }
}


int main(){
    
    

    /* 共享资源:100张票,共享内存中 */
    int shmid = shmget(ftok(".",'a'),2048,IPC_CREAT|0666);
    if(shmid == -1){
    
    
        perror("shmget");
    }
    int *ticketSum = (int *)shmat(shmid,NULL,0);
    if(ticketSum != NULL)
        *ticketSum = 100;
    else perror("shmat");

    /* 001售票窗口 */
    Window window1 = {
    
    
            .ticketNum = ticketSum,
            .windowsName = "001",
            .sell = ticketSell
    };

    /* 002售票窗口 */
    Window window2 = {
    
    
            .ticketNum = ticketSum,
            .windowsName = "002",
            .sell = ticketSell
    };

    pid_t pid = fork(); //父子进程模拟售票窗口
    if(pid == 0){
    
    
        while (*(window1.ticketNum) > 0) {
    
    
            window1.sell(window1.ticketNum, window1.windowsName);
        }
    }else{
    
    
        while (*(window2.ticketNum) > 0) {
    
    
            window2.sell(window2.ticketNum, window2.windowsName);
        }
    }
    return 0;
}

Insert image description here
Insert image description here

为了解决这种问题,必须采用同步机制,保证在同一时间内只能有一个进程访问该资源,直到该进程操作完成才能有其他进程进行操作

(2) Use of semaphore

Insert image description here
Insert image description here

  • Classic synchronization problem: multi-window ticketing bug solved
#include "ipcAll.h"
#include <pthread.h>


/* 结构体:售票窗口"类" */
typedef struct {
    
    
    int *ticketNum;  //窗口余票数
    char *windowsName;      //窗口名字
    void (*sell)(int *, char *);    //函数指针:窗口售票"方法"
} Window;

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操作:sem_op<0 获得信号量0 */
void operation_p(int semid){
    
    
    struct sembuf set = {
    
    
            .sem_flg = SEM_UNDO,
            .sem_num = 0,
            .sem_op = -1
    };
    semop(semid,&set,1);
}


/* V操作:sem_op>0 释放信号量0 */
void operation_v(int semid){
    
    
    struct sembuf set = {
    
    
            .sem_flg = SEM_UNDO,
            .sem_num = 0,
            .sem_op = 1
    };
    semop(semid,&set,1);
}

int semid;
/* 售票方法(同步方法) */
void ticketSell(int *tickNum, char *windowName) {
    
    
    operation_p(semid);
    static int cnt = 1;
    if (*tickNum > 0) {
    
    
        usleep(20 * 1000);   //200ms 加入睡眠,为了看到出现错票的概率
        (*tickNum)--;
        printf("(%d)windoes: %s sell ticket,now ticket sum: %d\n", cnt++, windowName, *tickNum);
    }
    operation_v(semid);
}


int main() {
    
    

    /* 共享资源:100张票,共享内存中 */
    int shmid = shmget(ftok(".", 'a'), 2048, IPC_CREAT | 0666);
    if (shmid == -1) {
    
    
        perror("shmget");
    }
    int *ticketSum = (int *) shmat(shmid, NULL, 0);
    if (ticketSum != NULL)
        *ticketSum = 100;
    else perror("shmat");

    /* 001售票窗口 */
    Window window1 = {
    
    
            .ticketNum = ticketSum,
            .windowsName = "001",
            .sell = ticketSell
    };

    /* 002售票窗口 */
    Window window2 = {
    
    
            .ticketNum = ticketSum,
            .windowsName = "002",
            .sell = ticketSell
    };
	
    /* 获得信号量(1个) */
    semid = semget(ftok("..", 'a'), 1, IPC_CREAT | 0666);
    union semun initSem = {
    
    .val = 1};
    semctl(semid, 0,SETVAL,initSem);

    pid_t pid = fork(); //父子进程模拟售票窗口
    if (pid == 0) {
    
    
        while (*(window1.ticketNum) > 0) {
    
    
            window1.sell(window1.ticketNum, window1.windowsName);
        }
    } else {
    
    
        while (*(window2.ticketNum) > 0) {
    
    
            window2.sell(window2.ticketNum, window2.windowsName);
        }
    }

    shmctl(shmid,IPC_RMID,NULL);
    semctl(semid,0,IPC_RMID);

    return 0;
}

3. Part 5: Inter-process communication methods (multi-machine communication) - network socket

Insert image description here

Guess you like

Origin blog.csdn.net/weixin_54429787/article/details/130041695