内存映射实现进程间通信
内存映射,Memory-mapped I/O
,是将磁盘文件的数据映射到内存,用户通过修改内存就能修改磁盘文件。
内存映射对应的虚拟地址空间是动态库/共享库所在的区域;映射的内存数据修改,文件数据也会对应修改,如果文件也映射到另一个进程,那么另一个进程就可以获取到修改后的数据。
1. 内存映射相关系统函数
内存映射相关系统函数在头文件 #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()
函数:
- 功能:
将文件或设备数据映射到内存中
;- 参数:
void *addr
:传递NULL,由内核指定该地址
size_t length
:映射的数据的长度,值不能为0,建议使用文件的长度,单位字节;stat()或>lseek()函数获取文件长度;最少是分页的整数倍;
int prot
:对申请的内存映射区的操作权限,可以使用按位或|操作;要操作映射内存,需要有读的权限;
PROT_NONE
:没有权限PROT_EXEC
:可执行权限PROT_READ
:读权限PROT_WRITE
:写权限- 推荐使用的权限:
PROT_READ
、PROT_READ|PROT_WRITE
int flags
:
MAP_SHARED
:映射区的数据自动和磁盘文件同步,进程间通信需要设置>MAP_SHAREDMAP_PRIVATE
:不同步,内存映射区的数据改变不会修改原来的文件,底层会重新创建一个文件,copy on write
int fd
:需要映射的文件的文件描述符,通过open()
打开磁盘文件得到;需要注意文件大小不能为0、open()
指定的权限不能和prot参数冲突(prot的权限 < fd的权限);
off_t offset
:文件映射时的偏移量,必须指定为4k的整数倍,一般不用设置为0,表示不偏移,从文件开头开始映射;- 返回值:成功返回创建的内存的地址,失败返回宏MAP_FAILED
munmap()
函数:
- 功能:释放内存映射
- 参数:
void *addr
:要释放的内存的首地址size_t length
:要释放的内存的大小,和mmap()
函数中的length
参数设置一样
内存映射可以实现有亲缘关系的进程间通信:
- 首先创建内存映射区;
- 然后创建子进程;
- 父子进程间共享创建的内存映射区;
也可以实现没有亲缘关系的进程间通信:
- 首先准备一个大小不是0的磁盘文件;
- 进程1通过磁盘文件创建内存映射区,得到操作内存的指针;
- 进程2通过磁盘文件创建内存映射区,得到操作内存的指针;
- 使用内存映射区通信;
内存映射区通信是非阻塞的。
2. 内存映射使用示例
- 使用内存映射实现父子进程之间的通信。
#include <stdio.h>
#include <sys/mman.h> // 内存映射相关头文件
#include <sys/types.h>
#include <unistd.h>
#include <fcntl.h>
#include <stdlib.h>
#include <string.h>
#include <sys/wait.h>
int main()
{
char *filename = "test.txt";
// 打开文件
int fd = open(filename, O_RDWR);
if (fd == -1)
{
perror("open");
exit(-1);
}
// 获取文件大小
off_t len = lseek(fd, 0, SEEK_END);
// 创建内存映射区
void *ptr = mmap(NULL, len, PROT_READ | PROT_WRITE, MAP_SHARED, fd, 0);
if (ptr == MAP_FAILED)
{
perror("mmap");
exit(-1);
}
// 创建子进程
pid_t pid = fork();
if (pid > 0)
{
wait(NULL);
// 父进程 读数据
char buf[1024] = {0};
strcpy(buf, (char *)ptr);
printf("receive: %s \n", buf);
}
else if (pid == 0)
{
// 子进程 写数据
strcpy((char *)ptr, "hello,father!!");
}
// 关闭内存映射区
munmap(ptr, len);
// 关闭文件
close(fd);
return 0;
}
运行结果:
zoya@zoya-virtual-machine:~/Linux/chap2/lesson12$ ./mmap
receive: hello,father!!
- 使用内存映射实现没有亲缘关系的进程间通信。
mmap-read.c
:
// 内存映射实现进程间通信的读数据
#include <stdio.h>
#include <sys/stat.h>
#include <sys/types.h>
#include <sys/mman.h>
#include <sys/wait.h>
#include <fcntl.h>
#include <stdlib.h>
#include <unistd.h>
#include <string.h>
int main()
{
// 1. 打开文件获取文件描述符,获取文件大小
char *filename = "test.txt";
int fd = open(filename, O_RDWR);
if (fd == -1)
{
perror("open");
exit(-1);
}
// 2. 获取文件长度
off_t len = lseek(fd, 0, SEEK_END);
if (len == -1)
{
perror("lseek");
exit(-1);
}
// 3. 内存映射 mmap
void *ptr = mmap(NULL, len, PROT_READ | PROT_WRITE, MAP_SHARED, fd, 0);
if (ptr == MAP_FAILED)
{
perror("mmap");
exit(-1);
}
// 4. 读取数据
char buf[1024] = {0};
while (1)
{
sleep(1);
memset(buf, 0, sizeof(buf));
strcpy(buf, (char*)ptr);
printf("read : %s \n", buf);
}
// 5. 释放内存映射
munmap(ptr, len);
// 6. 关闭文件
close(fd);
return 0;
}
mmap-write.c
:
// 内存映射实现进程间通信的写数据
#include <stdio.h>
#include <sys/stat.h>
#include <sys/types.h>
#include <sys/mman.h>
#include <sys/wait.h>
#include <fcntl.h>
#include <stdlib.h>
#include <unistd.h>
#include <string.h>
int main()
{
// 1. 打开文件获取文件描述符,获取文件大小
char *filename = "test.txt";
int fd = open(filename, O_RDWR);
if (fd == -1)
{
perror("open");
exit(-1);
}
// 2. 获取文件长度
off_t len = lseek(fd, 0, SEEK_END);
if (len == -1)
{
perror("lseek");
exit(-1);
}
// 3. 内存映射 mmap
void *ptr = mmap(NULL, len, PROT_READ | PROT_WRITE, MAP_SHARED, fd, 0);
if (ptr == MAP_FAILED)
{
perror("mmap");
exit(-1);
}
// 4. 写数据
char buf[1024] = {0};
int nIndex = 0;
while (1)
{
sleep(1);
memset(buf, 0, sizeof(buf));
sprintf(buf, "%d hello!", nIndex++);
printf("write : %s \n", buf);
strcpy((char*)ptr, buf);
}
// 5. 释放内存映射
munmap(ptr, len);
// 6. 关闭文件
close(fd);
return 0;
}
程序运行结果:
运行后test.txt
文件中的内容:
3. 内存映射函数使用注意事项
-
调用
mmap()
函数后错误,错误信息:Permission denied
原因是打开文件时使用的O_RDONLY | O_WRONLY
权限,应该使用O_RDWR
替换。
参考链接:https://www.cnblogs.com/AD-milk/p/14541555.html。 -
文件偏移量必须是4k的整数倍,如果不是整数倍,一般会错误,函数会返回
MAP_FAILED
,会提示错误Invalid argument
。 -
mmap()函数调用失败的情况:
- 参数len=0;
- 参数prot仅指定了写权限;
- 参数prot指定了PROT_READ|PROT_WRITE,但是参数fd打开的权限指定的是O_RDONLY或O_WRONLY;
- mmap()函数调用后可以关闭文件描述符,调用mmap()后,映射区仍然存在,fd被关闭没有影响;
- 对mmap()返回值prt操作越界,会导致段错误。
4. 匿名映射
匿名映射是不需要文件实体进行的内存映射,可以用来进行父子间的通信,如下:
// 匿名映射
#include <stdio.h>
#include <sys/stat.h>
#include <sys/types.h>
#include <sys/mman.h>
#include <sys/wait.h>
#include <fcntl.h>
#include <stdlib.h>
#include <unistd.h>
#include <string.h>
int main()
{
// 1. 创建匿名内存映射区
int len = 4096;
void *ptr = mmap(NULL, len, PROT_READ | PROT_WRITE, MAP_SHARED | MAP_ANONYMOUS, -1, 0); // 匿名映射
if (ptr == MAP_FAILED)
{
perror("mmap");
exit(-1);
}
// 父子进程间通信
pid_t pid = fork();
if (pid > 0)
{
// 父进程
// 写数据
strcpy((char *)ptr, "hello,world!");
wait(NULL);
}
else if (pid == 0)
{
// 子进程
// 读取数据
sleep(1);
printf("%s\n", (char *)ptr);
}
// 释放内存映射区
int ret = munmap(ptr, len);
if (ret == -1)
{
perror("munmap");
exit(-1);
}
return 0;
}