创建内存映射区
如果要实现进程间通信,可以通过函数创建一块内存映射区,和管道不同的是管道对应的内存空间在内核中,而内存映射区对应的内存空间在进程的用户区(用于加载动态库的那个区域),也就是说进程间通信使用的内存映射区不是一块,而是在每个进程内部都有一块。
由于每个进程的地址空间是独立的,各个进程之间也不能直接访问对方的内存映射区,需要通信的进程需要将各自的内存映射区和同一个磁盘文件进行映射,这样进程之间就可以通过磁盘文件这个桥梁完成数据的交互。

磁盘文件数据可以完全加载到进程的内存映射区,也可以部分加载到进程的内存映射区。当进程 A 中的内存映射区数据被修改了,数据会被自动同步到磁盘文件,同时和磁盘文件建立映射关系的其他进程内存映射区中的数据也会和磁盘文件进行数据的实时同步,这个同步机制保证了各个进程之间的数据共享。
使用内存映射区既可以用于有血缘关系的进程间通信,也可以用于没有血缘关系的进程间通信。
内存映射区的函数原型如下:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39
| #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);
|
进程间通信
操作内存映射区和操作管道是不一样的,得到内存映射区之后是直接对内存地址进行操作,管道是通过文件描述符读写队列中的数据。管道的读写是阻塞的,内存映射区的读写是非阻塞的。内存映射区创建成功之后,得到了映射区内存的起始地址,使用相关的内存操作函数读写数据就可以。
有血缘关系
由于创建子进程会发生虚拟地址空间的复制,所以在父进程中创建的内存映射区也会被复制到子进程中。在子进程里就可以直接使用这块内存映射区,对于有血缘关系的进程,进程间通信是非常简单的,示例代码如下:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41
|
#include <sys/mman.h> #include <fcntl.h>
int main() { int fd = open("./english.txt", O_RDWR); void* ptr = mmap(NULL, 4000, PROT_READ | PROT_WRITE, MAP_SHARED, fd, 0); if (ptr == MAP_FAILED) { perror("mmap error"); exit(0); }
pid_t pid = fork(); if (pid > 0) { const char* pt = "父进程,写数据..."; memcpy(ptr, pt, strlen(pt) + 1); } else if (pid == 0) { usleep(1); printf("子进程,从映射区读出的数据: %s\n", (char*)ptr); } munmap(ptr, 4000);
return 0; }
|
没有血缘关系
对于没有血缘关系的进程间通信,需要在每个进程中分别创建内存映射区,但是这些进程的内存映射区必须关联相同的磁盘文件,这样才能实现进程间的数据同步。
进程 A 的示例代码:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28
| #include <stdio.h> #include <stdlib.h> #include <unistd.h> #include <string.h> #include <sys/mman.h> #include <fcntl.h>
int main() { int fd = open("./english.txt", O_RDWR); void* ptr = mmap(NULL, 4000, PROT_READ|PROT_WRITE, MAP_SHARED, fd, 0); if (ptr == MAP_FAILED) { perror("mmap error"); exit(0); } const char* pt = "父进程,往内存映射区写数据..."; memcpy(ptr, pt, strlen(pt) + 1);
munmap(ptr, 4000);
return 0; }
|
进程 B 的示例代码:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28
| #include <stdio.h> #include <stdlib.h> #include <unistd.h> #include <string.h> #include <sys/mman.h> #include <fcntl.h>
int main() { int fd = open("./english.txt", O_RDWR); void* ptr = mmap(NULL, 4000, PROT_READ | PROT_WRITE, MAP_SHARED, fd, 0); if (ptr == MAP_FAILED) { perror("mmap error"); exit(0); }
printf("子进程,从内存映射区读出的数据: %s\n", (char*)ptr);
munmap(ptr, 4000);
return 0; }
|
拷贝文件
使用内存映射区除了可以实现进程间通信,也可以进行文件的拷贝。这种方式拷贝文件可以减少开发者的工作量,只需要负责创建内存映射区和打开磁盘文件,不用关心文件中的数据读写。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57
|
#include <stdio.h> #include <stdlib.h> #include <unistd.h> #include <string.h> #include <fcntl.h> #include <sys/mman.h>
int main() { int fd = open("./english.txt", O_RDWR); int size = lseek(fd, 0, SEEK_END);
void* ptrA = mmap(NULL, size, PROT_READ | PROT_WRITE, MAP_SHARED, fd, 0); if (ptrA == MAP_FAILED) { perror("mmap error"); exit(0); }
int fd2 = open("./copy.txt", O_RDWR | O_CREAT, 0664); ftruncate(fd2, size);
void* ptrB = mmap(NULL, size, PROT_READ | PROT_WRITE, MAP_SHARED, fd2, 0); if (ptrB == MAP_FAILED) { perror("2th mmap error"); exit(0); }
memcpy(ptrB, ptrA, size);
munmap(ptrA, size); munmap(ptrB, size); close(fd); close(fd2);
return 0; }
|
参考资料
https://subingwen.cn/linux/mmap