内存映射(mmap)
1. 创建内存映射区
实现进程间通信,还可以通过函数创建一块内存映射区,和管道不同的是管道对应的内存空间在内核中,而内存映射区对应的内存空间在进程的用户区(用于加载动态库的那个区域),也就是说进程间通信使用的内存映射区不是一块,而是在每个进程内部都有一块。
由于每个进程的地址空间是独立的,各个进程之间也不能直接访问对方的内存映射区,需要**通信的进程需要将各自的内存映射区和同一个磁盘文件进行映射,这样进程之间就可以通过磁盘文件这个唯一的桥梁完成数据的交互了。**
当进程A中的内存映射区数据被修改了,数据会被自动同步到磁盘文件,同时和磁盘文件建立映射关系的其他进程内存映射区中的数据也会和磁盘文件进行数据的实时同步,这个同步机制保障了各个进程之间的数据共享。
创建内存映射区的函数原型如下:
#include <sys/mman.h>
// 创建内存映射区
void *mmap(void *addr, size_t length, int prot, int flags, int fd, off_t offset);
参数:
-
addr: 从动态库加载区的什么位置开始创建内存映射区,一般指定为NULL, 委托内核分配
-
length: 创建的内存映射区的大小(单位:字节),实际上这个大小是按照4k的整数倍去分配的
-
prot: 对内存映射区的操作权限
- PROT_READ: 读内存映射区
- PROT_WRITE: 写内存映射区
- 如果要对映射区有读写权限: PROT_READ | PROT_WRITE
-
flags:
- MAP_SHARED: 多个进程可以共享数据,进行映射区数据同步
- MAP_PRIVATE: 映射区数据是私有的,不能同步给其他进程
-
fd: 文件描述符, 对应一个打开的磁盘文件,内存映射区通过这个文件描述符和磁盘文件建立关联
-
offset: 磁盘文件的偏移量,文件从偏移到的位置开始进行数据映射,使用这个参数需要注意两个问题:
- 偏移量必须是4k的整数倍, 写0代表不偏移
- 这个参数必须是大于 0 的
返回值:
- 成功: 返回一个内存映射区的起始地址
- 失败: MAP_FAILED (that is, (void *) -1)
1. 第一个参数 addr 指定为 NULL 即可
2. 第二个参数 length 必须要 > 0
3. 第三个参数 prot,进程间通信需要对内存映射区有读写权限,因此需要指定为:PROT_READ | PROT_WRITE
4. 第四个参数 flags,如果要进行进程间通信, 需要指定 MAP_SHARED
5. 第五个参数 fd,打开的文件必须大于0,进程间通信需要文件操作权限和映射区操作权限相同
- 内存映射区创建成功之后, 关闭这个文件描述符不会影响进程间通信
6. 第六个参数 offset,不偏移指定为0,如果偏移必须是4k的整数倍
释放函数
int munmap(void *addr, size_t length);
参数:
- addr: mmap()的返回值, 创建的内存映射区的起始地址
- length: 和mmap()第二个参数相同即可
返回值:函数调用成功返回 0,失败返回 -1
2. 进程间通信
管道的读写是阻塞的,内存映射区的读写是非阻塞的。内存映射区创建成功之后,得到了映射区内存的起始地址,使用相关的内存操作函数读写数据就可以
2.1 有血缘关系
由于创建子进程会发生虚拟地址空间的复制,那么在父进程中创建的内存映射区也会被复制到子进程中,这样在子进程里边就可以直接使用这块内存映射区了,所以对于有血缘关系的进程,进行进程间通信是非常简单的,处理代码如下:
#include <stdlib.h>
#include <unistd.h>
#include <string.h>
#include <stdio.h>
#include <sys/mman.h>
#include <fcntl.h>
int main()
{
// 1. 打开一个磁盘文件
int fd = open("./abc.txt", O_RDWR);
// 2. 创建内存映射区
void* ptr = mmap(NULL, 4000, PROT_READ|PROT_WRITE,
MAP_SHARED, fd, 0);
if(ptr == MAP_FAILED)
{
perror("mmap");
exit(0);
}
// 3. 创建子进程
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;
}
2.2 没有血缘关系
进程A:
#include <stdio.h>
#include <stdlib.h>
#include <unistd.h>
#include <string.h>
#include <sys/mman.h>
#include <fcntl.h>
int main()
{
// 1. 打开一个磁盘文件
int fd = open("./english.txt", O_RDWR);
// 2. 创建内存映射区
void* ptr = mmap(NULL, 4000, PROT_READ|PROT_WRITE,
MAP_SHARED, fd, 0);
if(ptr == MAP_FAILED)
{
perror("mmap");
exit(0);
}
const char* pt = "我是你爹";
memcpy(ptr, pt, strlen(pt)+1);
// 释放内存映射区
munmap(ptr, 4000);
return 0;
}
进程B:
#include <stdio.h>
#include <stdlib.h>
#include <unistd.h>
#include <string.h>
#include <sys/mman.h>
#include <fcntl.h>
int main()
{
// 1. 打开一个磁盘文件
int fd = open("./english.txt", O_RDWR);
// 2. 创建内存映射区
void* ptr = mmap(NULL, 4000, PROT_READ|PROT_WRITE,
MAP_SHARED, fd, 0);
if(ptr == MAP_FAILED)
{
perror("mmap");
exit(0);
}
// 读内存映射区
printf("从映射区读出的数据: %s\n", (char*)ptr);
// 释放内存映射区
munmap(ptr, 4000);
return 0;
}
3. 文件拷贝
使用内存映射区拷贝文件思路:
- 打开被拷贝文件,得到文件描述符 fd1,并计算出这个文件的大小 size
- 创建内存映射区A并且和被拷贝文件关联,也就是和fd1关联起来,得到映射区地址 ptrA
- 创建新文件,得到文件描述符 fd2,用于存储被拷贝的数据,并且将这个文件大小拓展为 size
- 创建内存映射区B并且和新创建的文件关联,也就是和fd2关联起来,得到映射区地址 ptrB
- 进程地址空间之间的数据拷贝,memcpy(ptrB, ptrA,size),数据自动同步到新建文件中
- 关闭内存映射区
代码如下:
#include <stdio.h>
#include <stdlib.h>
#include <unistd.h>
#include <string.h>
#include <fcntl.h>
#include <sys/mman.h>
int main()
{
// 1. 打开一个操盘文件english.txt得到文件描述符
int fd = open("./english.txt", O_RDWR);
// 计算文件大小
int size = lseek(fd, 0, SEEK_END);
// 2. 创建内存映射区和english.txt进行关联, 得到映射区起始地址
void* ptrA = mmap(NULL, size, PROT_READ|PROT_WRITE, MAP_SHARED, fd, 0);
if(ptrA == MAP_FAILED)
{
perror("mmap");
exit(0);
}
// 3. 创建一个新文件, 存储拷贝的数据
int fd1 = open("./copy.txt", O_RDWR|O_CREAT, 0664);
// 拓展这个新文件
ftruncate(fd1, size);
// 4. 创建一个映射区和新文件进行关联, 得到映射区的起始地址second
void* ptrB = mmap(NULL, size, PROT_READ|PROT_WRITE, MAP_SHARED, fd1, 0);
if(ptrB == MAP_FAILED)
{
perror("mmap----");
exit(0);
}
// 5. 使用memcpy拷贝映射区数据
// 这两个指针指向两块内存, 都是内存映射区
// 指针指向有效的内存, 拷贝的是内存中的数据
memcpy(ptrB, ptrA, size);
// 6. 释放内存映射区
munmap(ptrA, size);
munmap(ptrB, size);
close(fd);
close(fd1);
return 0;
}