#Linux内存管理# 详细介绍使用mmap函数创立共享文件映射的工作原理
使用 mmap 创建共享文件映射 - 详细解析与代码示例
共享文件映射的工作原理
共享文件映射 (MAP_SHARED) 允许多个进程访问同一文件的相同内存区域,所有修改都会同步回文件,并实时对其他进程可见:
1.内存地址映射
不同进程将同一文件映射到各自虚拟地址空间的不同地址处
2.页面共享机制
所有进程共享内核中的同一份物理内存页缓存
3.同步更新
任一进程的修改会:
立即反映到其他进程的内存视图
由内核管理同步回磁盘文件
4.零拷贝优势
完全避免进程间数据复制
完整代码示例
#include <stdio.h>
#include <stdlib.h>
#include <fcntl.h>
#include <sys/mman.h>
#include <sys/stat.h>
#include <sys/wait.h>
#include <unistd.h>
#include <string.h>
#include <time.h>
#define FILENAME "shared_file.bin"
#define FILESIZE 4096 // 建议使用页大小的倍数(通常4KB)
#define DATA_SIZE 256
// 共享内存区域结构体
struct shared_data {
int counter;
char timestamp[DATA_SIZE];
char message[DATA_SIZE];
};
void writer_process(void *shared_ptr) {
struct shared_data *data = (struct shared_data *)shared_ptr;
for (int i = 0; i < 5; i++) {
// 更新计数器
data->counter++;
// 更新时间戳
time_t now = time(NULL);
strftime(data->timestamp, DATA_SIZE, "%Y-%m-%d %H:%M:%S", localtime(&now));
// 更新消息
snprintf(data->message, DATA_SIZE, "进程 %d 写入了第 %d 条数据", getpid(), data->counter);
printf("写入进程 [%d]: 计数器=%d | %s\n", getpid(), data->counter, data->message);
// 模拟处理时间
usleep(800000); // 800ms
}
}
void reader_process(void *shared_ptr) {
struct shared_data *data = (struct shared_data *)shared_ptr;
for (int i = 0; i < 10; i++) {
printf("读取进程 [%d]: 计数器=%d | %s | 时间: %s\n",
getpid(), data->counter, data->message, data->timestamp);
// 模拟处理时间
usleep(500000); // 500ms
}
}
int main() {
int fd = open(FILENAME, O_RDWR | O_CREAT, 0666);
if (fd == -1) {
perror("文件打开失败");
exit(EXIT_FAILURE);
}
// 设置文件大小
if (ftruncate(fd, FILESIZE) == -1) {
perror("文件大小设置失败");
close(fd);
exit(EXIT_FAILURE);
}
// 创建共享文件映射
void *shared_ptr = mmap(NULL, FILESIZE,
PROT_READ | PROT_WRITE,
MAP_SHARED,
fd, 0);
if (shared_ptr == MAP_FAILED) {
perror("内存映射失败");
close(fd);
exit(EXIT_FAILURE);
}
// 初始化共享区域
struct shared_data *data = (struct shared_data *)shared_ptr;
data->counter = 0;
strcpy(data->timestamp, "未初始化");
strcpy(data->message, "等待数据...");
printf("=============================================\n");
printf("共享文件映射创建成功!\n");
printf("文件: %s\n", FILENAME);
printf("映射地址: %p\n", shared_ptr);
printf("文件大小: %d 字节\n", FILESIZE);
printf("=============================================\n\n");
// 创建两个子进程
pid_t pid_reader = fork();
if (pid_reader == 0) {
// 第一个子进程 - 读取进程
reader_process(shared_ptr);
exit(EXIT_SUCCESS);
}
pid_t pid_writer = fork();
if (pid_writer == 0) {
// 第二个子进程 - 写入进程
writer_process(shared_ptr);
exit(EXIT_SUCCESS);
}
// 等待所有子进程退出
wait(NULL);
wait(NULL);
// 解除映射
if (munmap(shared_ptr, FILESIZE) == -1) {
perror("取消映射失败");
}
close(fd);
printf("\n最终结果:\n");
printf(" 计数器: %d\n", data->counter);
printf(" 时间戳: %s\n", data->timestamp);
printf(" 消息: %s\n", data->message);
printf(" 结果已保存到文件 '%s'\n", FILENAME);
return 0;
}
关键代码解析
1. 创建共享文件映射
void *shared_ptr = mmap(NULL, FILESIZE,
PROT_READ | PROT_WRITE,
MAP_SHARED, // 关键参数:共享映射
fd, 0);
MAP_SHARED: 确保修改对其他进程和文件可见
PROT_READ | PROT_WRITE: 进程具有读写权限
文件大小设置为4096字节(系统页大小的倍数)
2. 数据同步机制
// 写入进程
data->counter++;
strftime(data->timestamp, ...);
snprintf(data->message, ...);
// 读取进程
printf("%s", data->message); // 立即看到更新
写入进程的任何修改都会实时反映在读取进程中
所有修改最终都会自动同步回文件
3. 结构化数据共享
struct shared_data {
int counter;
char timestamp[DATA_SIZE];
char message[DATA_SIZE];
};
定义清晰的数据结构规范共享内存格式
所有进程使用相同的内存布局
4. 文件资源管理
// 设置文件大小
ftruncate(fd, FILESIZE);
// 清理资源
munmap(shared_ptr, FILESIZE);
close(fd);
使用 ftruncate 预先设置文件大小
程序退出前取消映射并关闭文件
5. 编译运行与输出示例
# 编译程序
gcc shared_mmap.c -o shared_mmap
# 运行程序
./shared_mmap
示例输出:
=============================================
共享文件映射创建成功!
文件: shared_file.bin
映射地址: 0x7f8d9a2a9000
文件大小: 4096 字节
=============================================
读取进程 [29876]: 计数器=0 | 等待数据... | 时间: 未初始化
写入进程 [29875]: 计数器=1 | 进程 29875 写入了第 1 条数据
读取进程 [29876]: 计数器=1 | 进程 29875 写入了第 1 条数据 | 时间: 2023-08-05 14:30:22
读取进程 [29876]: 计数器=1 | 进程 29875 写入了第 1 条数据 | 时间: 2023-08-05 14:30:22
写入进程 [29875]: 计数器=2 | 进程 29875 写入了第 2 条数据
读取进程 [29876]: 计数器=2 | 进程 29875 写入了第 2 条数据 | 时间: 2023-08-05 14:30:23
...(更多交替输出)...
读取进程 [29876]: 计数器=5 | 进程 29875 写入了第 5 条数据 | 时间: 2023-08-05 14:30:26
最终结果:
计数器: 5
时间戳: 2023-08-05 14:30:26
消息: 进程 29875 写入了第 5 条数据
结果已保存到文件 'shared_file.bin'
优势与应用场景
1.高性能进程通信
比管道、套接字等传统IPC快10-100倍
避免数据复制开销
2.实时数据共享
进程间零延迟数据访问
适用于高频交易、实时监控系统
3.大型文件处理
高效处理超大文件(GB~TB级)
使用 mmap 比 read/write 节省CPU资源
4.内存数据库
通过文件映射实现持久化
故障后数据不丢失
注意事项
1.内存对齐
// 获取系统页大小
long page_size = sysconf(_SC_PAGESIZE);
2.同步机制
对共享数据的复杂操作需添加互斥锁
使用 POSIX 信号量或互斥锁
3.边界安全
避免访问超出映射区域的内存
错误示例:data->message[DATA_SIZE] = 'x';
4.文件管理
修改大小后需重新映射
使用 msync() 强制同步到磁盘
此代码展示了 mmap 共享文件映射的核心机制,实际应用时可结合业务需求进行扩展和优化。