zoukankan      html  css  js  c++  java
  • 进程间的通讯方式

    版权声明:本文为博主原创文章,遵循 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

  • 相关阅读:
    poj 2187 Beauty Contest(旋转卡壳)
    poj 2540 Hotter Colder(极角计算半平面交)
    poj 1279 Art Gallery(利用极角计算半平面交)
    poj 3384 Feng Shui(半平面交的联机算法)
    poj 1151 Atlantis(矩形面积并)
    zoj 1659 Mobile Phone Coverage(矩形面积并)
    uva 10213 How Many Pieces of Land (欧拉公式计算多面体)
    uva 190 Circle Through Three Points(三点求外心)
    zoj 1280 Intersecting Lines(两直线交点)
    poj 1041 John's trip(欧拉回路)
  • 原文地址:https://www.cnblogs.com/DXGG-Bond/p/11959161.html
Copyright © 2011-2022 走看看