zoukankan      html  css  js  c++  java
  • linux 进程通信之 mmap

    一,管道PIPE

    二,FIFO通信

    三,mmap通信

    创建内存映射区。

    #include <sys/mman.h>
    void *mmap(void *addr, size_t length, int prot, int flags,int fd, off_t offset);
    int munmap(void *addr, size_t length);
    

    函数mmap:打开一个文件,指定一个文件的区域,作为一个区域,映射到内存中,以后就直接操作那个内存,就能够实现进程间的通信。因为是内存操作,所以速度最快。

    • addr:固定NULL
    • length:拿出文件中的多长的一段,映射到内存。
    • offset:从文件内容中的哪个位置开始拿。
    • prot
      • PROT_EXEC Pages may be executed
      • PROT_READ Pages may be read.
      • PROT_WRITE Pages may be written.
      • PROT_NONE Pages may not be accessed
    • flags
      • MAP_SHARED:对内存里的值进行修改,会反映到文件,也就是文件也被修改。
      • MAP_PRIVATE:对内存里的值进行修改,不会反映到文件,文件不会被修改。
    • offset:起始位置
    • 返回值
      • 成功:可用的内存的首地址
      • 失败:MAP_FAILED (that is, (void *) -1)

    释放内存映射区。

    #include <sys/mman.h>
    int munmap(void *addr, size_t length);
    
    • addr:mmap的返回值
    • length:mmap创建的长度
    • 返回值:成功0;失败-1.

    例子:

    #include <sys/mman.h>
    #include <sys/types.h>
    #include <sys/stat.h>
    #include <fcntl.h>
    #include <stdio.h>
    #include <string.h>
    #include <unistd.h>
    
    int main(){
      int fd = open("mem", O_RDWR);
      //char* buf = mmap(NULL, 8, PROT_READ|PROT_WRITE, MAP_SHARED, fd, 0);
      char* buf = mmap(NULL, 8, PROT_READ|PROT_WRITE, MAP_PRIVATE, fd, 0);
      printf("%s
    ", buf);
      strcpy(buf, "FFFFF");
      //释放映射区
      munmap(buf, 8);
      close(fd);
    }
    

    mmap的七个问题:

    • 如果更改上面例子里变量buf的地址,释放的时候munmap,还能成功吗?

      不能成功。错误信息:【Invalid argument】

    • 对映射区的操作,越界了会怎么样。

      • open文件size > 要写入的size > mmap参数的length:能够全部写入文件。
      • open文件size < 要写入的size > mmap参数的length:不能全部写入文件,能够写入的size是open文件的size
    • 偏移量随便填个数字会怎么样。

      mmap函数出错,错误信息:【Invalid argument】

      offset必须是4K的整数倍,【0,4*1024。。。】

      用【stat】命令查看文件,发现文件的size实际小于4096,但是【IO Block: 4096】

        File: pi2.c
        Size: 442       	Blocks: 8          IO Block: 4096   regular file
      Device: 801h/2049d	Inode: 424247      Links: 1
      Access: (0664/-rw-rw-r--)  Uid: ( 1000/      ys)   Gid: ( 1000/      ys)
      Access: 2019-05-02 12:54:13.812282158 +0800
      Modify: 2019-04-29 13:49:42.489004001 +0800
      Change: 2019-04-29 13:49:42.489004001 +0800
      
    • 如果文件描述符先关闭,对mmap映射有没有影响。

      没有影响。

    • open的时候,可以用新创建一个文件的方式,来创建映射区吗?

      错误:Bus error (core dumped)。

      错误理由是:创建的文件的size为0,所以出上面的错误。新创建一个文件后,马上把文件大小扩展一下就不会发生错误了。

      int fd = open("mem", O_RDWR|O_CREAT, 0666);
      ftruncate(fd, 8);//把新创建的文件mem的大小扩展为8.
      
    • open文件时,选择O_WRONLY,可以吗

      mmap函数出错,错误:【Permission denied】。

      因为要把文件的内容读到内存,所以隐含一次读取操作,所以没有读的权限的话,就出这个错误。

    • 当选择MAP_SHARED的时候,open文件选择O_RDONLY,prot可以选择【PROT_READ|PROT_WRITE】吗

      mmap函数出错,错误:【Permission denied】。

      MAP_SHARED的时候会去写文件,但是open的时候只有读权限,所以权限不够。

    用mmap实现父子进程间通信的例子:

    注意:参数flags必须是MAP_SHARED,因为2个进程间通信,需要互相读写,所以必须是MAP_SHARED

    #include <stdio.h>
    #include <unistd.h>
    #include <sys/types.h>
    #include <sys/stat.h>
    #include <fcntl.h>
    #include <sys/mman.h>
    #include <sys/wait.h>
    
    int main(){
      int fd = open("mem", O_RDWR);
      int* mem = mmap(NULL, 4, PROT_READ | PROT_WRITE, MAP_SHARED, fd, 0);
      if(mem == MAP_FAILED){
        perror("mmap");
        return -1;
      }
      pid_t pid = fork();
    
      if(pid == 0){
        *mem = 100;
        printf("child:mem=%d
    ", *mem);
        sleep(3);
        printf("child:mem=%d
    ", *mem);
      }
      else if(pid > 0){
        sleep(1);
        printf("parent:mem=%d
    ", *mem);
        *mem = 200;
        printf("parent:mem=%d
    ", *mem);
        wait(NULL);
      }
    
      munmap(mem, 4);
      close(fd);
    }
    

    执行结果:

    child:mem=100
    parent:mem=100
    parent:mem=200
    child:mem=200
    

    不知道读者同学们发现了没有,用mmap有个非常鸡肋的地方,就是必须要使用一个文件,其实这个文件对程序没有什么作业。所以linux给我们提供了一个方法,叫做【匿名映射】。

    匿名映射:在调用mmap函数时候,在flags参数那里,设置【MAP_ANON】,并在fd参数那里设置【-1】。

    int* mem = mmap(NULL, 4, PROT_READ | PROT_WRITE, MAP_SHARED|MAP_ANON, -1, 0);
    

    有个问题,在有些unix系统里是没有【MAP_ANON】【MAP_ANONYMOUS】这2个宏的,这2个宏的作用是一样的,其中一个是简写。那怎么办呢?

    使用下面2个文件去映射,因为要用文件,所以必须还得有open的调用,但好处是不用事先做出一个大小合适的文件了。

    • /dev/zero:可以随意映射,size无限大,诨名叫【聚宝盆】
    • /dev/null:可以随意映射,size无限大,但映射完后,文件里不会存有任何内容,所以也被叫成【无底洞】,一般错误日志太多,而且不想保留的时候,会重定向到这个文件。

    匿名映射的弱点:不能实现无学员关系进程间的通信。

    用mmap实现无血缘关系的进程间通信的例子:

    写入端:

    #include <stdio.h>
    #include <unistd.h>
    #include <sys/types.h>
    #include <sys/stat.h>
    #include <fcntl.h>
    #include <sys/mman.h>
    #include <sys/wait.h>
    
    typedef struct _student{
      int id;
      char name[20];
    }Student;
    
    int main(int argc, char* argv[]){
    
      int fd = open("aaa", O_RDWR|O_TRUNC|O_CREAT, 0666);
      int length = sizeof(Student);
      ftruncate(fd, length);
      Student* std = mmap(NULL, length, PROT_READ | PROT_WRITE, MAP_SHARED, fd, 0);
      
      if(std == MAP_FAILED){
        perror("mmap");
        return -1;
      }
    
      int num = 0;
      while(1){
        std->id = num;
        sprintf(std->name, "xiaoming-%03d", num++);
        sleep(1);
      }
    
      munmap(std, length);
      close(fd);
    }
    
    

    读入端:

    #include <stdio.h>
    #include <unistd.h>
    #include <sys/types.h>
    #include <sys/stat.h>
    #include <fcntl.h>
    #include <sys/mman.h>
    #include <sys/wait.h>
    
    typedef struct _student{
      int id;
      char name[20];
    }Student;
    
    int main(int argc, char* argv[]){
    
      int fd = open("aaa", O_RDWR);
      int length = sizeof(Student);
      Student* std = mmap(NULL, length, PROT_READ | PROT_WRITE, MAP_SHARED, fd, 0);
    
    
      if(std == MAP_FAILED){
        perror("mmap");
        return -1;
      }
      
      while(1){
        printf("id:%03d, name:%s
    ", std->id, std->name);
        sleep(1);
      }
      
    
      munmap(std, length);
      close(fd);
    }
    
    

    利用mmap实现用多个进程拷贝一个文件的例子 核心思想:把要拷贝的文件和目标文件都映射成内存映射区,然后在各自的子进程里做拷贝,拷贝时,注意起点和大小。

    #include <unistd.h>
    #include <sys/types.h>
    #include <sys/stat.h>
    #include <fcntl.h>
    #include <sys/wait.h>
    #include <string.h>
    #include <stdio.h>
    #include <stdlib.h>
    #include <sys/mman.h>
    
    //argv[1]:进程的数量
    //argv[2]:要拷贝的文件
    int main(int argc, char* argv[]){
      if(argc < 3){
        printf("bad argv,need 3 arg
    ");
        return -1;
      }
    
      //用stat函数取得文件的大小
      struct stat sbuf;
      int ret = stat(argv[2], &sbuf);
      if(ret < 0){
        perror("stat");
        return -1;
      }
    
      //文件的大小
      off_t sz = sbuf.st_size;
    
      //余数
      off_t yu = sz % atoi(argv[1]);  
      //每个进程拷贝的大小
      off_t proSz = (sz - yu) / atoi(argv[1]);
    
      //open src file
      int srcfd = open(argv[2], O_RDONLY);
    
      //create target file
      char wk[20] = {0};
      sprintf(wk, "%s.copy", argv[2]);
      int decfd = open(wk,O_RDWR|O_CREAT|O_TRUNC, 0766);
      //创建和src文件同等大小的目标文件
      ret = ftruncate(decfd, sz);
      if(ret < 0){
        perror("ftruncate");
        return -1;
      }
    
      //打开要被拷贝文件的内存映射区
      void* src = mmap(NULL, sz, PROT_READ, MAP_SHARED, srcfd, 0);
      if(src == MAP_FAILED){
        perror("mmap src:");
        return -1;
      }
      
      //打开要目标文件的内存映射区
      void* dst = mmap(NULL, sz, PROT_READ|PROT_WRITE, MAP_SHARED, decfd, 0);
      if(dst == MAP_FAILED){
        perror("mmap dst:");
        return -1;
      }
      
      int i;
      int pCnt = atoi(argv[1]);
      
      //创建子进程,都是这些子进程都是本程序的子进程
      for(i = 0; i < pCnt; ++i){
        //in child process
        if(fork() == 0)
          break;
      }
    
      //子进程的处理
      if(i < pCnt){
        //last time
        if(i == pCnt - 1){
          memcpy(dst+i*proSz, src+i*proSz, proSz+yu);
        }
        else{
          memcpy(dst+i*proSz, src+i*proSz, proSz);
        }
      }
      //父进程的处理
      else{
        for(int i = 0; i < pCnt; ++i){
          wait(NULL);
        }
        //printf("parent pid=%d
    ", getpid());
        if(munmap(src, sz) == -1){
          perror("munmap src");
        }
        if(munmap(dst, sz) == -1){
          perror("munmap dsc");
        }
        close(srcfd);
        close(decfd);
    
      }
    }
    
    

    c/c++ 学习互助QQ群:877684253

    本人微信:xiaoshitou5854

  • 相关阅读:
    P4890 Never·island
    P2617 Dynamic Rankings
    P3243 [HNOI2015]菜肴制作
    P4172 [WC2006]水管局长
    P4219 [BJOI2014]大融合
    P5241 序列
    P1501 [国家集训队]Tree II
    无法读取用户配置文件,系统自动建立Temp临时用户
    组件服务 控制台打不开
    打印服务器 功能地址保护错误
  • 原文地址:https://www.cnblogs.com/xiaoshiwang/p/10823291.html
Copyright © 2011-2022 走看看