linux 進程通訊之 mmap

一,管道PIPE

二,FIFO通訊

三,mmap通訊

建立內存映射區。
html

#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:打開一個文件,指定一個文件的區域,做爲一個區域,映射到內存中,之後就直接操做那個內存,就可以實現進程間的通訊。由於是內存操做,因此速度最快。node

  • 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)

釋放內存映射區。linux

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

例子:c++

#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的七個問題:shell

  • 若是更改上面例子裏變量buf的地址,釋放的時候munmap,還能成功嗎?微信

    不能成功。錯誤信息:【Invalid argument】函數

  • 對映射區的操做,越界了會怎麼樣。學習

    • open文件size > 要寫入的size > mmap參數的length:可以所有寫入文件。
    • open文件size < 要寫入的size > mmap參數的length:不能所有寫入文件,可以寫入的size是open文件的size
  • 偏移量隨便填個數字會怎麼樣。3d

    mmap函數出錯,錯誤信息:【Invalid argument】unix

    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]:進程的數量
//argv[2]:要拷貝的文件
int main(int argc, char* argv[]){
  if(argc < 3){
    printf("bad argv,need 3 arg\n");
    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\n", 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

相關文章
相關標籤/搜索