linux系统编程-mmap(共享内存)

易错点

  • 对新创建的文件(文件没有大小)进行映射,会收到bus error的错误
  • mmap函数的最后一个参数 offset如果不是4K的整数倍,报错:Invalid argument



mmap(共享映射区)存储映射I/O

  • 一个磁盘文件与存储空间中的一个缓冲区相映射,于是当从缓冲区中取数据,就相当于读文件中的相应字节。于此类似,将数据存入缓冲区,则相应的字节就自动写入文件。这样,就可在不适用read和write函数的情况下,使用地址(指针)完成I/O操作。

  • void *mmap(void *addr, size_t length, int prot, int flags,int fd, off_t offset):创建映射区
    addr :建立映射区的首地址,由Linux内核指定。使用时,直接传递NULL
    length :欲创建映射区的大小(不能为0,不能给新创建的文件创建映射区,需要拓展新文件,
        可以小于文件大小,可以大于文件的大小(但是毫无意义)

    prot  :映射区权限PROT_READ、PROT_WRITE、PROT_READ|PROT_WRITE、PROT_NONE、PROT_EXEC
    flags :
        MAP_SHARED:会将映射区所做的操作反映到物理设备(磁盘)上
        MAP_PRIVATE:映射区所做的修改不会反映到物理设备

    fd   :用来建立映射区的文件描述符
    offset :映射文件的偏移(4k的整数倍):MMU帮助完成映射,MMU单位是4K

  • int munmap(void *addr, size_t length):回收映射区
    addr :mmap的返回值,映射空间的首地址
    length :映射区的大小



例子:给文件创建映射区,再往映射区中写内容,看是否写入磁盘空间

  #include <stdio.h>
  #include <string.h>
  #include <sys/mman.h>
  #include <fcntl.h>
  #include <sys/types.h>
  #include <sys/stat.h>
  #include <stdlib.h>
  #include <unistd.h>
   
  
  int main(void)
  {
 
         int fd;
          char*p;
  
         fd = open("./my.txt",O_RDWR|O_CREAT,0664);
         ftruncate(fd,sizeof("julian\n"));
 
         p = mmap(NULL,sizeof("julian\n"),PROT_READ|PROT_WRITE,MAP_SHARED,fd,0);
  
          memcpy(p,"julian\n",sizeof("julian\n"));
  
          munmap(p,sizeof("julian\n"));
          close(fd);
          
         return 0;
 }

结果:在当前目录下创建一个my.txt文件,里面有写入映射区的内容



结论

  • (1) 创建映射区的权限 一定要小于等于 映射的文件的权限(如果指定flags为O_PRIVATE就无所谓)
  • (2) 创建映射区的过程隐含了一次对文件的读操作
  • (3)映射的大小大于0的值,可以小于或者大于文件大小,大于没有意义(后面的空间对应的不是文件内容)
  • (4) 打开的文件的描述符可以先关闭,再释放映射区


文件的实名映射-父子进程间通信

  • mmap函数的flags参数在父子进程间通信时的区别:
    MAP_PRIVATE: (私有映射) 父子进程各自独占映射区
    MAP_SHARED: (共享映射) 父子进程共享映射区

例子:父进程创建映射区,fork子进程,子进程修改映射区的内容,然后,父进程再读映射区,查看是否共享


#include <stdio.h>
 #include <string.h>
 #include <sys/mman.h>
 #include <fcntl.h>
 #include <sys/types.h>
 #include <sys/stat.h>
 #include <stdlib.h>
 #include <unistd.h>
 
 
 int main(void)
 {
 
          int fd;
          int*p;
          pid_t pid;
          int global_val=10;
  
          fd = open("./my2.txt",O_RDWR|O_CREAT,0664); //临时文件,只为了创建映射区
  
          ftruncate(fd,4);
          unlink("./my2.txt");   //删除文件的目录项,使其进程介绍之后就被释放,删除
  
          p = mmap(NULL,4,PROT_READ|PROT_WRITE,MAP_SHARED,fd,0);
  
          close(fd);
          pid=fork();
          if(pid==0)
          {
                  *p=10000;
                  global_val=9999;
                  printf("son   ---*p:%d,global_val:%d\n",*p,global_val);
          }
          else
          {
                  sleep(1);
                  printf("father---*p:%d,global_val:%d\n",*p,global_val);
                  wait(NULL);
  
                  munmap(p,4);
  
          }

          return 0;
  }

结果:son 改了映射区*p的值,father读出来是修改过后的,但是global_val是各自进程独享的(0-3G用户空间),即使子进程改变了,父进程依然没改变
在这里插入图片描述
共享的条件是:mmap函数的Flags参数是shared,如果是private则结果如下:
在这里插入图片描述



匿名映射-父子进程间通信

  • 通过使用我们发现,使用映射区来完成文件读写操作十分方便,父子进程间通信也较容易。但缺陷是,每次创建映射区一定要依赖一个文件才能实现。父子间通常为了建立映射区要open一个temp文件,创建好了再unlink、close掉,比较麻烦。 可以直接使用匿名映射来代替。其实Linux系统给我们提供了创建匿名映射区的方法,无需依赖一个文件即可创建映射区。同样需要借助标志位参数flags来指定。使用 MAP_ANONYMOUS (或MAP_ANON)

在这里插入图片描述



  • 需要注意的是,以上的宏只能在linux使用,在类unix上没有此宏,但是也可以用如下方法:
    ① fd = open("/dev/zero", O_RDWR);
    ② p = mmap(NULL, size, PROT_READ|PROT_WRITE, MMAP_SHARED, fd, 0);

 #include <stdio.h>
 #include <string.h>
  #include <sys/mman.h>
 #include <fcntl.h>
 #include <sys/types.h>
 #include <sys/stat.h>
 #include <stdlib.h>
 #include <unistd.h>
 
 int main(void)
 {
 
         int fd;
         int*p;
         pid_t pid;
         int global_val=10;
 
         fd = open("/dev/zero",O_RDWR);   
         p = mmap(NULL,4,PROT_READ|PROT_WRITE,MAP_SHARED,fd,0);// 4 根据自己定义映射区大小
 
          close(fd);
          pid=fork();
          if(pid==0)
          {
                  *p=10000;
                  global_val=9999;
                  printf("son   ---*p:%d,global_val:%d\n",*p,global_val);
          }
          else
          {
                  sleep(1);
                  printf("father---*p:%d,global_val:%d\n",*p,global_val);
                  wait(NULL); 
                  munmap(p,4);
          } 

         return 0;
  }

使用宏和/dev/zero文件两个结果是一样的
在这里插入图片描述



mmap无血缘关系进程间通信

  • 两个进程打开同一个文件,再分别创建映射区,则映射区对应都是同一个文件,所以是同一个映射区,可以利用其来进程无血缘关系间的进程通信

例子:mmap_r.c:利用一个文件创建映射区,然后不断从中读数据(来自另一个进程写的结果)

   #include <stdio.h>
   #include <string.h>
   #include <sys/mman.h>
   #include <fcntl.h>
   #include <sys/types.h>
   #include <sys/stat.h>
   #include <stdlib.h>
   #include <unistd.h>
    
 struct student
 {
     int id;
     char name[20];
  };
  
  
  int main(int argc,char* argv[])
  {
  
          int fd;
          struct student student1;
          struct student* p;
  
  
          fd = open(argv[1],O_RDWR);  //文件作为参数传进来
  
          p = mmap(NULL,sizeof(student1),PROT_READ|PROT_WRITE,MAP_SHARED,fd,0);
  
          close(fd);
 
          while(1)
          {
                  printf("id=%d name=%s\n",p->id,p->name);
                  sleep(2);
          }
  
          munmap(p,sizeof(student1));
  
          return 0;
  };


mmap_w.c:利用一个文件创建映射区,然后不断往返回的地址 覆盖性的写数据
#include <stdio.h>
#include <string.h>
#include <sys/mman.h>
#include <fcntl.h>
#include <sys/types.h>
#include <sys/stat.h>
#include <stdlib.h>
#include <unistd.h>
   
  struct student
  {
      int id;
      char name[20];
  };
  
  
  int main(int argc,char* argv[])
  {
          int fd;
          struct student student1={1,"julian"};
          struct student* p;
  
  
          fd=open(argv[1],O_RDWR|O_CREAT,0644); 
          p = mmap(NULL,sizeof(student1),PROT_READ|PROT_WRITE,MAP_SHARED,fd,0);//
          close(fd); 

          while(1)
          {
                  sleep(1);
                  memcpy(p,&student1,sizeof(student1));
                  student1.id++;
          }
  
          munmap(p,sizeof(student1));
  
          return 0;
  }


结果:一个进程不断的读出另一个进程写进映射区的数据
在这里插入图片描述

猜你喜欢

转载自blog.csdn.net/zzyczzyc/article/details/82990108