IPC(进程间通信)详解

版权声明:本文为博主原创文章,未经博主允许不得转载。 https://blog.csdn.net/IT_10/article/details/90174175

Linux环境下,进程地址空间相互独立,每个进程各自有不同的用户地址空间。任何一个进程的全局变量在另一个进程中都看不到,所以进程和进程之间不能相互访问,要交换数据bi必须通过内核,在内核中开辟一块缓冲区,进程1把数据从用户空间放至内核缓冲区,进程2再从内核缓冲区把数据读走,内核提供的这种机制称为进程间通信(IPC InterProcess Communication)
在这里插入图片描述
在进程间完成数据传递需要借助操作系统提供的特殊方法,如:文件(因为两个进程共享打开的文件描述符,即虚拟地址空间内核态中的PCB映射到物理地址空间中的同一块地方)、管道、信号、共享内存、消息队列、套接字、命名管道等,有些方法已经被淘汰,现在常用的进程间通信方法有:
**
1.管道(使用最简单)
2.信号(开销最小)
3.共享映射区MMAP(无血缘关系的IPC)
4.本地套接字(最稳定)
**

管道

作用于有血缘关系的进程间通信,调用pipe函数即可创建一个管道,并且打开了读端和写端的文件描述符,并且通过函数的传入参数返回打开的文件描述符。

#include <unistd.h>
int pipe(int pipefd[2])

// 成功返回0,失败返回-1

管道的特质:
a)本质是一个伪文件(Linux中7中文件类型之一,实质为内核缓冲区)
b)由两个文件描述符引用,一个表示读端,一个表示写端。
c)规定数据从管道的写端流入,读端流出
管道的原理:管道实质为内核使用环形队列机制,借助内核缓冲区(4KB,可以用ulimit -a命令查看,结果是 pipe size 512 bytes 8 ,其中,512代表一个扇区的大小,8表示用了8个扇区,所以是4KB,大小可调节)实现。
管道的局限性:
a)数据能自己读不能自己写
b)数据一旦被读走,便不存在于管道中
c)由于管道采用双向半双工通信方式(数据的流动方向是单向的称之为半双工,双向半双工是指数可以从A端读B端写也可以从B端读A端写,双向全双工,即两端都可读可写,比如电话通话,只读不写或者只写不读称为单工通信,如BB机),因此,数据只能在一个方向上流动
d)只能在有公共祖先的进程间使用pipe
对于无血缘关系的进程,可以使用FIFO实现通信,也是管道的一种
代码实现:

#include <stdio.h>
#include <unistd.h>
#include <stdlib.h>
#include <string.h>
int main(void){
    int fd[2];//作为传入参数,接收文件描述符
    pid_t pid;
    
    int ret = pipe(fd);
    if(-1 == ret){
        perror("pipe error:");
        exit(1);
    }
    pid = fork();
    if(-1 == pid){
        perror("fork error:");
        exit(1);
    }else if(0 == pid){//子进程 读数据
        //由于此时子进程掌握着管道的读写两端,因此需要关闭写端保障单>向通信
        //认为规定,fd[0]为读端,fd[1]为写端
        close(fd[1]);
        char buf[1024];
        ret = read(fd[0],buf,sizeof(buf));
        if(0 == ret){
            printf("waitting write-----\n");
        }
        write(STDOUT_FILENO,buf,ret);
    }else{
        close(fd[0]);
        write(fd[1],"hello pipe\n",strlen("hello pipe\n"));
        sleep(1);
    }
    return 0;
}

共享内存

mmap函数,用于父子进程有血缘关系的IPC,作用是把磁盘上的文件映射到内存中,完成内存和磁盘文件间的一致性,也可用于父子进程间通信。
在这里插入图片描述
参数:
addr:建立映射区的首地址,有Linux内核指定,一般为NULL
length:欲创建映射区的大小
port:映射区权限PROT_READ PROT_WRITE PROT_READ|PORT_WRITE
flags:标志位参数,值为MAP_SHARED时,会将映射区所做的操作反映到物理设备(磁盘)上,值为MAP_PRIVATE时,不会将映射区所做的操作反映到物理设备(磁盘)上
fd:用来建立映射区的文件描述符
offset:映射文件的偏移(4K的整数倍,即会将映射区所做的操作反映到物理设备(磁盘)上)
返回值:成功返回映射区首地址,失败返回MAP_FAILD宏

