Linux系统应用编程(三)进程间通信

Linux系统应用编程(三)进程间通信

一、什么是IPC机制?/ 什么是进程间通信?

​ 操作系统中的进程并发执行时,可以是独立的也可以是协作的。独立的就是说该进程运行是不与其他进程共享数据,协作的就是说该进程能与其他进程共享数据,能影响其他进程或者被其他进程所影响。这种协作进程之间进行数据交互的过程就称作进程间通信机制,也就是IPC。进程间通信的方式在Linux中主要有:①管道(匿名管道和命名管道)、②消息队列、③共享内存、④信号、⑤Socket等
(PS:《Unix环境高级编程(第2版)》中,还介绍了Streams这一种方式,Steams和Socket可用于不同主机间的进程通信)

二、进程间通信的方式(单机通信)

1.管道
(1)匿名管道

在这里插入图片描述

#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)命名管道

在这里插入图片描述

2.消息队列
(1)消息队列的理解

消息队列是一种进程间通信机制,它允许不同进程之间通过共享消息实现通信和同步。在Linux中,消息队列由内核管理维护,每个消息队列都有唯一的标识符(也就是队列ID)。
<通过(2)消息队列的使用,可以进一步知道>:调用msgsnd()发送消息时,该消息是被添加到了消息队列的队尾,在调用msgrcv时,如果不指定接收的消息的类型,则遵循队列的"先进先出"的规则,也就是此时接收的消息是队列的第一条消息;如果指定接收的消息类型,则可以选择接收对应的消息,无论该消息是在队尾还是队首,从这里也可以看出,消息队列的结构实际上在内核中是一个链式结构。每一个节点是一条消息,节点与节点之间按照队列的顺序排列。

(2)消息队列的使用

在这里插入图片描述
在这里插入图片描述

#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;
}

在这里插入图片描述

3.共享内存

共享内存是进程间通信的一种高效率的方式,通过向内核申请一块内存空间,进程将其映射到自己的虚拟内存空间,多个进程可以通过读写操作自己的这片虚拟内存,对共享内存内的共享数据进行读写,实现进程间的数据共享。在这个过程中,由于允许多个进程操作共享数据,可能会造成类似Java的线程安全问题,所以需要同步机制来保证共享数据的安全。
<同步机制可用信号量,见文末>

#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.信号
(1)信号的理解

信号实际上是Linux系统的软中断,为Linux提供了一种处理异步通信事件的方法,信号可以由内核、进程发送,其原理是利用进程的中断机制,当信号到达时,中断当前进程的执行,转而执行信号处理函数。每一个信号都有一个id唯一标识。进程在收到信号时有三种处理方式:①忽略、②捕捉响应、③默认动作

(2)信号的使用

在这里插入图片描述

▲发送信号和信号处理kill( )和signal( )的使用 ——(略)

▲发送信号并携带消息sigqueue( )和sigaction( )的使用

在这里插入图片描述

  • siginfo_t结构体和sigval联合体:

在这里插入图片描述

#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;
}

在这里插入图片描述

5.信号量
(1)信号量的理解

Linux中的信号量(Semaphore)是一种用于协调多个进程对共享资源访问的同步机制。它本质上是一个计数器,用于限制对共享资源的访问数量,并保证在同一时间内只能有一个进程访问该资源。信号量的基本操作包括三个原语:创建信号量(semget)、销毁信号量(semctl)和信号量PV操作。其中,P操作用于获取(申请)信号量,V操作用于释放(归还)信号量。

<3.共享内存>提到了多个进程操作共享资源时可能会出现类似Java的线程安全问题,也就是多个进程同时访问和修改共享资源时,没有进行同步而发生了竞争条件,导致数据不一致等问题。例如以下代码:

(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;
}

在这里插入图片描述
在这里插入图片描述

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

(2)信号量的使用

在这里插入图片描述
在这里插入图片描述

  • 经典同步问题:多窗口售票BUG解决
#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;
}

三、第五篇 进程间通信的方式(多机通信)- 网络socket

在这里插入图片描述

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

AF_INET6

你的鼓励将是我创作的最大动力

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值