Linux多进程11-内存映射
内存映射(Memory-mapped I/O)是将磁盘文件的数据映射到内存,用户通过修改内存就能修改磁盘文件。
mmap
#include <sys/mman.h>
void *mmap(void *addr, size_t length, int prot, int flags, int fd, off_t offset);
功能: 将一个文件或者设备的数据映射到内存中
参数:
- void* addr: NULL 由内核指定
- length: 要映射的数据的长度, 这个值不能为0, 建议使用文件的长度
(获取文件的长度: stat lseek)
- prot: 对申请的内存映射区的操作权限
- PROT_EXEC: 可执行的权限
- PROT_READ: 读权限
- PROT_WRITE: 写权限
- PROT_NONE: 没有权限
要操作映射内存, 必须要有读权限, 如: PROT_READ|PROT_WRITE
- flags:
- MAP_SHARED: 映射区的数据会自动和磁盘文件进行同步, 进程间通信,必须要设置这个选项
- MAP_PRIVATE: 不同步, 内存映射区的数据改变了, 对原来的文件不会修改, 会重新创建一个新的文件(copy on write)
- fd: 需要映射的那个文件的文件描述符
- 通过open得到, open的是一个磁盘文件
- 注意: 文件的大小不能为0, open指定的权限不能和prot参数有冲突(prot参数要小于等于open参数)
- offset: 偏移量(一般不用), 必须指定的是4K的整数倍, 0表示不偏移
返回:
返回创建的内存的首地址
失败返回 MAP_FAILED, (void*) -1
int munmap(void *addr, size_t length);
功能: 释放内存映射
参数:
- addr: 要释放的内存的首地址
- length: 要释放的内存大小, 要和mmap函数中的length参数值一样
//使用内存映射实现进程间通信
1. 有关系的进程(父子进程)
- 还没有子进程的时候, 通过唯一的父进程, 先创建内存映射区
- 有了内存映射区以后, 创建子进程
- 父子进程共享创建的内存映射区
2. 没有关系的进程间通信
- 准备一个大小不是0的磁盘文件
- 进程1 通过磁盘文件创建内存映射区, 得到一个操作这块内存的指针
- 进程2 通过磁盘文件创建内存映射区, 得到一个操作这块内存的指针
- 使用内存映射区通信
注意: 内存映射区通信是非阻塞的
实例: 内存映射实现父子进程通信
test.txt
helloworld,helloworld,helloworld,helloworld,helloworld,helloworld,helloworld,helloworld,helloworld,helloworld,helloworld,helloworld.
mmap-parent-child-ipc.c
#include <sys/mman.h>
#include <stdio.h>
#include <sys/types.h>
#include <sys/stat.h>
#include <fcntl.h>
#include <unistd.h>
#include <stdlib.h>
#include <string.h>
#include <wait.h>
int main(int argc, char const *argv[])
{
//打开一个文件
int fd = open("test.txt", O_RDWR);
//获取文件大小
int size = lseek(fd, 0, SEEK_END);
//创建内存映射区
void *ptr = mmap(NULL, size, PROT_READ | PROT_WRITE, MAP_SHARED, fd, 0);
if (ptr == MAP_FAILED)
{
perror("mmap err");
exit(0);
}
//创建子进程
pid_t pid = fork();
if (pid > 0)
{
//回收完子进程的资源之后再读数据
wait(NULL);
//父进程 读数据
char buf[64];
strcpy(buf, (char *)ptr);
printf("read data: %s\n", buf);
}
else if (pid == 0)
{
//子进程 写数据
strcpy((char *)ptr, "nihao a, son!");
}
//关闭内存映射区
munmap(ptr, size);
return 0;
}
运行程序
$./mpci
read data: nihao a, son!
内存映射的注意事项
◼ 如果对mmap的返回值(ptr)做++操作(ptr++), munmap是否能够成功?
void * ptr = mmap(...); ptr++;
可以对其进行++操作
但是 munmap(ptr, len);
释放内存映射操作会产生错误, 要提前保存地址
◼ 如果open时O_RDONLY, mmap时prot参数指定PROT_READ | PROT_WRITE会怎样?
错误, 会返回MAP_FAILED; 建议open函数权限建议和prot参数权限保持一致
◼ 如果文件偏移量为1000会怎样?
偏移量必须是4K的整数倍, 返回MAP_FAILED
◼ mmap什么情况下会调用失败?
- 第二个参数: length=0
- 第三个参数: prot
- 只指定了写权限
- prot函数 PROT_READ | PROT_WRITE
- 第五个参数 fd 通过open函数时指定的 O_RDONLY / O_WRONLY
- 第六个参数偏移量必须是4K整数倍
◼ 可以open的时候O_CREAT一个新文件来创建映射区吗?
可以的, 但创建的文件大学不能为0; 可以使用 lseek() / truncate() 函数对新文件扩展
◼ mmap后关闭文件描述符,对mmap映射有没有影响?
没有任何影响, close(fd);
后映射区还存在, 内存还是没有释放(munmap)
◼ 对ptr越界操作会怎样?
void* ptr = mmap(NULL, 100, ,,,,);
越界操作是操作非法的内存 -> 段错误
内存映射完成文件复制
//使用内存映射实现文件拷贝功能
#include <stdio.h>
#include <sys/mman.h>
#include <sys/types.h>
#include <sys/stat.h>
#include <fcntl.h>
#include <stdlib.h>
#include <unistd.h>
#include <string.h>
int main(int argc, char const *argv[])
{
// 1.对原始文件进行内存映射
int fd = open("english.txt", O_RDWR);
if (fd == -1)
{
perror("open err");
exit(0);
}
//获取原始文件大小
int len = lseek(fd, 0, SEEK_END);
// 2.创建一个新文件
int fd1 = open("cpy.txt", O_RDWR | O_CREAT, 0664);
if (fd1 == -1)
{
perror("open err");
exit(0);
}
//拓展该文件
truncate("cpy.txt", len);
write(fd1, " ", 1);
// 3.把新文件数据映射到内存中
void *ptr = mmap(NULL, len, PROT_READ | PROT_WRITE, MAP_SHARED, fd, 0);
void *ptr1 = mmap(NULL, len, PROT_READ | PROT_WRITE, MAP_SHARED, fd1, 0);
if (ptr == MAP_FAILED)
{
perror("mmap err");
exit(0);
}
// 4.通过内存拷贝将一个新的文件内存数据拷贝到新的文件内存中
memcpy(ptr1, ptr, len);
// 5.释放资源 (后打开先释放)
munmap(ptr1, len);
munmap(ptr, len);
close(fd1);
close(fd);
return 0;
}
匿名映射
/*
匿名映射: 不需要文件实体进行一个内存映射, 因此也只能做父子进程通信
*/
#include <stdio.h>
#include <sys/mman.h>
#include <sys/types.h>
#include <sys/stat.h>
#include <fcntl.h>
#include <stdlib.h>
#include <unistd.h>
#include <string.h>
#include <sys/wait.h>
int main(int argc, char const *argv[])
{
// 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 err");
exit(0);
}
//父子进程间通信
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 err");
exit(0);
}
return 0;
}
运行程序
$./mmap-anon
hello, world #停1s后打印