• 进程间的通讯方式


    版权声明:本文为博主原创文章,遵循 CC 4.0 BY-SA 版权协议,转载请附上原文出处链接和本声明。
    本文链接:https://blog.csdn.net/lyn_00/article/details/84789508
    几种常见进程间通信(IPC)方式-共享存储
    前言

    进程间通信是指在不同进程之间传播或交换信息,在Linux环境下,进程地址空间相互独立,每个进程各自有不同的用户地址空间,进程之间不能相互访问。必须通过内核才能进行数据交换。如图:
    在这里插入图片描述
    常见的通信方式有以下几种:

        管道pipe
        有名管道FIFO
        消息队列MessageQueue
        共享存储
        信号量Semaphore
        信号Signal
        套接字Socket

    接下来我们将详细介绍共享存储
    共享存储
    内存映射I/O

    在讲解内存映射之前,我们先简单了解一些虚拟内存的概念。

    虚拟内存为每个进程提供了一个大的、一致的和私有的地址空间,它提供了3个能力:

        将主存看成是一个存储在磁盘上的地址空间的高速缓存,在主存中只保存活动区域,并根据需要在磁盘和主存之间来回传送数据。
        它为每个进程提供了一致的地址空间,从未简化了内存管理。
        它保护了每个进程的地址空间不被其他进程破坏。

    VM系统将虚拟内存分割为虚拟页,物理内存也被分隔为物理页。
    使用寄存器中的内存管理单元[MMU(Memory Management Unit)],利用存放在主存中的页表来动态翻译虚拟地址,就可以使用虚拟地址去访问相应的物理地址。

    内存映射
    内存映射,通过将虚拟内存区域与磁盘上的对象关联起来,以初始化这个虚拟内存区域的内容。
    磁盘上的对象可以是两类,Linux文件系统中的普通文件,或是匿名文件(由内核创建)。
    在这里插入图片描述

    共享内存映射
    将虚拟地址指向同一个物理地址。如图所示
    在这里插入图片描述
    mmap函数

    Linux进程可以使用mmap函数来创建新的虚拟内存区域,并将对象映射到这些区域中。

    void *mmap(void *adrr, size_t length, int prot, int flags, int fd, off_t offset);
    //成功:返回创建的映射区首地址;失败:MAP_FAILED宏


    参数

        addr: 建立映射区的首地址,由Linux内核指定。使用时,直接传递NULL
        length: 欲创建映射区的大小
        prot: 映射区权限PROT_READ、PROT_WRITE、PROT_READ|PROT_WRITE
        flags:标志位参数(常用于设定更新物理区域、设置共享、创建匿名映射区)
        MAP_SHARED: 会将映射区所做的操作反映到物理设备(磁盘)上。
        MAP_PRIVATE: 映射区所做的修改不会反映到物理设备。
        fd: 用来建立映射区的文件描述符
        offset: 映射文件的偏移(4k的整数倍)

    mmap建立的映射区在使用结束后也应调用类似free的函数来释放。

    int munmap(void *addr, size_t length);    
    //成功:返回0; 失败:返回-1


    mmap进程间通信

        父子等有血缘关系的进程之间可以通过 mmap建立的映射区来完成数据通信。但是相应的要在创建映射区的时候指定对应的标志位参参数flags。
        MAP_PRIVATE(私有映射)父子进程各自独占映射区
        MAP_SHARED(共享映射)父子进程共享映射区
        也可以通过匿名映射,不需要以来一个文件就能够实现。同样也需要依赖于标志位的设定(只适用于有血缘关系的进程之间通信)
        使用MAP_ANONYMOUS (或MAP_ANON), 如:
        int *p = mmap(NULL, 4, PROT_READ|PROT_WRITE, MAP_SHARED|MAP_ANONYMOUS, -1, 0);
        没有血缘关系的进程之间通信时需要,用open打开同一个文件 得到fd,再调mmap指定fd,将虚拟内存地映射到同一个物理内存地址上。

    代码样例 父子进程间通信

    #include <stdio.h>
    #include <stdlib.h>
    #include <unistd.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");                             //删除临时文件目录项,使之具备被释放条件.
        //将参数fd指定的文件大小改为参数length指定的大小
        ftruncate(fd, 4);

        //MAP_SHARED
        //父子进程各自独占映射区
        p = (int *)mmap(NULL, 4, PROT_READ|PROT_WRITE, MAP_SHARED, fd, 0);
        //MAP_PRIVATE
        //父子进程共享映射区
        //p = (int *)mmap(NULL, 4, PROT_READ|PROT_WRITE, MAP_PRIVATE, fd, 0);
        if(p == MAP_FAILED){                //注意:不是p == NULL
            perror("mmap error");
            exit(1);
        }
        close(fd);                                  //映射区建立完毕,即可关闭文件

        pid = fork();                               //创建子进程
        if(pid == 0){
            *p = 2000;
           //非共享变量
           //在子进程中改为1000,父进程中仍为100
            var = 1000;
            printf("child, *p = %d, var = %d ", *p, var);
        } else {
            sleep(1);
            printf("parent, *p = %d, var = %d ", *p, var);
            wait(NULL);

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

      

    文件进程间通信

    使用文件也可以完成进程间通信,父进程使用fork创建子进程后,父子进程共享文件描述符,也就是说,共享打开的文件。

    /*
     *父子进程共享打开的文件描述符------使用文件完成进程间通信.
     */
    #include <stdio.h>
    #include <unistd.h>
    #include <string.h>
    #include <stdlib.h>
    #include <fcntl.h>
    #include <sys/wait.h>


    int main(void)
    {
            int fd1, fd2; pid_t pid;
            char buf[1024];
            char *str = "---------test for shared fd in parent child process----- ";


            pid = fork();
            if (pid < 0) {
                    perror("fork error");
                    exit(1);
             //子进程进入
            } else if (pid == 0) {
                    fd1 = open("test.txt", O_RDWR);
                    if (fd1 < 0) {
                            perror("open error");
                            exit(1);
                    }
                    write(fd1, str, strlen(str));
                    printf("child wrote over... ");

            //父进程进入
            } else {
                    fd2 = open("test.txt", O_RDWR);
                    if (fd2 < 0) {
                            perror("open error");
                            exit(1);
                    }
                    sleep(1);                   //保证子进程写入数据

                    int len = read(fd2, buf, sizeof(buf));
                    write(STDOUT_FILENO, buf, len);

                    wait(NULL);
            }

            return 0;
    }


    看到这里你可能会有一些疑惑,文件进程间通信和有名管道FIFO有什么区别呢?

    我们知道管道只有两端,读端和写端,有名管道可以控制读写两端,保证他们的同步性。
    但是对于文件进程间进程通信而言,我们必须自己定义一些同步机制来控制读数据和写数据,否则可能会造成一些错误。
    总结

    共享存储是非常重要的一种进程间通信方式,理解起来也相对来说比较困难有点,但是在了解了一些基本概念之后再去学习就会容易得多。
    ————————————————
    版权声明:本文为CSDN博主「lynalmost」的原创文章,遵循 CC 4.0 BY-SA 版权协议,转载请附上原文出处链接及本声明。
    原文链接:https://blog.csdn.net/lyn_00/article/details/84789508

  • 相关阅读:
    [CF1439B] Graph Subset Problem
    [CF1439C] Greedy Shopping
    [CF1119F] Niyaz and Small Degrees
    [ARC101C] Ribbons On the Tree
    [CF1446C] Xor Tree
    11月24日 模拟赛 题解
    UOJ346
    [CF1229C] Konrad and Company Evaluation
    [CF1326F] Wise Men (Hard Version)
    学军联赛模拟 第二十七测 题解
  • 原文地址:https://www.cnblogs.com/DXGG-Bond/p/11959161.html
Copyright © 2020-2023  润新知