mmap创建映射区实例

#include <stdio.h>
#include <unistd.h>
#include <string.h>
#include <sys/mman.h>
#include <stdlib.h>
#include <fcntl.h>

int main(void){
    int len,ret;
    int fd = open("mytest.txt",O_CREAT|O_RDWR,0644);
    char *p = NULL;
    if(fd < 0){
        perror("open error:");
        exit(1);
    }
    if(-1 == len){
        perror("ftruncate error:");
    }
    p = mmap(NULL,4,PROT_READ|PROT_WRITE,MAP_SHARED,fd,0);
    if(p == MAP_FAILED){
        perror("mmap error:");
        exit(1);
    }
    strcpy(p,"abc");//向共享内存中写数据
    close(fd);
    ret = munmap(p,4);
    if(-1 == ret){
        perror("munmap error:");
    }
    return 0;
}

mmap实现父子进程共享实例

#include <stdlib.h>
#include <unistd.h>
#include <stdio.h>
#include <fcntl.h>
#include <sys/mman.h>
#include <sys/wait.h>

int var = 100;

int main(void){
    int *p;
    pid_t pid;
    int fd;

    fd = open("temp",O_RDWR|O_CREAT|O_TRUNC,0644);
    if(fd < 0){
        perror("open error:");
        exit(1);
    }
    unlink("temp"); //删除临时文件的目录项,使之具备被释放的条件
    ftruncate(fd,4);

    p = (int *)mmap(NULL,4,PROT_READ|PROT_WRITE,MAP_PRIVATE,fd,0);
    if(p == MAP_FAILED){
        perror("mmap error");
        exit(1);
    }
    close(fd); //映射区建立完毕,即可关闭文件

    pid = fork();
    if(pid < 0){
        perror("fork error");
        exit(1);
    }else if(0 == pid){
        *p = 2000;
        var = 1000;
        printf("child,*p = %d, var = %d\n",*p, var);
    }else{
        sleep(1);
        printf("parent,*p = %d,var = %d\n",*p, var);
        wait(NULL);

        int ret = munmap(p,4); //释放映射区
        if(-1 == ret){
            perror("munmap error:");
            exit(1);
        }
    }
    return 0;
}

在这里插入图片描述
p = (int *)mmap(NULL,4,PROT_READ|PROT_WRITE,MAP_PRIVATE,fd,0);语句中,将倒数第三个参数设置为MAP_PRIVATE,因此父子进程独占各自的映射区,子进程将映射区的值改为2000,父进程的p值不变。如果把倒数第二个参数改为MAP_SHARED,才能完成父子进程间通信,结果如下
在这里插入图片描述
其中,两个结果的父进程var值为100,子进程var值为1000,因为父子进程共享文件描述符和mmap映射区,0到3G的虚拟地址中的用户态为读时共享写时复制。因此全局变量var在父子进程中各自不影响。
可以发现上面代码中的temp文件很鸡肋,只是用作创建映射区的时候用一下,然后立刻就释放了,为了解决这一问题,便出现了匿名映射

匿名映射

解决了创建映射区必须依赖一个文件才可以实现的鸡肋问题。
int *p = mmap(NULL,4,PROT_READ|PROT_WRITE,MAP_SHARED|MAP_ANONYMOUS,-1,0);
4可以根据实际大小改变

#include <stdlib.h>
#include <unistd.h>
#include <stdio.h>
#include <fcntl.h>
#include <sys/mman.h>
#include <sys/wait.h>

int var = 100;

int main(void){
    int *p;
    pid_t pid;

    p = (int *)mmap(NULL,4,PROT_READ|PROT_WRITE,MAP_SHARED|MAP_ANON,-1,0);
    if(p == MAP_FAILED){
        perror("mmap error");
        exit(1);
    }

    pid = fork();
    if(pid < 0){
        perror("fork error");
        exit(1);
    }else if(0 == pid){
        *p = 2000;
        var = 1000;
        printf("child,*p = %d, var = %d\n",*p, var);
    }else{
        sleep(1);
        printf("parent,*p = %d,var = %d\n",*p, var);
        wait(NULL);

        int ret = munmap(p,4); //释放映射区
        if(-1 == ret){
            perror("munmap error:");
            exit(1);
        }
    }
    return 0;
}

猜你喜欢

转载自blog.csdn.net/IT_10/article/details/90174175