内存映射概述
内存映射(Memory-mapped I/O)是将磁盘文件的数据映射到内存,用户通过修改 内存就能修改磁盘文件。
内存映射相关系统调用
◼ #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.释放资源
*/
#include <stdio.h>
#include <sys/mman.h>
#include <sys/types.h>
#include <sys/stat.h>
#include <fcntl.h>
#include <unistd.h>
#include <string.h>
#include <stdlib.h>
int main() {
// 1.对原始的文件进行内存映射
int fd = open("english.txt", O_RDWR);
if(fd == -1) {
perror("open");
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");
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");
exit(0);
}
if(ptr1 == MAP_FAILED) {
perror("mmap");
exit(0);
}
// 内存拷贝
memcpy(ptr1, ptr, len);
// 释放资源
munmap(ptr1, len);
munmap(ptr, len);
close(fd1);
close(fd);
return 0;
}
匿名映射
Linux中的mmap系统调用可以将一个文件映射到进程的地址空间,以便于对该文件进行读写操作。而匿名映射则是一种特殊的mmap映射方式,它不映射任何文件,而是直接在进程的地址空间中创建一段指定大小的内存区域,通常用于实现进程间共享内存或者临时存储数据等场景。
在匿名映射中,我们可以通过mmap系统调用指定需要映射的内存大小、内存保护模式、映射标志以及共享内存的文件描述符等参数,通常使用MAP_SHARED来表示共享该内存区域,并且该内存区域会被清零初始化。
匿名映射最常见的应用场景是进程间通信,它可以提供一种高效的共享内存的机制,避免了进程之间频繁地进行复制或者传输大量数据的操作。例如,一个多线程程序可以通过匿名映射来实现线程之间的消息传递或者公共资源的访问。当然,由于匿名映射所映射的内存仅存在于当前进程的地址空间中,因此需要其他进程能够访问该共享内存,我们还需要借助一些IPC(进程间通信)机制,如管道、消息队列、信号量、共享内存等方式来实现。
/*
匿名映射:不需要文件实体进程一个内存映射
*/
#include <stdio.h>
#include <sys/mman.h>
#include <sys/types.h>
#include <sys/stat.h>
#include <fcntl.h>
#include <unistd.h>
#include <string.h>
#include <stdlib.h>
#include <sys/wait.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(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");
exit(0);
}
return 0;
}
内存映射注意事项
1.如果对mmap的返回值(ptr)做++操作(ptr++), munmap是否能够成功?
void * ptr = mmap(…);
ptr++; 可以对其进行++操作
munmap(ptr, len); // 错误,要保存地址
2.如果open时O_RDONLY, mmap时prot参数指定PROT_READ | PROT_WRITE会怎样?
错误,返回MAP_FAILED
open()函数中的权限建议和prot参数的权限保持一致。
3.如果文件偏移量为1000会怎样?
偏移量必须是4K的整数倍,返回MAP_FAILED
4.mmap什么情况下会调用失败?
- 第二个参数:length = 0
- 第三个参数:prot
- 只指定了写权限
- prot PROT_READ | PROT_WRITE
第5个参数fd 通过open函数时指定的 O_RDONLY / O_WRONLY
5.可以open的时候O_CREAT一个新文件来创建映射区吗?
- 可以的,但是创建的文件的大小如果为0的话,肯定不行
- 可以对新的文件进行扩展
- lseek()
- truncate()
6.mmap后关闭文件描述符,对mmap映射有没有影响?
int fd = open(“XXX”);
mmap(,fd,0);
close(fd);
映射区还存在,创建映射区的fd被关闭,没有任何影响。
7.对ptr越界操作会怎样?
void * ptr = mmap(NULL, 100,);
4K
ap后关闭文件描述符,对mmap映射有没有影响?
int fd = open(“XXX”);
mmap(,fd,0);
close(fd);
映射区还存在,创建映射区的fd被关闭,没有任何影响。
7.对ptr越界操作会怎样?
void * ptr = mmap(NULL, 100,);
4K
越界操作操作的是非法的内存 -> 段错误