linux开发之mmap内存映射
mmap概念
mmp是 将文件或设备直接映射到进程的虚拟内存空间 的一种机制,可实现程序像访问内存一样访问文件,而不需要传统的 read()/write()系统调用
文件内容被映射到进程的地址空间,读写文件就像操作内存一样,操作系统负责自动同步内存和文件的修改(除非显式禁用)。
好处:
- 文件 I/O 优化:避免频繁的 read()/write(),提升性能,例如数据库引擎(如 SQLite、LevelDB)用 mmap加速磁盘访问
- 多个进程需要高效共享数据(如父子进程、独立进程),比管道、消息队列更快(直接内存访问),Chrome 浏览器用 mmap共享多个标签页的内存,机器学习训练时,多个进程共享模型参数
- 序运行时加载动态链接库(.so/.dll)时,库文件被映射到内存,按需加载(懒加载),节省内存
- sendfile:文件通过 mmap映射到内存,直接通过 DMA 发送到网卡,无需 CPU 参与拷贝
语法:函数声明
void *mmap(void *addr, size_t length, int prot, int flags, int fd, off_t offset);
参数 | 说明 |
---|---|
addr | 建议映射的起始地址(通常传 NULL ,由系统自动分配) |
length | 映射区域的长度(字节) |
prot | 内存保护方式(PROT_READ , PROT_WRITE 等) |
flags | 映射类型(MAP_SHARED , MAP_PRIVATE ) |
fd | 文件描述符(通过 open 获得) |
offset | 文件映射起始偏移(必须是页大小的整数倍) |
相关常量:
// 保护方式
PROT_READ // 可读
PROT_WRITE // 可写
PROT_EXEC // 可执行
PROT_NONE // 不可访问// 映射类型
MAP_SHARED // 修改会写回文件,进程间共享
MAP_PRIVATE // 写时复制,修改不写回文件// 取消映射
int munmap(void *addr, size_t length); // 释放映射
mmap写文件
[root@prs31 01-fd]# cat 01-mmap_read.c
// 01-mmap_read.c#include <stdio.h>
#include <stdlib.h>
#include <fcntl.h>
#include <unistd.h>
#include <sys/mman.h>
#include <sys/stat.h>int main() {int fd;struct stat sb;char *mapped;fd = open("test.txt", O_RDONLY);if (fd == -1) {perror("open");exit(1);}// 2. 获取文件状态(主要是大小)if (fstat(fd, &sb) == -1) {perror("fstat");exit(1);}// 3. 映射文件到内存mapped = mmap(NULL, sb.st_size, PROT_READ, MAP_SHARED, fd, 0);if (mapped == MAP_FAILED) {perror("mmap");close(fd);exit(1);}// 4. 像访问数组一样读取内容printf("文件内容:\n");for (off_t i = 0; i < sb.st_size; i++) {putchar(mapped[i]); // 逐字符打印内存映射文件(mmap)的内容}// 5. 解除映射if (munmap(mapped, sb.st_size) == -1) {perror("munmap");}close(fd);return 0;
}
# mmap追加文件内容
[root@prs31 01-fd]# cat 02-mmap_write.c
// mmap_write.c
#include <stdio.h>
#include <stdlib.h>
#include <fcntl.h>
#include <unistd.h>
#include <sys/mman.h>
#include <sys/stat.h>
#include <string.h>int main() {int fd;struct stat sb;char *mapped;const char *new_content = "\nAppended via mmap!";// 1. 以读写方式打开文件fd = open("test.txt", O_RDWR);if (fd == -1) {perror("open");exit(1);}// 2. 获取文件大小if (fstat(fd, &sb) == -1) {perror("fstat");exit(1);}// 3. 扩展文件大小(为追加内容预留空间)off_t new_size = sb.st_size + strlen(new_content);if (ftruncate(fd, new_size) == -1) {perror("ftruncate");close(fd);exit(1);}// 4. 映射整个新大小的文件,这里传递使用扩展后的文件大小mapped = mmap(NULL, new_size, PROT_READ | PROT_WRITE, MAP_SHARED, fd, 0);if (mapped == MAP_FAILED) {perror("mmap");close(fd);exit(1);}// 5. 在原内容末尾写入新内容,strcpy(目的变量,要添加的变量)strcpy(mapped + sb.st_size, new_content);printf("已追加内容到文件\n");if (munmap(mapped, new_size) == -1) {perror("munmap");}close(fd);return 0;
}
# mmap实现进程间共享内存
[root@prs31 01-fd]# cat 03-shm_writer.c
// 03-shm_writer.c
#include <stdio.h>
#include <stdlib.h>
#include <fcntl.h>
#include <unistd.h>
#include <sys/mman.h>
#include <sys/stat.h>
#include <string.h>int main() {int fd;char *mapped;// 1. 创建共享文件fd = open("/tmp/shm_file", O_CREAT | O_RDWR, 0644);if (fd == -1) {perror("open");exit(1);}// 2. 扩展文件大小ftruncate(fd, 4096); // 1页// 3. 映射mapped = mmap(NULL, 4096, PROT_READ | PROT_WRITE, MAP_SHARED, fd, 0);if (mapped == MAP_FAILED) {perror("mmap");exit(1);}// 4. 写入数据sprintf(mapped, "Hello from Process A (PID: %d)", getpid());printf("写入: %s\n", mapped);printf("等待按回车继续...\n");getchar(); // 等待进程B读取munmap(mapped, 4096);close(fd);return 0;
}
[root@prs31 01-fd]# cat 03-shm_reader.c
// 03-shm_writer
#include <stdio.h>
#include <fcntl.h>
#include <unistd.h>
#include <stdlib.h>
#include <sys/mman.h>int main() {int fd;char *mapped;fd = open("/tmp/shm_file", O_RDONLY);if (fd == -1) {perror("open");exit(1);}mapped = mmap(NULL, 4096, PROT_READ, MAP_SHARED, fd, 0);if (mapped == MAP_FAILED) {perror("mmap");exit(1);}printf("读取到: %s\n", mapped);munmap(mapped, 4096);close(fd);return 0;
}
运行代码
[root@prs31 01-fd]# gcc 03-shm_writer.c shm_writer
[root@prs31 01-fd]# gcc 03-shm_reader.c shm_reader
第二个终端: