欢迎您访问程序员文章站本站旨在为大家提供分享程序员计算机编程知识!
您现在的位置是: 首页  >  科技

linux 进程通信之 mmap

程序员文章站 2022-05-20 17:04:47
一, "管道PIPE" 二, "FIFO通信" 三,mmap通信 创建内存映射区。 函数mmap:打开一个文件,指定一个文件的区域,作为一个区域,映射到内存中,以后就直接操作那个内存,就能够实现进程间的通信。因为是内存操作,所以速度最快。 addr:固定NULL length:拿出文件中的多长的一段 ......

一,管道pipe

二,fifo通信

三,mmap通信

创建内存映射区。
linux 进程通信之 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\n", 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\n", *mem);
    sleep(3);
    printf("child:mem=%d\n", *mem);
  }
  else if(pid > 0){
    sleep(1);
    printf("parent:mem=%d\n", *mem);
    *mem = 200;
    printf("parent:mem=%d\n", *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\n", 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]:process count
//argv[2]:src file
int main(int argc, char* argv[]){
  if(argc < 3){
    printf("bad argv,need 3 arg");
    return -1;
  }

  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]);
  printf("prosz:%ld, yu:%ld\n", prosz, yu);

  //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, 0666);
  ret = ftruncate(decfd, sz);
  if(ret < 0){
    perror("ftruncate");
    return -1;
  }

  void* vc[atoi(argv[1])];
  for(int i = 0; i < atoi(argv[1]); ++i){
    vc[i] = mmap(null, prosz, prot_read|prot_write, map_shared, decfd, 0);
    if(vc[i] == map_failed){
      perror("mmap die:");
      return -1;
    }
  }

  close(srcfd);
  close(decfd);
}